VHDL Episódio 1 - Um simples somador

@ 2011-06-16 by João Paulo Pizani Flor

Episódios anteriores:

Demorou pra começar esse tutorial, mas a desculpa era nobre: estava finalizando o trabalho final de curso de Ciência da Computação, vida de formando é cheia de desculpas… Vamos começar isso de uma vez! O primeiro episódio de nosso tutorial vai girar em torno de um somador binário.

Caso você não saiba o que é um somador binário, a Wikipédia está sempre à disposição: Binary Adder. Nosso primeiro hardware descrito em VHDL vai ser um Full Adder de um bit:

Full adder de um bit
Full adder de um bit

E pra mostrar que VHDL não é tão monstruoso assim, lá vai todo o código desse bloco, numa tacada só:

library ieee;
use ieee.std_logic_1164.all;

entity fullAdder is
    Port(
        a    : in std_logic;
        b    : in std_logic;
        cin  : in std_logic;
        s    : out std_logic;
        cout : out std_logic);
end fullAdder;

architecture arch of fullAdder is
    signal aXorB : std_logic;
begin
    aXorB <= a xor b;
    s     <= aXorB xor cin;
    cout  <= (a and b) or (cin and aXorB);
end arch;

Vamos comentar um pouco o código pra depois então fazer os testes e mostrar que esse incrível somador funciona!

Durante os episódios do tutorial eu vou mostrar o coding style VHDL que eu uso. O primeiro detalhes desse estilo é que cada bloco de hardware fica em um arquivo. O nosso fullAdder, portanto, vai ficar no arquivo fullAdder.vhd.

Cada arquivo VHDL tem DUAS seções principais: entity e architecture. A seção entity descreve a interface do bloco com o mundo externo, e aquele Port(... descreve exatamente o que você está pensando: as portas de entrada e saída do circuito. Cada porta tem um nome, uma direção e um tipo. Aliás, TUDO em VHDL tem um tipo, e isso vai te fazer amar ou odiar a linguagem, ninguém fica indiferente :)

O tipo que estamos usando em todas as portas do somador é std_logic, um tipo de bit que além de ‘0’ e ‘1’ tem mais alguns estados possíveis (alta impedância, indefinido e outros que eu nunca lembro). O tipo std_logic e o seu companheiro std_logic_vector são de longe os mais comuns em todos os códigos VHDL do mundo.

Agora um pouco sobre a segunda seção importante de todo arquivo VHDL: a architecture. Nessa seção são definidos os blocos básicos que usamos pra montar nosso circuito, como nós os interconectamos e quais sinais são usados pra fazer essa interconexão. Logo antes da palavra begin há a seção onde se declara tudo que vai ser usado na architecture, em nosso caso nós declaramos um único sinal:

signal aXorB : std_logic;

Sinais são como fios, ou barramentos. Eles também têm um nome e um tipo, mas não têm direção. Logo depois das declarações começa o corpo da arquitetura propriamente dito. Na nossa arquitetura temos três atribuições paralelas. Cada atribuição liga um sinal (ou porta) a uma expressão. Como a atribuição é paralela, o sinal (ou porta) fica ligado à expressão para sempre, e o compilador VHDL vai reclamar se você tiver duas atribuições conflitantes para o mesmo sinal (é como se fosse um curto-circuito). As atribuições da nossa arquitetura são uma mera cópia das expressões lógicas que definem um Full Adder segundo a Wikipédia :P

S = A xor B xor Cin
Cout = (A and B) or (Cin and (A xor B))

As regras de coding style que dá pra notar nesse primeiro episódio são:

Esse é um estilo de código VHDL que eu acho bonito e fácil de ler. Por favor não comecem o flame war :)

Depois do coding-style vamos pra parte final desse primeiro episódio do tutorial de VHDL: mostrar que o negócio funciona (testar) / Primeiro de tudo, vamos só rodar o GHDL pra conferir que a sintaxe do nosso código está correta:

ghdl -s fullAdder.vhd

Agora vem a parte divertida de verdade: escrever um testbench pro nosso somador. Daqui pra frente, cada component de hardware que nós descrevermos terá um testbench correspondente. Um testbench nada mais é do que um arquivo VHDL que dá algumas entradas para o componente sendo testado e confere se as saídas estão corretas. O testbench do nosso fullAdder é o seguinte:  http://hpaste.org/47881

Dá pra ver que o código é grande, e com certeza há maneiras de deixá-lo menor e mais bonito (nesse caso específico). Eu escolhi escrever o testbench do somador dessa maneira pra demonstrar a filosofia geral de como serão escritos todos os nossos testbenches daqui pra frente.

A seção entity de um testbench sempre é vazia, e na seção architecture um padrão é seguido:

Como no nosso caso (fullAdder) só há 8 possíveis combinações de entradas (3 bits), nós testamos todas elas. Normalmente os casos de teste testam apenas algumas combinações consideradas mais importantes pelo projetista :)

Cada caso de teste tem mais ou menos o mesmo formato:

sig_<0> <= valor_<0>;
...
sig_<1> <= valor_<1>;
wait for x ns;
assert (saida_esperada = saida_real) report "nome_do_teste" severity failure;

Nós fornecemos cada uma das entradas do circuito, depois então esperamos por um tempo necessário para a saída estabilizar-se e checamos se a saída produzida é igual a que esperávamos…

Bem, por hoje era isso, o primeiro episódio foi bem comprido pois várias coisas tinham que ser mostradas “do zero”. Aí vão os exercícios então.

Exercícios pro episódio de hoje:

  1. Baixe o arquivo VHDL1, o descompacte, rode a simulação e veja o trace.vcd gerado no GTKWake. O makefile incluído no pacote vai ajudar você.

  2. Modifique testes, faça alguns falharem e depois os corrija, enfim, mexa no código. Essa mesma estrutura de arquivos e o makefile vão ser usados nos episódios futuros :)