VHDL Episódio 1 - Um simples somador
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:
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:
- Em geral:
- Todos os blocos entre
begin
eend
(ouis
eend
) são indentados (recuados pra direita), SEMPRE com 4 espaços - Os nomes de portas e sinais seguem o formato camelCaseComPrimeiraMinuscula
- Todos os blocos entre
- Na seção
entity
:- A palavra
Port
é indentada 1 nível com relação àentity
A lista de portas em si é indentada 1 nível com relação aoPort(...
- Na lista de portas nós mantemos os dois-pontos alinhados verticalmente
- A palavra
- Na seção
architecture
:- Em uma sequência de atribuições nós alinhamos as flechas (<=) verticalmente
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:
- Declarações:
- Primeiro vem a declaração do
component
a ser testado, nós simplesmente copiamos a partePort(...
do arquivo fullAdder.vhd - Depois declaramos um sinal para cada porta do componente sendo testado
- A linha
for dut: fullAdder...
só serve pra dizer qual entidade usar no teste
- Primeiro vem a declaração do
- Testes em si:
- Logo após o begin nós instanciamos o componente a ser testado. Isto é, ligamos as suas portas aos sinais que declaramos acima. Esses sinais vão ser monitorados no testbench
- Dentro do processo testbench há vários casos de teste, com entradas e saídas esperadas.
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:
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ê.
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 :)