⏩Guia Rápido do VHDL

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 operadorOperadores mais comuns
Atribuição<=, := e =>
Lógicosand, or, not, nand, nor, xor e xnor
Aritméticos+, -, *, **, /, rem, mod
Concatenação&
Deslocamentossl, 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;