Esse guia visa apresentar os conceitos elementares sobre o VHDL. Ao final desse guia, você será capaz de descrever grande parte dos circuitos costumeiramente vistos nas disciplinas de circuitos digitais. Caso queira se aprofundar, recomendamos consultar nosso curso sobre a estrutura da linguagem.
↪️Características Gerais do VHDL
As principais características do VHDL são:
- Não é case-sensitive, ou seja, palavras em maiúscula e minúscula são interpretadas da mesma forma pelo VHDL;
- Espaços em branco não são interpretados;
- –: Comentário em uma linha;
- As regras para formação de identificadores são:
- Só é permitido letras, números e underline (_);
- O primeiro caractere deve ser uma letra;
- O último não pode ser underline;
- Não são permitidos 2 underline em sequência;
- Letras maiúscula e minúscula são equivalentes (não é case-sensitive).
🏗️Estrutura Básica
A estrutura básica de um código em VHDL é apresentada abaixo.
-- Isso aqui eh um comentario library ieee; use ieee.std_logic_1164.all; <declaração_de_outras_bibliotecas> entity nome_da_entidade is port ( nome_da_porta : modo_da_porta tipo_da_porta; nome_da_porta : modo_da_porta tipo_da_porta; ... nome_da_porta : modo_da_porta tipo_da_porta ); end nome_da_entidade; architecture nome_da_arquitetura of nome_da_entidade is <declaração_de_sinais> <declaração_de_componentes> <declaração_de_constantes> <declaração_de_novos_tipos> begin <descrição_do_circuito> end nome_da_arquitetura;
Todo código em VHDL apresenta três regiões:
- Bibliotecas;
- Entidade e;
- Arquitetura.
Bibliotecas
São coleções de pacotes, funções, procedimentos, e definições de tipos que podem ser reutilizados em vários projetos. Elas fornecem funcionalidades que ajudam a simplificar o processo de design. A principal biblioteca no VHDL é a ieee, sendo que o principal pacote é o std_logic_1164. Para utilizar a biblioteca ieee, fazemos:
library ieee;
Já para utilizar o pacote std_logic_1164, fazemos:
use ieee.std_logic_1164.all;
Se quisermos incluir outra biblioteca, fazemos sempre a seguinte estrutura:
library library_name; use library_name.package_name.package_parts;
Entidade
A entidade é a descrição da interface externa de um componente de hardware. Ela define os pinos de entrada e saída do componente. Uma entidade contém a definição do nome do componente e a lista de portas (pinos). A declaração da entidade simples é feita como:
entity nome_da_entidade is port ( nome_da_porta : modo_da_porta tipo_da_porta; nome_da_porta : modo_da_porta tipo_da_porta; ... nome_da_porta : modo_da_porta tipo_da_porta ); end nome_da_entidade;
A principal diretiva da entidade é a diretiva port. Dentro da diretiva port colocamos o nome das portas, o modo da porta e o tipo.
O modo da porta indica se a porta é entrada, saída ou bidirecional. No caso,
- Se o pino for entrada, utiliza-se o modo in;
- Se o pino for saída, utiliza-se o modo out;
- Se o pino for bidirecional, utiliza-se inout.
Por fim, ocorre a declaração do tipo. Um detalhamento mais aprofundado dos tipos é feito na seção de estrutura da linguagem, porém aqui basta saber alguns tipos elementares:
- bit: admite nível lógico ALTO ou BAIXO;
- bit_vector: coleção do tipo bit. Para fazer a declaração de um bit_vector com N posições procedemos da seguinte forma:
a : in bit_vector(N-1 downto 0);
No caso, a é uma entrada do tipo bit_vector com N posições.
- std_logic: extensão do tipo bit. Além dos níveis lógicos ALTO ou BAIXO, admite estados como alta impedância, don`t care, entre outros. É o tipo mais comum de ser utilizado. Requer, necessariamente, o pacote std_logic_1164 da biblioteca ieee;
- std_logic_vector: coleção do tipo std_logic. Para fazer a declaração de um STD_LOGIC_VECTOR com N posições procedemos da seguinte forma:
x : out std_logic_vector (N-1 downto 0);
No caso x é uma saída do tipo std_logic_vector com N posições.
Exemplo de declaração de entidade: Vamos supor uma entidade denominada de and_gate com entradas A e B, saída Y. A declaração da entidade ficaria assim:
entity and_gate is port ( A : in std_logic; B : in std_logic; Y : out std_logic ); end and_gate;
Notar que a última linha do port não tem o ponto e virgula.
Arquitetura
A arquitetura em VHDL descreve o comportamento interno de uma entidade. Enquanto a entidade representa a interface externa (como uma caixa preta), a arquitetura detalha as operações internas do hardware. Ela declara constantes, sinais e processos que definem o funcionamento do circuito. No VHDL existem três abordagens comuns para se descrever uma arquitetura:
- Abordagem concorrente;
- Abordagem sequencial;
- Abordagem hierarquica.
🧮Abordagem Concorrente
Na abordagem concorrente todos os comandos são executados de forma paralela. Essa abordagem é útil para modelar circuitos combinacionais, como somadores, multiplexadores e unidades lógico-aritmética.
As principais estruturas da abordagem concorrente são os operadores e as estruturas de seleção.
Operadores do VHDL
O VHDL possui vários tipos de operadores, tal como: operadores lógicos, aritméticos, de deslocamento, comparação e entre outros. A tabela abaixo apresenta os principais operadores do VHDL.
A tabela abaixo apresenta os principais operadores lógicos do VHDL.
Tipo de operador | Operadores mais comuns |
---|---|
Atribuição | <=, := e => |
Lógicos | and, or, not, nand, nor, xor e xnor |
Aritméticos | +, -, *, **, /, rem, mod |
Concatenação | & |
Deslocamento | ssl, srl, sla, sra, rol, ror |
Comparação | =, /=, <, <=, >, >= |
Neste guia utilizaremos apenas operadores lógicos. O único operador de atribuição que será detalhado será o <=, que serve para realizar a atribuição de saídas e de valores intermediários ao circuito.
Exemplo 1 – Somador Completo
O somador completo é apresentado na Figura abaixo.
Ele possui três entradas, denominadas de a, b e cin e duas saídas, denominadas de s e cout. As equações que relacionam a entrada e a saída são:
A descrição deste circuito em VHDL seria:
--somadorcompleto.vhd --Somador completo em VHDL --Autor: Pedro Thiago V. de Souza --Bibliotecas LIBRARY ieee; USE ieee.std_logic_1164.all; --Entity ENTITY somadorcompleto IS PORT( a, b, cin : IN STD_LOGIC; s, cout : OUT STD_LOGIC --Ultima linha do PORT nao tem ; ); END somadorcompleto; --Architecture ARCHITECTURE dataflow OF somadorcompleto IS BEGIN s <= a XOR b XOR cin; cout <= (a AND b) OR (a AND cin) OR (b AND cin); END dataflow;
Na construção da abordagem concorrente podemos utilizar signal. Signal são variáveis intermediárias que podem assumir valores de outros circuitos. Do ponto de visto prático, um signal é equivalente a um fio em uma ligação física. Os signal são utilizados nas seguintes premissas:
- Quando se deseja, em um projeto, levar uma informação de um componente a outro ou;
- Quando se deseja atribuir, em um circuito, valores intermediários, resultantes, por exemplo, de expressões lógicas.
Os signal são declarados no preambulo da arquitetura da seguinte forma:
signal signal_name: signal_type [:= default_value];
O valor padrão do signal é opcional, e não utilizaremos aqui neste guia.
Exemplo 2 – Descrevendo um circuito através de SIGNALS
Considere a descrição do circuito apresentado na figura abaixo.
Poderíamos obter a expressão lógica da saída F, porém podemos descrever as expressões lógicas intermediárias utilizando signal como apresentado no código a seguir:
s
No VHDL temos duas estruturas de seleção concorrentes: a when…else e a with…select. A declaração geral da estrutura when…else é a seguinte:
target <= value1 when condition1 else value2 when condition2 else (…) value0;
Neste caso, a saída target assumirá value1 se a condition1 for verdadeira. Caso contrário, a saída target assumirá value2 se condition2 for verdadeira e assim por diante. Caso nenhuma das condições for verdadeira, a saída target assumirá o value0.
Por sua vez, a estrutura with…select possui a seguinte declaração:
with variavel select target <= value1 when condition1, value2 when condition2, (…) value0 when others;
Neste caso, variable será nossa variável de seleção. A saída target assumirá value1 se a variavel for igual à condition1, a saída target assumirá value2 se variavel for igual à condition2 e assim por diante. Caso nenhuma das condições for verdadeira, a saída target assumirá o value0, indicada pela condição when others.
Exemplo 3 – Multiplexador 4:1
Podemos utilizar a estrutura when…else e with…select para implementar um multiplexador 4:1. No caso, vamos implementar um multiplexador com entradas de dados a, b, c e d, bits de seleção s1 e s0 e saída de dados roteada x. A implementação utilizando a estrutura when…else é apresentada abaixo.
library ieee; use ieee.std_logic_1164.all; entity mux_when_else is port( a, b, c, d : in std_logic; s1, s0 : in std_logic; x : out std_logic ); end mux_when_else; architecture arch of mux_when_else is signal sel : std_logic_vector (1 downto 0); begin sel <= s1 & s0; x <= a when sel="00" else b when sel="01" else c when sel="10" else d; end arch;
O signal sel foi declarado para facilitar a descrição da estrutura, pois ao invés de trabalhar com os bits s1 e s0 individualmente, podemos trabalhar diretamente com a concatenação destes bits, armazenado no signal sel. A implementação do mesmo multiplexador através da estrutura with…select é apresentado abaixo:
library ieee; use ieee.std_logic_1164.all; entity mux_with_select is port( a, b, c, d : in std_logic; s1, s0 : in std_logic; x : out std_logic ); end mux_with_select; architecture arch of mux_with_select is signal sel : std_logic_vector (1 downto 0); begin sel <= s1 & s0; with sel select x <= a when "00", b when "01", c when "10", d when others; end arch;
🏛️Abordagem Hierarquica
A abordagem hierarquica utiliza componentes menores para construir componentes maiores. Neste caso, utilizamos as diretivas component e port map.
Os component são blocos elementares de lógica que podem ser ligados entre si para gerar blocos maiores. Os component devem estar definidos no projeto VHDL, no pré-âmbulo da arquitetura, como apresentado abaixo.
component nome_do_componente is port ( nome_da_porta : modo_de_operação tipo_da_porta; nome_da_porta : modo_de_operação tipo_da_porta; ...); end component;
Basicamente a declaração do component pode ser feita copiando-se a entidade no qual desejamos utilizar como component, e trocando-se a palavra entity por component e encerrando a declaração do component usando end component.
Após invocar os componentes, é necessário instancia-los no código. Para isso utilizamos a diretiva port map. O instanciamento dos blocos é feito dentro da arquitetura da seguinte forma:
label: component_name port map (port_list);
No caso label é o nome específico de uma instância de um component. O mapeamento das portas pode ser feito de duas formas: associação por lista ou associação por nome.
Na associação por lista, as entradas/saídas são colocadas na ordem no qual foram declaradas na entidade. Neste caso a ordem importa. Por sua vez, na associação por nome, as entradas/saídas são associadas a pinos específicos da entidade usando o operador =>. Nesta situação, a ordem não importa.
Vamos supor que tenhamos um component chamado nand3, cuja declaração aparece abaixo:
component nand3 is port ( a1, a2, a3: in std_logic; b: out std_logic ); END COMPONENT;
Segue duas formas de se fazer o mapeamento utilizando o port map:
nand3_1: nand3 port map (x1, x2, x3, y); nand3_2: nand3 port map (a1=>x1, a2=>x2, a3=>x3, b=>y);
Veja que ambas resultam no mesmo circuito, porém, na associação por nome (segundo caso), é possível trocar a ordem da associação sem maiores problemas:
nand3_2: nand3 port map (a1=>x1, a3=>x3, a2=>x2, b=>y);
Exemplo 4 – Somador Binário
Inicialmente precisamos descrever nosso somador completo (veja o exemplo 1). Estamos repetindo aqui por comodidade:
--somadorcompleto.vhd --Somador completo em VHDL --Bibliotecas LIBRARY ieee; USE ieee.std_logic_1164.all; --Entity ENTITY somadorcompleto IS PORT( a, b, cin : IN STD_LOGIC; s, cout : OUT STD_LOGIC --Ultima linha do PORT nao tem ; ); END somadorcompleto; --Architecture ARCHITECTURE dataflow OF somadorcompleto IS BEGIN s <= a XOR b XOR cin; cout <= (a AND b) OR (a AND cin) OR (b AND cin); END dataflow;
Após isso podemos descrever nosso somador binário. Neste caso, é importante que ambos os arquivos – o do somador completo e do somador binário estejam no mesmo projeto VHDL. A ligação entre os blocos de somadores completo é feito através de signal.
---somador4b.vhd --Somador de 4 bits utilizando a abordagem estrutural --Autor: Pedro Thiago V. de Souza --Bibliotecas library ieee; use ieee.std_logic_1164.all; --Entidade entity somador4b is port( A : in std_logic_vector(3 downto 0); --a3a2a1a0 = 4 bits B : in std_logic_vector(3 downto 0); --b3b2b1b0 = 4 bits Cin : in std_logic; S: out std_logic_vector(3 downto 0); --s3s2s1s0 = 4 bits Cout: out std_logic --ultima linha do port nao tem ; ); end somador4b; --Arquitetura architecture stucture of somador4b is --Pre-ambulo = components e signals --Component do somadorcompleto component somadorcompleto IS port( a, b, cin : in std_logic; s, cout : out std_logic --Ultima linha do PORT nao tem ; ); end component; --Signals signal c1, c2, c3 : std_logic; begin u0 : somadorcompleto port map (a=>A(0), b=>B(0), cin=>Cin, s=>S(0), cout=>c1); u1 : somadorcompleto port map (a=>A(1), b=>B(1), cin=>c1, s=>S(1), cout=>c2); u2 : somadorcompleto port map (a=>A(2), b=>B(2), cin=>c2, s=>S(2), cout=>c3); u3 : somadorcompleto port map (a=>A(3), b=>B(3), cin=>c3, s=>S(3), cout=>Cout); end stucture;
🔁Abordagem Sequencial
Exemplo 4 – Flip-Flop Tipo D
Exemplo 5 – Registrador de Carga Paralela
--reg4b.vhd --Registrador de 4 bits em VHDL --Autor: Pedro Thiago V. de Souza --Bibliotecas library ieee; use ieee.std_logic_1164.all; --Entidade entity reg4b is port( rst, en, clk : in std_logic; d : in std_logic_vector (3 downto 0); --d3 d2 d1 d0 = 4 bits; q : out std_logic_vector (3 downto 0) --Ultima linha do port nao tem ; ); end reg4b; --Arquitetura architecture comportamento of reg4b is begin --Process: A lista de sensibilidade é o sinal de clk e o de rst, pois toda vida que --essas entradas mudarem, a minha saida deverá ser calculada novamente process (clk, rst) begin if (rst = '1') then --Verifica se houve um rst assincrono q <= (others => '0'); --Equivalente à q <= "0000" elsif (rising_edge(clk)) then --Verifica se houve uma borda de subida do clock if (en = '1') then --Verifica se o sinal en = 1. q <= d; end if; end if; end process; end comportamento;
🎰Máquinas de Estados em VHDL
Exemplo 6 – Máquina de Estados
--fsm.vhd --Maquina de estados implementada em VHDL --Autor: Pedro Thiago V. de Souza --Bibliotecas library ieee; use ieee.std_logic_1164.all; --Entidade entity fsm is port( x, clk, rst: in std_logic; y : out std_logic --Ultima linha do port nao tem ; ); end fsm; --Arquitetura architecture comportamento of fsm is --Pre-Ambulo --Definir um novo tipo, chamado estado, que assume os valores alfa, beta, gamma e delta type estado is (alfa, beta, gamma, delta); --Crio as variaveis de estado atual e estado proximo signal estadoatual, estadoproximo : estado; begin --Process 1: Registrador de estado - lista de sensibilidade: clk, rst registradordeestado : process (clk, rst) begin if (rst = '1') then estadoatual <= alfa; --Estado inicial é o alfa elsif (rising_edge(clk)) then estadoatual <= estadoproximo; --As variaveis de estado proximo passam a ser as variaveis de estado atual end if; end process; --Process 2: Logica Combinacional - listado de sensibilidade: estadoatual, x logicacombinacional : process (estadoatual, x) begin case estadoatual is --Estado alfa when alfa => y <= '0'; --Saida --Estado proximo if (x='1') then estadoproximo <= alfa; else estadoproximo <= beta; end if; --Estado beta when beta => y <= '1'; --Saida --Estado proximo if (x='1') then estadoproximo <= gamma; else estadoproximo <= beta; end if; --Estado gamma when gamma => y <= '1'; --Saida estadoproximo <= delta; --Estado proximo --Estado delta when delta => y <= '0'; estadoproximo <= alfa; end case; end process; end comportamento;