Vous êtes sur la page 1sur 41

Lógi

a Congurável (FPGA)
texto de suporte para aulas
Instituto Federal de Edu ação, Ciên ia e Te nologia - SP

Ri ardo Pires
20 de Dezembro de 2017

Conteúdo
1 Introdução 2

2 Exemplo de Arquitetura de um FPGA 2


2.1 CLB . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
2.2 IOB . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
2.3 RAM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
2.4 Multiplicadores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
2.5 DCM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
2.6 Rede de Interconexões . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5

3 Configuração 6

4 Software de Suporte a Projeto 6

5 Descrições de Circuitos em VHDL 7


5.1 Descrição da Interface de um Tipo de Bloco . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
5.2 Descrição Interna de um Tipo de Bloco . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
5.2.1 Exemplo de Descrição Comportamental . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
5.2.2 Exemplo de Descrição Estrutural . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
5.3 Comentários numa Descrição . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
5.4 Identificadores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
5.5 Números . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
5.6 Constantes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
5.7 Variáveis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
5.8 Tipos de Dados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
5.8.1 Tipo Inteiro . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
5.8.2 Tipo Real . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
5.8.3 Tempo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
5.8.4 Tipo Booleano . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
5.8.5 Tipo Bit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
5.8.6 Tipo Standard Logic . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
5.8.7 Criação de Tipos de Dados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
5.8.8 Subtipos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
5.9 Comandos Sequenciais . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
5.9.1 Comandos Condicionais . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
5.9.2 Comandos de Repetição . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
5.10 Dados Compostos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
5.10.1 Vetores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23

1
Ricardo Pires - Lógica Configurável (FPGA) 2

5.10.2 Records . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
5.11 Genéricos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
5.12 Biblioteca numeric_bit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
5.13 Simulação Usando-se Arquivos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
5.14 Geração de Estruturas Regulares Usando-se o Comando generate . . . . . . . . . . . . . . . . . . 34

A Uso do Compilador e Simulador GHDL 38

B Descrição de Sistema para Teste 39

1 Introdução
Field Programmable Gate Arrays (FPGAs) são dispositivos semicondutores baseados em uma matriz de
blocos lógicos configuráveis (CLBs) interconectados de forma configurável [1]. Cada bloco combinatório e as
conexões podem ser configurados após a fabricação do dispositivo, ou seja, após a venda de um destes chips,
seu comprador poderá definir, segundo suas necessidades, a função lógica particular de cada bloco e a forma
das conexões entre estes blocos. Isso distingue os FPGAs dos circuitos integrados de aplicação específica, que
são fabricados para desempenhar determinadas tarefas, para um determinado cliente.
A configuração de um FPGA requer apenas o uso de um computador, de software apropriado e de uma
interface entre o computador e o chip, para que a configuração planejada no computador seja levada eletricamente
ao chip que a implementará.
Como, para o andamento desta disciplina, estão disponíveis placas didáticas contendo chips FPGA Spartan3E-
100 da Xilinx, neste texto, supõe-se que este será o material usado nas aulas.
Supõe-se, também, que estará disponível o programa GHDL [3], que possibilita o rápido desenvolvimento
de projetos a serem implementados em FPGA e que é distribuído como software livre.
Como este é apenas um texto introdutório, recomenda-se, para aprofundamento, a consulta a livros especí-
ficos sobre FPGA e à documentação disponibilizada na Internet pelas empresas da área.

2 Exemplo de Arquitetura de um FPGA


O FPGA Spartan3E-100 da Xilinx [2] é composto por:

• blocos lógicos configuráveis, ou Configurable Logic Blocks (CLBs)

• blocos de entrada e de saída, ou Input/Output Blocks (IOBs)

• bloco de memória RAM

• blocos multiplicadores

• blocos gerenciadores de sinal de relógio, ou Digital Clock Manager (DCM) Blocks

• rede de interconexões

2.1 CLB

O CLB é o principal recurso lógico para a implementação de circuitos combinatórios e sequenciais. Cada CLB
contém quatro partes (slices). Cada slice contém duas tabelas LUT (Look-Up Tables) para implementar lógica
e dois elementos de armazenagem que podem ser usados como flip-flops ou latches. O FPGA Spartan3E-100 da
Xilinx possui 22 linhas e 16 colunas de CLBs [4].
Uma LUT é uma forma de implementação de uma tabela verdade baseada no uso de um multiplexador.
Como exemplo, supõe-se que se deseja implementar a seguinte tabela verdade, cujas entradas são A, B e C e
cuja saída é S:
Ricardo Pires - Lógica Configurável (FPGA) 3

IOBs

slice

CLB

IOBs

Figura 1: Posições de CLBs e de IOBs.

A B C S
0 0 0 1
0 0 1 0
0 1 0 1
0 1 1 1
1 0 0 1
1 0 1 0
1 1 0 1
1 1 1 0

O projeto de um circuito lógico que implementasse esta tabela poderia ser realizado usando-se um mapa de
Karnaugh, o qual possibilitaria encontrar-se uma forma minimizada de circuito usando-se uma combinação de
portas e, ou e não convenientemente interligadas para se obter a função especificada.
Uma implementação alternativa, conforme a figura 2, consiste no uso de um multiplexador, cujas entradas de
seleção seriam A, B e C da tabela e cuja saída seria a própria saída S da tabela. A última coluna da tabela seria
copiada para as entradas do multiplexador, de forma que, quando fosse estabelecida uma certa entrada (A,B,C)
da tabela nas entradas de seleção, a saída da linha correspondente fosse selecionada para ir da entrada para a
saída do multiplexador. Por exemplo, se for aplicada às entradas de seleção a combinação (A,B,C) = (1,0,1), o
valor da entrada 5 (o qual é 0) do multiplexador será copiado para sua saída, de acordo com o especificado na
tabela verdade (a saída para a linha 1 0 1 da tabela vale 0). Esta é uma LUT com 3 entradas.
Nas LUTs presentes num FPGA, os bits de entrada de dados do multiplexador são mantidos em flip-flops,
os quais formam um registrador de deslocamento, conforme a figura 3. Desta forma, a LUT pode ser ajustada
para implementar qualquer tabela verdade. Basta que se forneçam ao registrador de deslocamento, em série,
os bits da coluna de saída da tabela verdade. E a mesma LUT que funcionou por um tempo implementando
uma certa tabela verdade poderá passar a implementar uma nova tabela: basta que se preencha o registrador
de deslocamento com a coluna de saída da nova tabela, substituindo-se os bits anteriores pelos novos.
As LUTs do FPGA Spartan3E-100 da Xilinx possuem 4 entradas. Uma tal LUT, então, implementa uma
tabela verdade com 4 variáveis de entrada e 16 combinações de valores destas entradas (tabela com 16 linhas).
Como os FPGA têm seu funcionamento apoiado nas LUTs, para que um FPGA entre em operação normal,
ele deve passar por uma fase inicial, em que os registradores de deslocamento de todas as suas LUTs são
preenchidos com os bits das tabelas verdade que elas deverão implementar. Para isto, estes bits devem estar
disponíveis em uma memória não volátil, de onde serão lidos cada vez que o FPGA for ligado.
No FPGA Spartan3E-100 da Xilinx, existem dois tipos de slices: SLICEM e SLICEL. Um slice de qualquer
um destes tipos contém duas LUTs de 4 entradas cada, dois registradores e lógica adicional [4]. Um SLICEM
Ricardo Pires - Lógica Configurável (FPGA) 4

1 0
0 1
1 2
1 3 mux S
1 4 8x1
0 5
1 6
0 7

A B C

Figura 2: Exemplo de LUT (look-up table)

0
1
1
0
2
1
3 mux
1 S
4 8x1
1
5
0
6
1
7
0

entrada
série A B C

Figura 3: LUT com registrador de deslocamento à entrada

contém, além disto, dois blocos de memória RAM e dois registradores de deslocamento. A saída de cada LUT
está disponível de forma direta e pode, também, ser memorizada em um flip-flop, para uso posterior.
Conexões de LUTs a multiplexadores permitem que LUTs de um slice sejam associadas a LUTs de outros
slices, possibilitando, assim, a obtenção de funções lógicas mais complexas do que as que podem ser obtidas
com apenas uma LUT. Desta forma, podem ser implementadas tabelas verdade com mais do que 4 entradas.

2.2 IOB

Um IOB proporciona uma interface programável, unidirecional ou bidirecional, entre um pino do chip FPGA
e a lógica interna do chip.
Há três caminhos principais para dados num IOB: um caminho de saída, um caminho de entrada e um
caminho com três estados [4]. Cada caminho tem seu próprio par de elementos de armazenamento. Os três
principais caminhos de sinal são:

• O caminho de entrada traz dados de fora do chip através de um elemento de atraso programável opcional.

• O caminho de saída leva dados da lógica interna do FPGA através de um multiplexador e então através
de um driver de três estados para um pino do chip. Além deste caminho direto, o multiplexador fornece
a possibilidade de se inserir no caminho um par de elementos de armazenamento.
Ricardo Pires - Lógica Configurável (FPGA) 5

• O caminho com três estados determina quando um driver de saída ficará em alta impedância. Neste
caminho, também há a opção de inserção de um par de elementos de armazenamento.

• Todos os caminhos de entrada têm uma opção com inversão.

2.3 RAM

A memória RAM distribuída do FPGA Spartan3E-100 da Xilinx, disponível em pequenas quantidades pelos
CLBs, serve para a memorização de pequenas quantidades de dados ao longo de caminhos de processamento.
O FPGA Spartan3E-100 da Xilinx possui, também, memória RAM disponível em 4 blocos, os quais têm
maior capacidade de armazenagem de dados, de 18kbits cada um, e estão fisicamente concentrados em certas
regiões do chip.
Na inicialização do chip, cada bloco de RAM pode ser preenchido com valores iniciais adequados à aplicação.

2.4 Multipli adores

Um chip FPGA Spartan3E-100 da Xilinx possui 4 multiplicadores dedicados, ou seja, multiplicadores já im-
plementados fisicamente (e não por configuração pós-fabricação). Desta forma, qualquer que seja a configuração
do chip, os 4 multiplicadores estarão sempre disponíveis.
Cada um destes multiplicadores recebe dois operandos de 18 bits cada e gera seu produto com 36 bits,
usando aritmética em complemento de dois. Assim, a faixa de valores para cada operando vai de −131 07210 a
+131 07110 . O resultado (produto) pode estar entre −17 179 738 11210 e +17 179 869 18410 .

2.5 DCM

Um chip FPGA Spartan3E-100 da Xilinx possui 2 blocos gerenciadores de sinal de relógio (DCM). A função
de um DCM é a de controlar a frequência e a fase do sinal de relógio (clock ) no chip, diante de eventuais variações
de temperatura e de tensão de alimentação. Além disto, ele elimina o problema do atraso de propagação de
transições de relógio ao longo do chip.

2.6 Rede de Inter onexões

A rede de interconexões de um FPGA é uma rede configurável de conexões entre blocos dos tipos descritos
nas subseções anteriores, inclusive entre blocos do mesmo tipo.
A figura 4 mostra a técnica usada para se estabelecerem interconexões configuráveis. A e B são nós que
podem ou não estar interconectados, dependendo de se o transistor N-MOS entre eles estiver conduzindo ou em
corte. O transistor N-MOS conduz se tiver nível 1 em sua porta e não conduz se tiver nível 0 em sua porta.
O nível lógico da porta é determinado pela saída do flip-flop FF1. Assim, se FF1 fornecer 0, A não estará
conectado a B. Se FF1 fornecer 1, A estará conectado a B. O valor memorizado no flip-flop lhe é fornecido
quando o FPGA é configurado, ou seja, quando ele é energizado e busca sua configuração numa memória.
Seguindo-se o mesmo raciocínio, o condutor C da figura 4 estará conectado ao condutor D somente se o
flip-flop FF2 estiver fornecendo o valor 1 ao transistor N-MOS de controle daquela interconexão. No chip, estes
condutores C e D ortogonais entre si não estão, normalmente, em contato elétrico, por estarem em camadas
diferentes de condutor (um está um pouco acima do outro). Eles só são colocados em contato por meio do
transistor controlado pelo F F 2.
O FPGA possui um grande número de interconexões configuráveis como as da figura 4, entre seus blocos
e entre seus condutores internos. Assim, há uma grande liberdade de se ativarem conexões entre blocos, de
acordo com a necessidade de um determinado projeto.
Portanto, com a grande flexibilidade em se configurarem as funções lógicas dos CLBs, com a também grande
flexibilidade em se configurarem as interconexões e, ainda, com os recursos de memória RAM, multiplicadores
já implementados, recursos de entrada e de saída e de controle do sinal de relógio, um FPGA é uma forma
poderosa de se implementarem circuitos digitais complexos num único chip.
Ricardo Pires - Lógica Configurável (FPGA) 6

FF1 FF2
D
D Q D Q

Q’ Q’

B C
A

Figura 4: Interconexões configuráveis

3 Conguração
Conforme visto, um FPGA é um chip digital genérico, que, após a compra, deve ser configurado para poder
resolver um problema específico. Configurá-lo, basicamente, significa colocar bits adequados nas LUTs, para que
elas assumam as tabelas verdade desejadas, e colocar bits adequados nos flip-flops de controle das interconexões.
O FPGA passa por uma fase de configuração cada vez que entra em funcionamento, ou seja, quando é ligado,
ele passa um tempo tendo seus flip-flops de configuração sendo preenchidos com os bits adequados à aplicação
que implementará. Quando for desligado, ele perderá estes bits. Ao ser ligado novamente, este processo deverá
se repetir. Para que isto seja possível, os dados de configuração devem estar disponíveis em uma memória não
volátil, acessível ao FPGA. Ao ser ligado, o FPGA buscará seus dados de configuração nesta memória.
Um FPGA que já foi configurado para uma certa aplicação poderá ser configurado, em outro momento, para
outra aplicação. Apesar de haver um lento fenômeno de envelhecimento, um FPGA desligado é como um FPGA
novo, recém comprado. Não importa que ele já tenha sido configurado algumas vezes. Como um FPGA novo,
um FPGA usado poderá funcionar normalmente assumindo uma configuração nova, para uma nova aplicação.
O FPGA Spartan3E-100 da Xilinx tem pinos dedicados ao recebimento de dados de configuração e pinos
que são usados para configuração mas que, após esta fase, assumem outras funções.
O FPGA Spartan3E-100 da Xilinx tem 581 344 bits de configuração, ou seja, este é o número de flip-flops
que devem ter seus valores definidos para que o chip entre em operação.
Como a determinação do valor que deve ser colocado em cada um destes 581 344 flip-flops para que o chip
assuma o comportamento desejado é uma tarefa muito complexa, ela é realizada por software disponibilizado
pela Xilinx, conforme seção a seguir.

4 Software de Suporte a Projeto


O projetista de um sistema digital que desejar implementar seu projeto em FPGA deverá fazer com que
todas as LUTs e todas as interconexões do FPGA assumam as configurações adequadas a este projeto. Para
que o projetista não precise conhecer em detalhe o esquema interno do FPGA e para que a utilização do FPGA
como solução para a implementação seja facilitada, a Xilinx disponibiliza o software Vivado [2]. Ele proporciona
recursos para a descrição e a simulação do sistema, para a sua implementação, para a escolha do modelo de
chip mais apropriado ao projeto em questão e para a configuração do chip.
Na interação com o Vivado, um aspecto crítico é o da correta descrição do circuito a ser projetado. Normal-
mente, esta descrição é feita usando-se uma linguagem especial para a descrição de circuitos digitais. Destas, o
Vivado reconhece a linguagem VHDL e a linguagem Verilog. As duas têm a mesma finalidade. Pode-se dizer
que são “concorrentes” entre si.
Neste texto, escolhe-se arbitrariamente a linguagem VHDL para a descrição de circuitos para o Vivado. A
seção a seguir é dedicada a ela.
O Vivado é necessário no projeto voltado aos chips da Xilinx, por ser capaz de gerar os bits de configuração
para aqueles chips. Mas, ele apresenta alguns inconvenientes para seu uso didático: ocupa vários gigabytes
quando instalado, possui interface com o usuário muito complexa e o ciclo de elaboração de descrição em VHDL
seguido por simulação envolve muitas etapas e a definição de muitos parâmetros. Por isto, nesta disciplina,
prevê-se, nos primeiros meses, o uso do GHDL [3], o qual é software livre, ocupa pouca memória quando
Ricardo Pires - Lógica Configurável (FPGA) 7

instalado (pode ser usado em live CD) e possibilita um ciclo de descrição em VHDL e simulação mais rápido do
que o do Vivado. Porém, um projeto desenvolvido usando-se o GHDL, para ser implementado em um FPGA da
Xilinx, tem de ser, numa última etapa, fornecido ao Vivado, para a geração dos bits de configuração. Instruções
para o uso do GHDL são dadas no apêndice A.

5 Des rições de Cir uitos em VHDL


Para que um projetista possa aproveitar todo o potencial de um programa como o Vivado, ele deve, prefe-
rencialmente, ter um razoável domínio de uma linguagem de descrição de circuitos digitais. É pelo uso de uma
tal linguagem que o projetista “explica” ao Vivado como é o circuito que deverá ser implementado no FPGA.
Uma linguagem de descrição digital é uma linguagem que possui regras de sintaxe e de semântica e segundo a
qual é criado um texto que descreve, sem ambiguidade, o circuito que se deseja obter. Dentre estas linguagens,
a VHDL [5] [6] é uma das mais usadas. Ela é padronizada pelo IEEE [7], a associação mundial dos engenheiros
elétricos.
A VHDL supõe que o circuito a ser descrito possua vários componentes, ou, mais genericamente, vários
blocos. Estes blocos são, então, interconectados, para que se obtenha o circuito com o comportamento desejado.
Um bloco pode ser desde uma simples porta lógica até, por exemplo, um microprocessador.
Para que um certo tipo de bloco lógico possa ser usado num projeto em VHDL, deve-se descrever previamente
este tipo de bloco em VHDL. Esta descrição possui duas partes: a interface, a qual especifica as entradas e
saídas do bloco, e a arquitetura, a qual descreve o interior do bloco.

5.1 Des rição da Interfa e de um Tipo de Blo o


Eis um exemplo de descrição da interface de um bloco, apresentada em [5]:

entity reg4 is
port( d0, d1, d2, d3, en, clk: in bit;
q0, q1, q2, q3: out bit );
end entity reg4;

Nela, as palavras: entity, is, port, in, out, bit e end são palavras-chave de VHDL. entity anuncia que se está
criando um novo tipo de bloco. O nome que se escolheu livremente para este tipo de bloco foi reg4. port anuncia
que, a seguir, virá a especificação dos terminais deste tipo de bloco. Aqui, a lista de nomes indo de d0 até clk
contém nomes escolhidos para os seis terminais de entrada. Os nomes dos terminais de saída estão na lista que
vai de q0 até q3. A palavra-chave in indica entrada e a palavra-chave out indica saída. A palavra-chave bit
indica que os terminais são do tipo bit, ou seja, o valor em cada terminal pode ser apenas 0 ou 1.
O bloco deste exemplo é um registrador que memoriza 4 bits. Suas entradas, que recebem os 4 bits a
memorizar, têm seus nomes começando em d. O terminal de entrada en é o terminal de habilita (enable)
do registrador. O terminal clk é o que recebe o sinal de relógio (clock ). Os terminais de saída, com nomes
começando em q, são aqueles em que os 4 bits memorizados podem ser lidos por algum outro bloco.
Uma vez descrita a interface de um tipo de bloco, blocos deste tipo poderão fazer parte de blocos maiores.
Isto será visto adiante.

5.2 Des rição Interna de um Tipo de Blo o


Para que um tipo de bloco esteja totalmente descrito em VHDL, além de se descrever sua interface, deve-se
fazer sua descrição interna, chamada de architecture (arquitetura). Cada tipo de bloco deve ter uma única
descrição de interface. Mas, pode ter várias descrições de arquitetura alternativas. Quando um bloco de um
certo tipo for usado como componente em um projeto, escolhe-se qual das arquiteturas dele será usada, se
houver mais de uma disponíveis.
Uma arquitetura pode ser comportamental, estrutural ou mista. A comportamental estabelece o funci-
onamento do tipo de bloco, ou seja, como ele calcula valores de saída a partir de valores de entrada. A estrutural
descreve internamente o tipo de bloco como sendo formado por componentes de outros tipos de bloco. Neste
Ricardo Pires - Lógica Configurável (FPGA) 8

caso, o funcionamento do tipo de bloco sendo descrito dependerá do funcionamento de seus componentes. Fi-
nalmente, uma descrição mista, em parte, define o comportamento do bloco e, em parte, usa componentes mais
básicos, de tipos previamente descritos.

5.2.1 Exemplo de Descrição Comportamental


Na descrição comportamental, o comportamento do bloco é descrito por meio de processos. Um processo é
como um programa de computador. Contém uma série de comandos, os quais, normalmente, definem a relação
entre os valores de entrada e os de saída do bloco. Um processo pode conter, também, variáveis, as quais são
usadas, por exemplo, para memorizar resultados intermediários de cálculos.
Um bloco pode ter vários processos. Considera-se que eles são executados simultaneamente, mesmo que
escritos um após o outro. A ordem em que aparecem numa descrição não importa. Também são simultâneos os
processos que estejam em blocos diferentes. Isto reflete o fato de que um sistema digital físico em funcionamento
tem muitas partes funcionando simultaneamente. O resultado da simulação de um sistema descrito em VHDL
corresponde ao que ocorre fisicamente, levando em conta esta simultaneidade.
Numa descrição VHDL, além dos blocos e dos processos, podem estar presentes os sinais. Um sinal pode
ser visto como um fio, tendo o papel de interconectar terminais de blocos. Um processo pode atribuir valores a
variáveis e a sinais. Porém, a atribuição de um valor a um sinal tem a característica especial de ser agendada
para ser efetivada em um certo momento futuro, calculado de acordo com o comportamento físico do sistema
digital sendo projetado.
Para ilustrar estes fatos, aqui está uma descrição de arquitetura para o tipo de bloco reg4 cuja interface foi
descrita na subseção anterior [5]:

architecture comportamento of reg4 is


begin
memoriza: process is
variable mem_d0, mem_d1, mem_d2, mem_d3: bit;
begin
if en = ’1’ and clk = ’1’ then
mem_d0 := d0;
mem_d1 := d1;
mem_d2 := d2;
mem_d3 := d3;
end if ;
q0 <= mem_d0 after 5 ns;
q1 <= mem_d1 after 5 ns;
q2 <= mem_d2 after 5 ns;
q3 <= mem_d3 after 5 ns;
wait on d0, d1, d2, d3, en, clk;
end process memoriza;
end architecture comportamento;

A seguir, comenta-se esta descrição trecho a trecho.


Em:

architecture comportamento of reg4 is

anuncia-se que se está iniciando a descrição da arquitetura do tipo de bloco reg4. Esta descrição de arquitetura
recebeu arbitrariamente o nome de comportamento. Se forem criadas outras arquiteturas para o reg4, cada uma
delas deverá receber um nome diferente. of e is são palavras-chave.
Em:
Ricardo Pires - Lógica Configurável (FPGA) 9

begin
memoriza: process is
variable mem_d0, mem_d1, mem_d2, mem_d3: bit;

begin indica início de descrição de arquitetura. memoriza é o nome escolhido arbitrariamente para o processo
que vem a seguir. process is são palavras-chave que indicam início de descrição de processo. Neste caso, a
palavra-chave is é opcional. variable indica que, a seguir, vem uma lista de variáveis usadas neste processo.
Elas receberam os nomes arbitrários de mem_d0 a mem_d3. A palavra-chave bit indica que estas variáveis são
para valores do tipo bit.
Em:

begin
if en = ’1’ and clk = ’1’ then
mem_d0 := d0;
mem_d1 := d1;
mem_d2 := d2;
mem_d3 := d3;
end if ;

begin indica início de descrição de processo. O trecho compreendido entre if e end if;, chamado de condicional,
só é executado quando a condição en = ’1’ and clk = ’1’ dada para ele for verdadeira, ou seja, somente quando
o valor de en e o valor de clk forem 1 simultaneamente. then significa então e está sempre associada à palavra
if.
Na linha:

mem_d0 := d0;

o valor da entrada d0 é copiado para a variável mem_d0. O mesmo tipo de ação ocorre nas linhas similares
seguintes.
Assim, este processo faz com que, se o sinal de relógio (clk ) e o sinal de habilitar (en) estiverem ativos, os
valores dos 4 bits de entrada do registrador sejam copiados para as variáveis internas, ou seja, sejam copiados
para dentro do registrador.
O trecho:

q0 <= mem_d0 after 5 ns;


q1 <= mem_d1 after 5 ns;
q2 <= mem_d2 after 5 ns;
q3 <= mem_d3 after 5 ns;

indica que os valores das variáveis internas serão copiados para as saídas do registrador. Esta cópia ocorre
com um atraso de 5ns em relação à variação de qualquer entrada do registrador. O símbolo <= indica que a
atribuição de valor é feita a um sinal, ou seja, que ela é feita levando-se em conta temporização.
Portanto, os valores memorizados no registrador vão para suas saídas sempre 5ns após a variação do valor
de alguma entrada deste bloco.
A linha:

wait on d0, d1, d2, d3, en, clk;

indica que, após ser executado, o processo fica inativo, esperando (wait) por uma mudança no valor de algum
dos terminais indicados na lista após a palavra-chave on, ou seja, ele fica à espera de uma mudança de valor
Ricardo Pires - Lógica Configurável (FPGA) 10

em qualquer entrada do registrador. Quando uma mudança destas ocorrer, o processo é executado novamente,
desde seu início até a palavra wait. Então, ele entra em nova fase de espera e este ciclo se repete.
Finalmente, as linhas:

end process memoriza;


end architecture comportamento;

indicam o final do processo e o final da descrição de arquitetura.

5.2.2 Exemplo de Descrição Estrutural


Conforme comentado anteriormente, uma descrição estrutural de um bloco descreve seu interior como sendo
composto por outros blocos. Assim, neste caso, ao invés de se explicar seu funcionamento, descreve-se sua
estrutura interna.
Como exemplo, suponha que se deseje descrever um circuito que implemente a função ou-exclusivo usando
portas e, ou e não, conforme a figura 5.
nao1 e1
na
a nab

b
s

a ous

b anb
nb e2
nao2

Figura 5: Circuito ou-exclusivo, para descrição estrutural

Suponha, também, que estejam disponíveis, como parte deste projeto, as seguintes descrições das portas
lógicas que serão usadas na figura 5:
Arquivo porta_nao.vhdl:

entity porta_nao is
port (
entrada : in bit;
saida : out bit);
end porta_nao;

architecture basica of porta_nao is


begin
calcula_saida: process (entrada)
begin
saida <= not entrada;
end process calcula_saida;
end basica;

Nesta descrição da porta não, no trecho: process (entrada), a palavra entrada entre parênteses indica que o
processo deverá ser ativado sempre que houver mudança no valor do terminal entrada. Em geral, no início da
descrição de um processo, após a palavra-chave process, pode-se incluir uma lista de nomes de terminais e de
sinais entre parênteses, chamada lista de sensibilidade, indicando que o processo é sensível a estes terminais e
sinais, ou seja, que ele deverá ser ativado sempre que houver alteração no valor de qualquer terminal ou sinal
da lista.
Arquivo porta_ou.vhdl:
Ricardo Pires - Lógica Configurável (FPGA) 11

entity porta_ou is
port (
entrada_1, entrada_2 : in bit;
saida : out bit);
end porta_ou;

architecture basica of porta_ou is


begin
calcula_saida: process (entrada_1, entrada_2)
begin
saida <= entrada_1 or entrada_2;
end process calcula_saida;
end basica;

Arquivo porta_e.vhdl:

entity porta_e is
port (
entrada_1, entrada_2 : in bit;
saida : out bit);
end porta_e;

architecture basica of porta_e is


begin
calcula_saida: process (entrada_1, entrada_2)
begin
saida <= entrada_1 and entrada_2;
end process calcula_saida;
end basica;

A descrição da arquitetura daquele circuito ou-exclusivo poderia, então, ser feita de forma estrutural, como
a seguir:
Ricardo Pires - Lógica Configurável (FPGA) 12

entity ou_exclusivo is
port (
a : in bit;
b : in bit;
s : out bit);
end ou_exclusivo;

architecture estrutura of ou_exclusivo is


signal na, nab, nb, anb: bit;
begin
nao1: entity work.porta_nao( basica )
port map (
entrada => a,
saida => na);
nao2: entity work.porta_nao( basica )
port map (
entrada => b,
saida => nb);
e1: entity work.porta_e( basica )
port map (
entrada_1 => na,
entrada_2 => b,
saida => nab);
e2: entity work.porta_e( basica )
port map (
entrada_1 => a,
entrada_2 => nb,
saida => anb);
ous: entity work.porta_ou( basica )
port map (
entrada_1 => nab,
entrada_2 => anb,
saida => s);
end architecture estrutura;

Nesta descrição, architecture, of e is são palavras-chave. estrutura e ou_exclusivo são palavras escolhidas
arbitrariamente. ou_exclusivo foi o nome escolhido para este tipo de bloco.
Na linha:

signal na, nab, nb, anb: bit;

a palavra-chave signal anuncia que, a seguir, virá uma lista de nomes de sinais (fios que interconectam blocos).
Os nomes arbitrários destes sinais são de na até anb. Ao fim da linha, diz-se que eles são do tipo bit.
Então, após a palavra-chave begin, são especificados os quatro blocos internos do bloco ou-exclusivo. Um
deles, por exemplo, é:

e1: entity work.porta_e( basica )


port map (
entrada_1 => na,
entrada_2 => b,
saida => nab);
Ricardo Pires - Lógica Configurável (FPGA) 13

Neste trecho, anuncia-se que será usado um bloco chamado e1 (nome arbitrário). A palavra-chave entity indica
que e1 é um bloco interno desta arquitetura. porta_e indica que se trata de um bloco do tipo porta_e, cuja
descrição em VHDL deve estar disponível. work indica que a descrição da porta_e está disponível como parte
deste mesmo projeto. (Alternativamente, este bloco poderia ser de uso mais geral, estando armazenado em
alguma outra pasta no computador, para uso em vários projetos.) basica indica que, das possíveis arquiteturas
disponíveis para a porta_e, deve ser escolhida uma chamada basica. (Lembre-se de que um tipo de bloco pode
ter várias descrições de arquitetura, o que não ocorreu neste exemplo.)
port map introduz a forma como terminais da porta_e serão conectados aos sinais e aos terminais deste
circuito ou-exclusivo. Assim, o sinal na será conectado ao terminal entrada_1 da porta_e, o terminal b da
ou-exclusivo será conectado ao terminal entrada_2 da porta_e e o sinal nab será conectado ao terminal saida
da porta_e.

Exercício 1
Descreva a interface e a arquitetura estrutural do circuito da figura 6, supondo disponíveis as portas do exemplo
anterior.

m i
b r
k j
c

d n p
e

f s

Figura 6: Circuito para exercício de descrição estrutural

5.3 Comentários numa Des rição


VHDL permite que o projetista coloque comentários em uma descrição. Um comentário é uma anotação livre
feita pelo projetista, com o objetivo de explicar algum trecho escrito em VHDL, para que o próprio projetista,
mais tarde, lembre-se de porque o escreveu daquela maneira ou para fazer este esclarecimento a outros projetistas
que estejam autorizados a ler ou a modificar esta descrição.
Um comentário é introduzido usando-se dois traços seguidos. Por exemplo, na descrição anterior do com-
portamento do reg4, poderia ter sido escrita a linha:

mem_d1 := d1; -- O dado de entrada é memorizado.

Aqui, o que está após os dois traços é uma frase escrita pelo projetista, para esclarecer o significado do que está
no início da linha. Esta frase fica guardada no texto da descrição, mas é ignorada por programas simuladores e
por programas que implementam o circuito em FPGA.
Ricardo Pires - Lógica Configurável (FPGA) 14

É considerado comentário tudo o que estiver após os dois traços, até o final da linha. É permitido que uma
linha sirva apenas para comentário, como:

-- Este circuito foi projetado por Fulano de Tal, em 2009.

Esta linha, que é puramente comentário, é ignorada por programas que leiam VHDL para simulação ou para
implementação. Pode haver várias linhas seguidas como esta.
Se se desejar escrever um comentário longo, de várias linhas, cada linha dele deverá começar com dois traços.

5.4 Identi adores


Identificadores são aquelas palavras arbitrárias usadas para se darem nomes a blocos, a sinais, a variáveis, a
componentes etc. Nos exemplos anteriores, como exemplos de identificadores, viram-se: reg4, nao2, q3 etc.
É conveniente que se escolham nomes que tenham relação com os elementos nomeados. Assim, é melhor se
chamar um registrador de reg1 do que de x, embora estes dois nomes sejam igualmente permitidos.
Em VHDL, identificadores podem ser formados por letras, algarismos e pelo caractere de sublinhar (_). Ele
deve começar por letra. Não pode terminar com caractere de sublinhar, nem pode ter caracteres de sublinhar
em seguida (__).
Não há distinção entre letras maiúsculas e minúsculas.
Não pode haver espaço dentro de um identificador.
Nenhuma palavra-chave de VHDL pode ser usada como identificador. Por exemplo, um sinal não pode se
chamar signal, nem if, nem entity.

5.5 Números
Em VHDL, podem ser escritos números inteiros e números reais com parte fracionária. O caractere que
separa a parte inteira da fracionária num número real é o ponto (e não a vírgula). Um número pode ser escrito
como:

4e5

o que significa 4 × 105 . Um erro comum é se escrever uma potência de 10, como, por exemplo, 104 , como 10e4,
o qual significa, na realidade, 10 × 104 .
Podem-se escrever números em bases que não sejam a decimal, como a binária e a hexadecimal, na forma
dada pelo exemplo:

2#10101111#

em que é escrito o número binário 101011112 e pelo exemplo:

16#1A3F#

em que é escrito o número hexadecimal 1A3F16 .


Para se facilitar a leitura de números longos, podem-se colocar caracteres de sublinhar dentro deles, como
em:

2#1010_1111#

em que o número é 101011112 , mas está com seus bits agrupados de quatro em quatro somente para facilitação
de leitura.
Ricardo Pires - Lógica Configurável (FPGA) 15

5.6 Constantes
Uma constante é um elemento da descrição que guarda permanentemente um certo valor.
Antes de ser usada, uma constante deve ser declarada. Um exemplo de uma tal declaração é:

constant numero_de_bits : integer := 8;

Aqui, foi declarada uma constante chamada arbitrariamente numero_de_bits, cujo valor, inteiro, será sempre
8. constant e integer são palavras-chave.
Uma utilidade de se usar uma constante numa descrição é a de torná-la mais compreensível. Neste exemplo,
se, mais tarde, durante o projeto, o projetista quiser escrever, em um certo local, o número 8 porque uma certa
operação será realizada sobre 8 bits, será melhor, ao invés de se escrever 8, escrever-se numero_de_bits. Se
aquele projetista escrevesse 8, um colega seu de equipe, ao ler a descrição, poderia não entender o motivo de
aquele 8 estar lá, enquanto que, se fosse escrito numero_de_bits, o colega entenderia rapidamente de que se
trata.
A definição de constante dada neste exemplo pode, também, servir para se mostrar outra utilidade deste
recurso. Suponha que um grande projeto tivesse sido feito em VHDL e que todo ele usasse dados de 8 bits. Sem
esta declaração de constante, no projeto, deveria haver muitos 8 escritos por toda a parte. Se fosse necessário
alterar este projeto, para que, em lugar de 8 bits, ele passasse a usar dados de 16 bits, muitos daqueles 8 teriam
de ser substituídos por 16. Além do trabalho exigido para se fazer esta substituição, haveria o risco de ocorrerem
erros, porque algum 8 presente no projeto poderia significar algo diferente de 8 bits e, se ele fosse trocado, um
erro seria introduzido no projeto. Mas, sendo usada a constante, para se modificar o projeto de 8 para 16 bits,
bastaria que se alterasse a declaração anterior para:

constant numero_de_bits : integer := 16;

Desta forma, automaticamente, em todos os lugares da descrição em que o identificador numero_de_bits tivesse
sido usado, ele assumiria o novo valor 16, sem a necessidade de se fazerem outras modificações na descrição
além desta modificação na declaração da constante.

5.7 Variáveis
Em VHDL, uma variável serve para a memorização de um valor, dentro de um processo. Tem uso semelhante
ao das variáveis das linguagens de programação.
Antes de ser usada, uma variável deve ser declarada. Esta declaração deve ser feita após a declaração do
processo e antes da palavra-chave begin do funcionamento do processo. Um exemplo de declaração de variável
é:

variable contagem: integer := 0;

Aqui, foi declarada uma variável chamada contagem, de tipo inteiro, cujo valor inicial é 0. Não é obrigatória a
especificação do valor inicial.
Quando uma variável for declarada como parte de um processo, ela não é acessível por outros processos.
Assim, um processo pode possuir uma variável com nome igual ao de uma variável de outro processo. Neste
caso, apesar de terem o mesmo nome, elas são consideradas variáveis diferentes. Colocar um valor numa delas
não afeta a outra.
Viu-se um exemplo de uso de variáveis na seção 5.2.1, na descrição comportamental do bloco reg4.
A atribuição de um novo valor a uma variável é feita usando-se o operador :=. Exemplo:

contagem := 7;

Numa simulação, a atribuição de um valor a uma variável é realizada assim que esta atribuição for lida na
descrição. Isto é diferente do que ocorre com a atribuição de um valor a um sinal, a qual é agendada para um
Ricardo Pires - Lógica Configurável (FPGA) 16

momento futuro apropriado. Por isto, a atribuição de valor a um sinal usa um operador diferente, o <=.
Como mais um exemplo de uso de variáveis, aqui está uma descrição comportamental do circuito ou-exclusivo
da figura 5:

entity ou_exclusivo is
port (
a : in bit;
b : in bit;
s : out bit);
end ou_exclusivo;

architecture comportamento of ou_exclusivo is


begin comportamento
calcula: process (a,b) is
variable na, nab, nb, anb : bit;
begin process calcula
na := not a;
nab := na and b;
nb := not b;
anb := a and nb;
s <= nab or anb;
end process calcula;
end comportamento;

5.8 Tipos de Dados


Viu-se que cada variável e cada sinal recebem dados de um certo tipo. Quando um elemento é declarado
para receber dados de um certo tipo, ele não poderá receber dados de outro tipo.

5.8.1 Tipo Inteiro


Representa números inteiros, ou seja, que não têm parte fracionária. O tipo integer corresponde a todos os
números inteiros representáveis no computador.
Um inteiro pode ser negativo.
Com inteiros, podem ser usadas as operações da tabela 1.

símbolo operação
+ adição
- subtração
* multiplicação
/ divisão
rem resto de divisão entre inteiros
abs valor absoluto (módulo)
** exponenciação

Tabela 1: Operações com Inteiros

A estas operações, aplicam-se as regras de precedência usadas em linguagens de programação. Por exemplo,
numa expressão, a operação de multiplicação precede a de adição. Para que operações sejam executadas em
outra ordem, podem-se usar parênteses.
Por exemplo, se forem declaradas as variáveis:

variable a, b, c, d, f: integer;
Ricardo Pires - Lógica Configurável (FPGA) 17

elas poderão ser usadas em operações como:

a := b + c ; -- a recebe a soma de b com c.


d := f ** 2 ; -- d recebe f ao quadrado.
c := a * (2 + d); -- parênteses forçam adição antes

Exercício 2
Descreva em VHDL um sistema que possua duas entradas, do tipo inteiro, e1 e e2, e uma saída, saida, também
do tipo inteiro. Ele deve fornecer à saída, a cada instante, o resultado de e1 + 2 × e2.

5.8.2 Tipo Real


É usado para representar números reais, os quais têm parte inteira e parte fracionária (a qual pode valer
zero).
Exemplo de declaração de variável real:

variable x: real;

As operações permitidas sobre variáveis reais são dadas na tabela 2.

símbolo operação
+ adição
- subtração
* multiplicação
/ divisão
abs valor absoluto (módulo)
** exponenciação

Tabela 2: Operações com Reais

Como os valores reais englobam os inteiros, poderia surgir a dúvida: Por que não se usarem sempre variáveis
reais, já que estas são mais gerais? Ocorre que algumas variáveis, de fato, só devem receber valores inteiros. É o
caso, por exemplo, de uma variável que deve fazer uma contagem de 1 a 10, de um em um. Declará-la como real
poderia implicar, durante o projeto, que ela deveria ser memorizada por um registrador capaz de memorizar sua
parte fracionária, o que seria um desperdício, já que esta será sempre igual a zero. Outro problema é que, por
erro de projeto, em algum momento, poderia se tentar atribuir a esta variável um valor não inteiro, como 1,5.
Se ela for declarada como inteira, o simulador detectará este erro. Se ela for declarada como real, o simulador
aceitará que ela receba este valor imprevisto e o erro não será detectado tão facilmente.

5.8.3 Tempo
Em VHDL, a grandeza tempo é pré-definida (com o nome time), por ser muito utilizada na descrição de
sistemas digitais, especialmente na especificação de atrasos de propagação de sinais.
Em uma descrição, um intervalo de tempo pode ser especificado em fs (femtossegundos), ps (picossegundos),
ns (nanossegundos), us (microssegundos), ms (milissegundos), sec (segundos), min (minutos) ou hr (horas).
Neste texto, já foi visto o uso de intervalo de tempo em linhas como:

q0 <= mem_d0 after 5 ns;


Ricardo Pires - Lógica Configurável (FPGA) 18

5.8.4 Tipo Booleano


Possui apenas dois valores possíveis: true (verdadeiro) e false (falso).
No comando if do exemplo:

if en = ’1’ and clk = ’1’ then


mem_d0 := d0;
...
end if ;

a condição en = ’1’ and clk = ’1’ fornece um resultado booleano true ou false. Os comandos compreendidos
pelo bloco if somente serão executados se o resultado da condição for true.
As operações lógicas disponíveis em VHDL aplicáveis a valores booleanos são: and (e), or (ou), nand (não
e), nor (não ou), xor (ou exclusivo), xnor (não ou exclusivo) e not (não).

5.8.5 Tipo Bit


Pode ter apenas os valores ’0’ e ’1’, sempre escritos, como aqui, entre apóstrofos.
Exemplo de uso:

port( d0, d1, d2, d3, en, clk: in bit;


q0, q1, q2, q3: out bit );

As operações lógicas aplicáveis a booleanos também se aplicam a bits, com o ’1’ significando true e o ’0’
significando false. O resultado de uma operação lógica sobre bits é um bit.

5.8.6 Tipo Standard Logic


É definido num pacote chamado std_logic_1164. (Um pacote é um conjunto de definições e de blocos
escritos em VHDL, disponíveis para uso em qualquer projeto.)
A palavra-chave usada para se declararem variáveis deste tipo é std_logic.
Um elemento do tipo std_logic pode assumir, dentre outros, os valores: ’0’, ’1’, ’U’ (bit não inicializado),
’X’ (indefinido), ’Z’ (alta impedância) e ’-’ (“não importa”).
Os valores ’0’ e ’1’ se comportam como no tipo bit.
O valor ’U’ pode ser o valor encontrado, por exemplo, na saída de um flip-flop no início da simulação de um
circuito digital (antes de este flip-flop ter recebido algum valor definido).
O valor ’X’ pode ser o resultado, por exemplo, encontrado num fio ao qual uma porta lógica tenta aplicar
’1’, enquanto outra tenta aplicar ’0’.
O valor ’Z’ corresponde ao estado “desligado”, ou seja, o estado de um fio que não esteja conectado nem a
’0’, nem a ’1’.
O valor ’-’ tem o significado de “não importa” usado em mapas de Karnaugh.
Assim, o tipo standard logic engloba o tipo bit. O tipo bit deve ser usado em casos nos quais só se esperam
valores ’0’ e ’1’ em um elemento, casos estes em que, se surgir um outro valor, um erro deverá ser indicado pelo
simulador VHDL. O tipo standard logic deve ser usado quando aqueles valores diferentes de ’0’ e de ’1’ também
forem esperados ou aceitos.
Como o tipo standard logic não faz parte da VHDL, mas está definido no pacote std_logic_1164, para usá-lo
em uma descrição, deve-se, previamente, dentro dela, inserir as linhas:

library ieee;
use ieee.std_logic_1164.all;

as quais trazem definições daquele pacote para esta descrição.


Ricardo Pires - Lógica Configurável (FPGA) 19

5.8.7 Criação de Tipos de Dados


Em VHDL, é possível a criação de novos tipos a partir daqueles existentes. Exemplo:

type segundos is range 0 to 59;

Aqui, foi criado um novo tipo, chamado segundos, cujas variáveis só poderão receber valores entre 0 e 59. Se
for feita uma tentativa de lhes atribuir um valor fora desta faixa, o simulador VHDL indicará um erro.
Após a criação deste tipo, poderão ser criadas variáveis deste tipo, como em:

variable instante_inicial, instante_final: segundos;

Estas duas variáveis, então, só poderão receber valores que estejam entre 0 e 59, inclusive. Elas poderiam ter
sido declaradas como sendo do tipo inteiro. Mas, se o fossem, não seria detectado um erro quando uma delas
recebesse, por exemplo, o valor 61.

5.8.8 Subtipos
Um subtipo é um novo tipo criado pelo projetista, como um subconjunto de um tipo já existente. Exemplo:

subtype atraso is time range 0 us to 10 us;

Aqui, atraso foi definido como um tipo que só pode assumir valores entre 0us (ou seja, zero) e 10µs.

5.9 Comandos Sequen iais


Em VHDL, comandos sequenciais são usados para manipular dados dentro de processos. Eles são executados
pelo simulador na ordem em que são encontrados na descrição VHDL.
O comando sequencial mais simples é o de atribuição de valor a variável. Tendo sido declaradas duas
variáveis, a e b, o comando:

a := b;

é um comando sequencial, o qual, assim que for lido pelo simulador, será executado.
Dentre os comandos sequenciais disponíveis, destacam-se, pela sua utilidade, os comandos condicionais
e os comandos de repetição, descritos nas seções a seguir.

5.9.1 Comandos Condicionais


Numa descrição VHDL, um comando condicional é um ponto de onde partem várias sequências alternativas
de execução. A escolha da sequência que será seguida dependerá do resultado da avaliação da condição.
O comando condicional mais usado é o comando if. Sua forma geral é:

if condição_1 then
grupo de comandos 1
elsif condição_2 then
grupo de comandos 2
elsif condição_3 then
grupo de comandos 3
... -- Pode haver outros elsif.
else
grupo de comandos do else
end if ;
Ricardo Pires - Lógica Configurável (FPGA) 20

A palavra-chave if significa “se”. A palavra-chave then significa “então”. Assim, o início da estrutura anterior
pode ser lido como: “se a condição_1 for verdadeira, então deve ser executado o grupo de comandos 1”.
Em inglês, a palavra else significa “senão”. Em VHDL, a palavra-chave elsif é uma contração de else com
if. Portanto, elsif significa “senão, se”. Assim, na descrição anterior, o trecho:

elsif condição_2 then


grupo de comandos 2

diz que, se a condição anterior resultar falsa, mas a condição_2 for verdadeira, deverá ser executado o grupo
de comandos 2. Desta forma, cada elsif só será avaliado se as condições anteriores a ele resultarem falsas. Se
a condição dele for verdadeira, os comandos que estiverem logo a seguir serão executados. Senão, prossegue-se
para o próximo elsif.
No comando condicional, se todas as condições avaliadas resultarem falsas e for encontrada a palavra-chave
else, os comandos do grupo deste else serão executados. O else é um “senão” final, sem novas condições. Sob
ele, fica um grupo de comandos que deverá ser executado se todas as condições anteriores falharem.
No comando condicional, deve sempre haver o if e uma condição inicial. Pode não haver blocos introduzidos
por elsif e pode não haver o bloco introduzido por else. Pode haver qualquer número de elsif. Sem o else, nada
será feito se todas as condições anteriores resultarem falsas.
Eis um exemplo de uso do condicional, na sua versão mais simples:

if habilita = ’1’ then


valor_memorizado := dado_de_entrada;
end if ;

Este trecho poderia descrever o funcionamento de um elemento de memória. Neste caso, se um certo sinal de
habilita tiver o valor 1, um certo dado de entrada será memorizado. Senão, nada será feito, o valor memorizado
não será alterado.
O exemplo a seguir descreve o funcionamento de um multiplexador com duas entradas:

if selecao = 0 then
saida <= entrada_0; -- Executado se a condição for verdadeira.
else
saida <= entrada_1; -- Executado se a condição for falsa.
end if ;

Normalmente, as condições consistem em comparações. Em VHDL, comparações são feitas usando-se a


sintaxe dada na tabela 3.

operador significado
= igual a
/= diferente de
< menor do que
<= menor ou igual
> maior do que
>= maior ou igual

Tabela 3: Operadores para comparações

Deve-se notar que a comparação de igualdade usa um simples sinal de igual, diferentemente do comando de
atribuição, o qual usa := ou <=, conforme visto. E a comparação menor ou igual usa um operador igual ao
Ricardo Pires - Lógica Configurável (FPGA) 21

de atribuição a sinal. Mas, numa descrição VHDL, pelo contexto, não há dúvida quanto ao significado deste
operador, quando ele for usado.

Exercício 3
Descreva em VHDL um multiplexador que possua quatro entradas de dados, a, b, c e d, todas do tipo inteiro,
e dois bits de entrada para seleção, sel_1 e sel_2. A saída s é definida pela tabela 4.

sel_1 sel_2 s
0 0 a
0 1 b
1 0 c
1 1 d

Tabela 4: Tabela para exercício 3.

5.9.2 Comandos de Repetição


Um comando de repetição faz com que um certo trecho de descrição VHDL seja executado repetidamente.
Os tipos básicos de comandos de repetição em VHDL são: loop, while e for.
A sintaxe do comando loop é:

loop
grupo de comandos executados repetidamente
end loop;

Os comandos colocados entre as palavras-chave loop e end loop; serão executados repetidamente e indefinida-
mente, ou seja, a execução deste bloco repetitivo nunca terminará. Isto corresponde a um sistema digital que
realiza sempre uma certa tarefa repetitiva enquanto estiver ligado.
A seguir, é dado um exemplo de um contador, usando o comando loop.

entity contador is
port (
ck : in bit;
saida : out integer);
end contador;

architecture comportamento of contador is


begin
incrementador: process is
variable valor_da_contagem : integer := 0;
begin
saida <= valor_da_contagem;
loop
wait until ck = ’1’;
valor_da_contagem := ( valor_da_contagem + 1 ) rem 16;
saida <= valor_da_contagem;
end loop;
end process incrementador;
end comportamento;

Este contador inicia sua contagem com o valor 0. A cada subida do sinal ck, o valor da contagem é incrementado.
Ricardo Pires - Lógica Configurável (FPGA) 22

A operação rem 16 obtém o resto da divisão do valor da contagem por 16. Assim, o valor da contagem avança
de 0 a 15 e, em seguida, retorna a 0 e retoma a contagem, enquanto o contador estiver ligado.
A sintaxe do comando while é:

while expressão_condicional loop


grupo de comandos executados repetidamente
enquanto a expressão_condicional for verdadeira
end loop;

A palavra-chave while significa “enquanto”, em inglês. Assim, os comandos do grupo sob a palavra-chave while
são executados repetidamente enquanto a expressão condicional dada for verdadeira. A expressão condicional,
geralmente, é uma comparação, como, por exemplo, tempo < 100 us.
A sintaxe do comando de repetição for é:

for identificador in faixa_de_valores loop


grupo de comandos executados repetidamente
end loop;

Aqui, o identificador tem a função de variável, a qual assume, sucessivamente, os valores da faixa dada. Para cada
valor que ela assumir, é executado, uma vez, o grupo de comandos fornecido. Esta variável é automaticamente
criada quando usada no comando for e tem sua validade restrita a ele. Seu valor pode ser consultado, mas
não alterado, no grupo de comandos controlado pelo for. A faixa de valores assumidos por esta variável é dada
como:

valor_inicial to valor_final

o que faz com que o identificador parta do valor inicial dado e avance até o valor final ou, então, pode ser dada
como:

valor_inicial downto valor_final

quando o valor inicial for maior do que o final. Neste caso, a palavra downto indica que o valor do identificador
deverá descer do valor inicial ao final.
Um exemplo de uso do comando for é dado abaixo. Este módulo serve para se testar o contador dado
anteriormente:
Ricardo Pires - Lógica Configurável (FPGA) 23

entity contador_tb is
end contador_tb;

architecture comportamento of contador_tb is


signal ck : bit;
signal saida : integer;
begin comportamento
meu_contador : entity work.contador
port map (
ck => ck,
saida => saida);
process
begin
for i in 1 to 20 loop
ck <= ’0’;
wait for 10 ns;
ck <= ’1’;
wait for 10 ns;
end loop; i
wait;
end process;
end comportamento;

Exercício 4
Projetar um gerador de ondas retangulares, que receba pulsos de relógio e que gere três formas de onda binárias,
em três saídas: uma com metade da frequência de relógio, uma com um quarto da frequência de relógio e uma
com um oitavo da frequência de relógio.

5.10 Dados Compostos


Um tipo de dado composto consiste num agrupamento de dados logicamente relacionados. VHDL possui
recursos especiais para a criação e manipulação destes tipos de dados.
Uma forma de dado composto é o vetor (ou array). Ele consiste num agrupamento de dados do mesmo
tipo, identificados, individualmente, por meio de um índice. Um exemplo disto é o byte, o qual agrupa 8 bits
que só têm sentido quando estão em conjunto.
A outra forma de dado composto é o record. Ele agrupa dados que podem ser de tipos diferentes uns dos
outros, mas que estão relacionados logicamente. Um exemplo genérico de record em programação seria um
agrupamento contendo o nome de um aluno e seu prontuário. O nome é uma sequência de caracteres. O
prontuário poderia ser uma sequência de algarismos. Neste caso, nome e prontuário seriam de tipos diferentes.
Mas, num sistema com muitos alunos, haveria muitos nomes e muitos prontuários. Seria conveniente, então,
haver uma forma de se agrupar cada nome com o prontuário correspondente. É isto o que faz o record.

5.10.1 Vetores
Conforme já comentado, um vetor consiste num agrupamento de dados do mesmo tipo, identificados, indi-
vidualmente, por meio de um índice.
Para que se possa usar um vetor de um certo tipo (como o byte), é necessário que, previamente, seja criado
este tipo. A criação de um tipo de vetor tem a forma:

type nome_do_tipo is
Ricardo Pires - Lógica Configurável (FPGA) 24

array ( faixa_de_valores_dos_índices ) of tipo_dos_dados;

A criação do tipo byte, então, seria feita como:

type byte is
array ( 7 downto 0 ) of bit;

Neste caso, foi definido um tipo de dado chamado byte (o nome do tipo pode ser qualquer identificador inventado
pelo projetista). Foi especificado que o byte contém elementos do tipo bit. E, também, foi especificado que os
bits têm índices (ou posições) numerados de 7 até 0. Assim, têm-se 8 bits formando o byte.
Até agora, foi apenas criado o tipo byte. Após a criação do tipo, podem ser criadas variáveis ou criados
sinais deste tipo, como em:

variable a, b, c: byte;

Aqui, foram declaradas as variáveis a, b e c, todas do tipo byte, criado anteriormente.


Se, após esta declaração, desejar-se usar o valor de um único bit de uma daquelas variáveis, este bit deverá
ser identificado por meio de seu índice, como em:

a(3) := b(5);

Aqui, o valor do bit 5 da variável b é copiado para o bit 3 da variável a.


Para se copiar um byte inteiro de uma variável para outra, usa-se, por exemplo:

b := c;

Assim, os 8 bits de c são copiados para as posições correspondentes de b.


Um vetor de bits pode ser inicializado como neste exemplo:

variable b: byte := "01010101";

em que cada bit entra em sua posição esperada na variável b.


Outro exemplo de criação de vetor:

type vet_int_5 is
array ( 1 to 5 ) of integer;

descreve um tipo vetor para armazenar 5 números inteiros. Pode-se criar e inicializar uma variável deste tipo
com:

variable v: vet_int_5 := ( 3, 5, 7, 9, 1 );
Ricardo Pires - Lógica Configurável (FPGA) 25

em que v(1) é inicializado com o valor 3, v(2) com 5 etc. Outra forma de inicialização é:

variable v: vet_int_5 := ( 1 => 3, 2 => 5, 3 => 7, 4 => 9, 5 => 1 );

em que, em cada par i => v, i indica um índice no vetor e v indica o valor a ser colocado naquela posição.
Nesse caso, a ordem dos elementos na inicialização não importa. O resultado do último exemplo seria o mesmo
se fosse usado:

variable v: vet_int_5 := ( 4 => 9, 3 => 7, 1 => 3, 2 => 5, 5 => 1 );

Outra forma de inicialização é:

variable v: vet_int_5 := ( 4 => 9, others => 0 );

Neste caso, v(4) recebe o valor 9 e os outros elementos recebem o valor 0. Para se inicializarem todos os
elementos com valores iguais, pode-se fazer:

variable v: vet_int_5 := ( others => 99 );

em que todos os elementos do vetor foram inicializados com o valor 99.


Em VHDL, está pré-definido o tipo bit_vector, o qual é um vetor de bits. Um exemplo de declaração de
variável deste tipo é:

variable n : bit_vector( 0 to 15 );

Aqui, a variável n contém 16 bits, numerados de 0 a 15.

Exercício 5
Descrever, em VHDL, um sistema que possua uma entrada u e uma saída y, ambas para dados inteiros, e uma
entrada ck para sinal de relógio, do tipo bit. A cada subida de ck, um dado de u deverá ser lido pelo sistema.
A cada subida de ck, é colocado em y o dado que estava em u dez ciclos de ck atrás.

5.10.2 Records
Conforme já comentado, um record agrupa dados que podem ser de tipos diferentes uns dos outros, mas que
estão relacionados logicamente.
Um exemplo de uso de um record, baseado em exemplo dado em [5], é:

type horario is record


segundos: integer range 0 to 59;
minutos: integer range 0 to 59;
horas: integer range 0 to 23;
end record horario;

variable hora_atual: horario;


Ricardo Pires - Lógica Configurável (FPGA) 26

Neste exemplo, é definido um novo tipo de dado, chamado horario. Entre as palavras-chave is record e end record,
informa-se quais são os dados que compõem o tipo horario. Estes componentes, usualmente, são chamados
campos. Aqui, o tipo horario tem um campo chamado segundos, que é do tipo inteiro e pode assumir valores
entre 0 e 59. Um outro campo é o minutos, similar ao segundos. Finalmente, o campo horas pode assumir
valores entre 0 e 23.
Num record, os campos podem ser de tipos diferentes entre si.
Tendo sido definido o tipo horario, foi, então, criada a variável hora_atual, daquele tipo. Por ser do tipo
horario, a variável hora_atual possui aqueles três campos. Num certo momento, seu campo segundos poderia
valer, por exemplo, 40, seu campo minutos poderia valer 11 e seu campo horas poderia valer 5.
O acesso a um campo de uma variável record é feito por meio do operador ponto, conforme o exemplo a
seguir:

variable hora: integer;

hora := hora_atual.horas;

Aqui, o valor do campo horas da variável hora_atual foi copiado para a variável inteira hora, declarada previ-
amente. Esta atribuição poderia ser feita “ao contrário”, ou seja, o valor da variável hora poderia ser fornecido
ao campo horas de hora_atual, invertendo-se as posições das variáveis na atribuição:

hora_atual.horas := hora;

Finalmente, se for criada uma nova variável do tipo horario, como:

variable hora_final: horario;

a atribuição:

hora_final := hora_atual;

copiará os valores dos três campos de hora_atual para os campos correspondentes na variável hora_final.

5.11 Genéri os
Há certos tipos de blocos que possuem alguma característica que seria conveniente que fosse definida apenas
quando algum exemplar deste tipo de bloco fosse usado, não durante seu projeto. Por exemplo, ao se projetar
um registrador, poder-se-ia desejar que seu número de bits fosse deixado como um parâmetro genérico, já que
registradores possuem características gerais a serem levadas em conta no projeto que não dependem de seu
número de bits. Não é desejável que alguém tenha de projetar, individualmente, uma versão de registrador com
8 bits, outra com 16 bits, outra com 32 bits etc., já que elas são, qualitativamente, quase iguais.
Para se permitir o uso de parâmetros genéricos em projetos, existe o recurso do uso de um parâmetro chamado
generic na interface de um tipo de bloco. Para se compreender seu uso e sua utilidade, será desenvolvido aqui
um projeto de um divisor de frequência digital. Ele receberá um sinal de relógio e fornecerá um pulso de saída
a cada n pulsos de relógio.
Inicialmente, desenvolver-se-á um divisor de frequência específico, o qual gerará um pulso de saída a cada 5
pulsos de relógio:

entity divisor_frequencia_5 is
port (
entrada : in bit;
saida : out bit);
end divisor_frequencia_5;
Ricardo Pires - Lógica Configurável (FPGA) 27

architecture arch of divisor_frequencia_5 is


begin arch
gera_saida: process (entrada)
variable conta : integer := 0;
begin process gera_saida
if entrada = ’1’ then
conta := conta + 1;
if conta > 5 then
conta := 1;
end if ;
if conta = 5 then
saida <= ’1’;
else
saida <= ’0’;
end if ;
end if ;
end process gera_saida;
end arch;

Exercício 6
Crie um circuito de teste para o divisor_frequencia_5. Ele deverá fornecer pulsos de relógio à entrada. Verifique
que a saída gera um pulso a cada 5 pulsos de entrada.

Após o desenvolvimento deste divisor de frequência por 5, seu projetista poderia precisar de um novo divisor
de frequência, desta vez por um outro número, como, por exemplo, 3. Uma solução seria copiar-se a descrição
do divisor por 5, dando-se à cópia um outro nome, como divisor_frequencia_3.vhdl, e substituir-se, na cópia,
cada número 5 por um número 3. Este procedimento poderia se repetir, cada vez que se desejasse criar um
divisor por um novo número.
Esta é uma situação típica em que se usa um parâmetro genérico. Uma solução para este problema usando-se
um genérico é:

entity divisor_frequencia is
generic (
gen_divisao_por : integer);
port (
entrada : in bit;
saida : out bit);
end divisor_frequencia;
Ricardo Pires - Lógica Configurável (FPGA) 28

architecture arch of divisor_frequencia is


begin arch
gera_saida: process (entrada)
variable conta : integer := 0;
begin process gera_saida
if entrada = ’1’ then
conta := conta + 1;
if conta > gen_divisao_por then
conta := 1;
end if ;
if conta = gen_divisao_por then
saida <= ’1’;
else
saida <= ’0’;
end if ;
end if ;
end process gera_saida;
end arch;

Esta descrição possui a palavra-chave generic em sua interface. Ela introduz uma lista de parâmetros
genéricos, a qual, neste caso, tem apenas um elemento, o genérico arbitrariamente chamado gen_divisao_por.
Trata-se, aqui, de um genérico do tipo inteiro, cuja função é definir por qual número será feita a divisão de
frequência.
Na arquitetura, vê-se que o processo gera_saida, agora, ao invés de usar um valor específico para a divisão
(como o 5, da descrição anterior), usa o nome do parâmetro genérico gen_divisao_por.
Assim, tem-se, agora, a descrição de um divisor de frequência por um número inteiro qualquer. É um divisor
de frequência genérico. O sistema que o utilizar como componente terá de definir o valor deste parâmetro
genérico, conforme feito no exemplo a seguir:
Ricardo Pires - Lógica Configurável (FPGA) 29

entity divisor_frequencia_tb is
end divisor_frequencia_tb;

architecture arch of divisor_frequencia_tb is


signal entrada, saida_5, saida_7 : bit;
begin arch
divisor_por_5: entity work.divisor_frequencia
generic map (
gen_divisao_por => 5)
port map (
entrada => entrada,
saida => saida_5);
divisor_por_7: entity work.divisor_frequencia
generic map (
gen_divisao_por => 7)
port map (
entrada => entrada,
saida => saida_7);
gera_entrada: process
begin process gera_entrada
for i in 1 to 20 loop
entrada <= ’0’;
wait for 0.5 ns;
entrada <= ’1’;
wait for 0.5 ns;
end loop; i
wait;
end process gera_entrada;
end arch;

Vê-se que, nesta descrição, são usados dois exemplares de divisores de frequência genéricos: um para divisão
por 5 e o outro para divisão por 7. Vê-se que a definição do valor do parâmetro genérico é feita numa lista de
associações chamada generic map, semelhante à port map já estudada.
Neste exemplo, a lista de parâmetros genéricos do divisor tinha apenas um parâmetro (gen_divisao_por ).
Em geral, uma lista destas pode ter um número ilimitado de parâmetros, cujos valores são definidos na lista
generic map do sistema utilizador do componente.

Exercício 7
Descrever, em VHDL, um sistema que possua uma entrada u e uma saída y, ambas para dados inteiros, e uma
entrada ck para sinal de relógio, do tipo bit. A cada subida de ck, um dado de u deverá ser lido pelo sistema. A
cada subida de ck, é colocado em y o dado que estava em u n ciclos de ck atrás, sendo n um parâmetro genérico
do sistema. No seu teste, usar dois exemplares deste tipo de módulo: um com n = 3, o outro com n = 5.

5.12 Bibliote a numeri _bit


É uma biblioteca fornecida pelo IEEE. Define vetores de bits para representar, em binário, números inteiros
com sinal ou sem sinal. O número de bits usado na representação de cada número inteiro deve ser definido pelo
usuário da biblioteca. As definições destes tipos são:

type unsigned is array (natural range <> ) of bit;


type signed is array (natural range <> ) of bit;
Ricardo Pires - Lógica Configurável (FPGA) 30

O tipo unsigned representa, em binário, números inteiros sem sinal, ou seja, números não negativos. O tipo
signed representa números com sinal, ou seja, pode representar números inteiros negativos, usando complemento
de dois.
Nestas declarações, o range <> indica que a faixa dos índices não está especificada. O usuário da biblioteca
é que deverá especificá-la.
Uma grande utilidade da biblioteca numeric_bit é que ela define como operações algébricas, tais como “+”,
“-”, “∗” e “/”, aplicam-se a séries de bits que representam números inteiros.
Eis um exemplo de uso:

library ieee;

use ieee.numeric_bit.all;

entity soma_binarios is
port (
a : in signed ( 3 downto 0 );
b : in signed ( 3 downto 0 );
s : out signed ( 3 downto 0 ));
end soma_binarios;

architecture calcula of soma_binarios is


begin calcula
calcula_saida: process (a,b)
begin process calcula_saida
s <= a + b;
end process calcula_saida;
end calcula;

Aqui, é definido um módulo chamado soma_binarios. Ele tem duas entradas com 4 bits cada uma, indexados
de 3 a 0. A saída tem o mesmo formato que as entradas. Todos estes terminais são do tipo signed. Assim, eles
são considerados números inteiros em binário, com sinal, usando representação em complemento de dois.
O processo chamado calcula_saida é sensível às duas entradas. Assim que uma delas tiver variação em seu
valor, usando-se o operador “+”, a saída é calculada como sendo a soma das entradas.

Exercício 8
Crie um módulo de teste para este somador. Ele deverá fornecer valores variados às entradas a e b, positivos
e negativos. Verifique os resultados. Atenção para a possibilidade de resultados que estejam fora da faixa de
valores representáveis em complemento de dois com 4 bits.

5.13 Simulação Usando-se Arquivos


VHDL fornece recursos para o registro, em arquivos, de resultados de uma simulação, bem como para a
leitura, em arquivos, de estímulos a serem usados numa simulação.
Como um exemplo, tem-se, a seguir, a descrição de um módulo de teste para o contador apresentado na
página 21.
Ricardo Pires - Lógica Configurável (FPGA) 31

use std.textio.all;

entity contador_tb_texto is
end contador_tb_texto;

architecture comportamento of contador_tb_texto is


signal ck : bit;
signal saida : integer;
begin comportamento
meu_contador: entity work.contador
port map (
ck => ck,
saida => saida);
estimula: process
begin process estimula
for i in 1 to 50 loop
ck <= ’0’;
wait for 0.5 ns;
ck <= ’1’;
wait for 0.5 ns;
end loop; i
wait;
end process estimula;
gera_arq: process (ck)
file arquivo : text is out "resultado.txt";
variable linha : line;
begin process gera_arq
write( linha, string’( "tempo: " ) );
write( linha, now );
write( linha, string’( ", ck: " ) );
write( linha, ck );
write( linha, string’( ", saida: " ) );
write( linha, saida );
writeline( arquivo, linha );
end process gera_arq;
end comportamento;

Nesta descrição, resultados da simulação são escritos num arquivo chamado resultado.txt. Isto requer a
inclusão da linha

use std.textio.all;

a qual indica que será usada a biblioteca std.textio, em que são definidas funções para interação com arquivos
do tipo texto. (Também é possível a interação com arquivos em formato binário, não legíveis em editores de
texto.)
Na parte arquitetural desta descrição, o processo gera_arq é o responsável pela escrita no arquivo. Vê-se
que ele é sensível a mudanças no sinal ck.
Nele, a linha:

file arquivo : text is out "resultado.txt";

declara a variável arquivo como sendo do tipo file. É por meio desta variável que o processo interage com o
arquivo. Especifica-se que o arquivo é do tipo texto (text) e que será aberto no modo out, ou seja, saída, o que
Ricardo Pires - Lógica Configurável (FPGA) 32

significa que dados sairão do processo para o arquivo.


Logo a seguir, é declarada uma variável chamada linha, do tipo line.
No corpo do processo, vê-se que é usada uma série de comandos write. Cada write recebe dois parâmetros:
o nome de uma linha e um dado a ser escrito nela. Assim, uma série de write constrói uma linha, acrescentando
sucessivamente dados a ela.
Quando se deseja, explicitamente, escrever uma palavra numa linha, o parâmetro correspondente ao dado a
ser escrito na linha é precedido pelo operador string’, conforme visto em algumas linhas do processo. Isto faz
com que aquela palavra não seja interpretada como o nome de uma variável.
A palavra reservada now representa o valor do tempo atual de simulação. Quando se envia now para
uma linha, o arquivo recebe um fragmento de texto contendo o instante da simulação em que aquela linha foi
executada, por exemplo, “34 ns”.
Para se escrever o valor de um sinal ou de uma variável no arquivo, basta que se indique o nome deste sinal
ou variável como o segundo parâmetro de write. Neste processo, isto é feito para os sinais: ck e saida.
Quando uma linha estiver pronta, ela é escrita no arquivo usando-se, neste exemplo:

writeline( arquivo, linha );

Isto envia a linha indicada para o arquivo indicado e, em seguida, esvazia a variável linha, deixando-a
disponível para a montagem de uma nova linha.

Exercício 9
Realize uma simulação usando o contador da página 21 e este módulo para teste. Verifique o arquivo resultado.txt
gerado.

A leitura de estímulos de simulação em um arquivo é ilustrada no exemplo a seguir. O objetivo é o teste da


porta ou apresentada na página 11.
Inicialmente, é criado um arquivo contendo os estímulos de teste, os quais são pares de bits a serem fornecidos,
sucessivamente, às entradas da porta ou. Supõe-se, aqui, que estes estímulos estejam num arquivo chamado
ou_estimulos.txt, cujo conteúdo é:

0 0
0 1
1 0
1 1

Neste caso, deseja-se realizar um teste exaustivo da porta ou, fornecendo às suas duas entradas todas as com-
binações possíveis de valores de bits.
A descrição do módulo de teste é:
Ricardo Pires - Lógica Configurável (FPGA) 33

use std.textio.all;

entity ou_tb_arq is
end ou_tb_arq;

architecture teste of ou_tb_arq is


signal a, b, s : bit;
begin
minha_ou: entity work.porta_ou
port map (entrada_1 => a, entrada_2 => b, saida => s);
estimula: process
file arquivo : text is in "ou_estimulos.txt";
variable linha : line;
variable leu : boolean;
variable bit_lido : bit;
begin process estimula
loop
readline( arquivo, linha );
read( linha, bit_lido, leu );
if leu = true then
a <= bit_lido;
read( linha, bit_lido, leu );
if leu = true then
b <= bit_lido;
wait for 1 ns;
else
wait;
end if ;
else
wait;
end if ;
end loop;
end process estimula;
end teste;

Nesta descrição, o processo chamado estimula cria uma variável chamada arquivo, do tipo file, no modo in.
Isto faz com que dados do arquivo possam entrar no processo, vindo do arquivo.
O arquivo é lido linha a linha, pelo comando readline. Cada linha lida tem seu conteúdo colocado na variável
linha, do tipo line.
Uma linha pode conter vários dados. (Neste exemplo, cada linha contém dois bits.) Cada dado pode ser
lido de uma linha usando-se o comando read, o qual, neste caso, tem três parâmetros: o nome da variável do
tipo line de onde o dado será lido, o nome da variável que receberá este dado e uma variável booleana, a qual
indicará se a leitura foi bem-sucedida. Neste exemplo, o bit lido irá sempre para uma variável chamada bit_lido
e a variável booleana é chamada leu.
Nas linhas seguintes do processo estimula, vê-se que comandos condicionais if verificam se a leitura foi bem-
sucedida e, caso positivo, colocam o valor de bit_lido no sinal a ou no sinal b. Após a leitura do sinal b, que
convencionou-se como sendo o segundo bit de cada linha, há um comando wait for 1 ns, para que o par de bits
que acaba de ser lido permaneça nas entradas do ou_exclusivo durante 1 ns. Caso a leitura de algum bit falhe,
é executado um comando wait simples, o qual suspende a simulação. Neste exemplo, a leitura falha quando se
tenta ler a quinta linha do arquivo, a qual está vazia. Em testes feitos usando-se o compilador e simulador ghdl,
foi necessário incluir-se uma linha em branco explícita como sendo a quinta linha do arquivo ou_estimulos.txt,
para se provocar a falha de leitura que finaliza a simulação.
Ricardo Pires - Lógica Configurável (FPGA) 34

Exercício 10
Execute a simulação da porta ou usando o módulo de teste ou_tb_arq e o arquivo de estímulos ou_estimulos.txt.

5.14 Geração de Estruturas Regulares Usando-se o Comando generate


Suponha que se deseje descrever, em VHDL, uma estrutura regular, como a da figura 7.

inversores
entr(4) sai(4)

entr(3) sai(3)

entr(2) sai(2)

entr(1) sai(1)

entr(0) sai(0)

Figura 7: Exemplo de estrutura regular

Ela poderia ser descrita usando-se método já apresentado neste texto, segundo o qual seria dado um nome
a cada exemplar de porta não (por exemplo, nao_0, nao_1 etc.) e, numa descrição arquitetural estrutural,
seriam repetidos, cinco vezes, trechos do tipo:

nao_x: entity work.porta_nao


port map (
entrada => entr(x),
saida => sai(x) );

em que cada uma das cinco cópias deste trecho teria os x substituídos pelos números apropriados, de 0 a 4.
Para se evitarem estas repetições manuais, o comando generate facilita a exploração da regularidade estru-
tural, em combinação com uma construção de repetição do tipo laço for, usando a mesma ideia relacionada ao
uso de repetições do tipo for já vista em processos.
Uma descrição do sistema da figura 7 usando generate é:
Ricardo Pires - Lógica Configurável (FPGA) 35

entity inversores_generate is
port (
entr : in bit_vector(0 to 4);
sai : out bit_vector(0 to 4));
end inversores_generate;

architecture regular of inversores_generate is


begin regular
gera_portas: for num_porta in 0 to 4 generate
uma_porta: entity work.porta_nao
port map (
entrada => entr(num_porta),
saida => sai(num_porta));
end generate gera_portas;
end regular;

Na arquitetura desta descrição, é usado um laço do tipo for...generate para gerar iterativamente cópias de
portas não e conectá-las aos terminais do sistema. Aqui, a construção for...generate possui um nome arbitrário
(rótulo): gera_portas. Há uma variável para iteração, chamada num_porta. Ela assume, sucessivamente, cada
valor inteiro de 0 a 4. Para cada um dos valores assumidos, é criada uma cópia de porta não (como a descrita na
página 10). O nome da cópia (uma_porta) pertence apenas à iteração em que é usado. Por isto, não é problema
este nome ser gerado cinco vezes, pois cada iteração não “vê” nomes criados em outras iterações. Esta localidade
de nomes é similar à verificada na possibilidade de repetição de nomes de variáveis de um processo para outro.
Nas conexões de cada porta aos terminais do sistema, vê-se a variável de iteração num_porta gerando os índices
apropriados.
Viu-se que o uso de um laço for...generate possibilitou que a descrição daquela estrutura regular ficasse
muito compacta. Se, em lugar desta estrutura contendo cinco portas, fosse desejada uma estrutura contendo,
por exemplo, cem portas não em paralelo, uma descrição em VHDL quase igual a esta poderia ser usada, desde
que se substituísse, nesta, cada número 4 (na interface e no for ) por um número 99. Este fato é normalmente
explorado associando-se o recurso da parametrização por genérico ao do for...generate, conforme o exemplo a
seguir:

entity inversores_generate_generico is
generic (
quantas_portas : integer);
port (
entr : in bit_vector(0 to (quantas_portas 1));
sai : out bit_vector(0 to (quantas_portas 1)));
end inversores_generate_generico;

architecture regular of inversores_generate_generico is


begin regular
gera_portas: for num_porta in 0 to (quantas_portas 1) generate
uma_porta: entity work.porta_nao
port map (
entrada => entr(num_porta),
saida => sai(num_porta));
end generate gera_portas;
end regular;

Neste exemplo, ao invés de se usar um número fixo de portas, deixou-se este número parametrizável por
meio do genérico quantas_portas. O usuário de um componente deste tipo, então, deverá especificar o valor
deste genérico, de acordo com sua necessidade.
Suponha que se deseje gerar uma estrutura regular um pouco mais complexa do que a anterior, dada na
Ricardo Pires - Lógica Configurável (FPGA) 36

figura 8, a qual consiste em um conjunto regular de associações em série de portas não a buffers.

portas_nao_buers
entr(4) sai(4)

entr(3) sai(3)

entr(2) sai(2)

entr(1) sai(1)

entr(0) sai(0)

Figura 8: Outro exemplo de estrutura regular

Uma solução, usando-se generate, é:

entity portas_nao_buffers is
port (
entr : in bit_vector(0 to 4);
sai : out bit_vector(0 to 4));
end portas_nao_buffers;

architecture regular of portas_nao_buffers is


begin regular
gera_portas: for num_porta in 0 to 4 generate
signal interno : bit;
begin
uma_porta_nao: entity work.porta_nao
port map (
entrada => entr(num_porta),
saida => interno );
uma_porta_buffer: entity work.porta_buffer
port map (
entrada => interno,
saida => sai(num_porta) );
end generate gera_portas;
end regular;

Neste caso, a estrutura regular possui conexões internas (entre inversores e buffers). Em cada iteração do
for...generate, foi declarado um sinal chamado interno, para realizar a conexão interna correspondente àquela
iteração. Como esta declaração é interna à iteração, não há problema em se repetir o nome interno de uma
iteração à outra, já que cada iteração não conhece nomes usados internamente pelas outras. Este é mais um
caso de localidade de declaração. Deve-se notar que, neste exemplo, a presença de uma declaração de sinal
submetida ao for...generate requer o uso de um begin logo em seguida, para separar aquela declaração do corpo
do for...generate.
Em comandos generate, podem ser usadas estruturas condicionais do tipo if. Isto se aplica ao exemplo da
figura 9.
Eis uma descrição deste sistema usando-se if dentro de generate:
Ricardo Pires - Lógica Configurável (FPGA) 37

portas_nao_serie

entr i(1) i(2) i(3) i(4) sai

Figura 9: Outro exemplo de estrutura regular

entity portas_nao_serie is
port (
entr : in bit;
sai : out bit);
end portas_nao_serie;

architecture serie of portas_nao_serie is


signal i : bit_vector( 1 to 4 );
begin serie
gera_portas: for num_porta in 1 to 5 generate
esquerda: if num_porta = 1 generate
porta_esquerda: entity work.porta_nao
port map (
entrada => entr,
saida => i(num_porta));
end generate esquerda;
meio: if num_porta > 1 and num_porta < 5 generate
porta_meio: entity work.porta_nao
port map (
entrada => i(num_porta 1),
saida => i(num_porta));
end generate meio;
direita: if num_porta = 5 generate
porta_direita: entity work.porta_nao
port map (
entrada => i(num_porta 1),
saida => sai);
end generate direita;
end generate gera_portas;
end serie;

Evidentemente, neste exemplo, foram usados condicionais if porque havia três regras distintas para a conexão
das portas, segundo suas posições na série. Foram usados três if em sequência, independentes entre si. Na versão
2008 de VHDL, introduziu-se a construção if...elsif...else associada a generate. Assim, a partir daquela versão,
aquela sequência de três if do exemplo poderia ser substituída por uma if...elsif...else equivalente.
Nesta descrição, os sinais internos foram declarados fora do for...generate, porque cada um deles deve ser
visto por duas iterações deste for (pela iteração que gera a porta à esquerda do sinal e pela iteração que gera a
porta à direita dele). Portanto, cada sinal não é local a uma iteração.
O recurso for...generate tanto pode ser usado para a geração de várias cópias de um bloco estrutural, como
nestes exemplos, como para a geração de cópias de processos.
Ricardo Pires - Lógica Configurável (FPGA) 38

A Uso do Compilador e Simulador GHDL


Em sistemas operacionais Unix/Linux, uma forma de se usar o GHDL é por meio de uma janela de comandos
(terminal).
Cada descrição em VHDL usada num projeto deverá ter o nome de seu arquivo repetindo o nome da entity
nele descrita e .vhdl ao final. Por exemplo, se um arquivo contiver a descrição de uma entity chamada teste, o
nome do arquivo deverá ser teste.vhdl.
Cada descrição deverá ser compilada pelo GHDL, usando-se o comando:

ghdl -a <nome_do_arquivo.vhdl>

Aqui, -a indica analyse. O GHDL analisa a sintaxe do arquivo e, caso não haja erros, compila-o, gerando um
correspondente arquivo objeto, com .o em lugar de .vhdl em seu nome.
Se o projeto contiver vários arquivos em VHDL, todos eles deverão ser compilados desta forma, em ordem
tal que os blocos mais básicos sejam compilados antes daqueles que os usem, ou seja, num projeto hierárquico,
a compilação deverá começar pela base da hierarquia e terminar no topo.
Em seguida, deverá ser gerado um arquivo executável, correspondente ao bloco do topo da hierarquia. Para
isto, usa-se o comando:

ghdl -e <nome_do_bloco_do_topo>

Aqui, -e indica elaborate, porque o sistema é elaborado, reunindo-se as informações de todos os seus blocos
obtidas na fase de compilação. Deve-se observar que, nesta linha de comando, é usado o nome do bloco do topo
da hierarquia, não o nome de seu arquivo. Por isto, não se usa .vhdl naquela linha. Por exemplo, se o bloco do
topo da hierarquia se chamar principal, a elaboração será ativada usando-se:

ghdl -e principal

Isto gera um arquivo executável, cujo nome é igual ao do bloco do topo da hierarquia. Sua execução calcula as
sequências temporais de valores assumidos por todos os sinais presentes no sistema. Por isto, só há sentido em
se elaborar um sistema em que haja estímulos aplicados às entradas de seus blocos.
Finalmente, a execução deste arquivo é ativada usando-se:

ghdl -r <nome_do_bloco_do_topo>

Se a descrição de um sistema não escrever na tela do computador, nem em arquivos, os valores assumidos
pelos sinais ao longo do tempo, sua simulação não será visível pelo projetista, a menos que estes valores sejam
gravados num arquivo que descreva formas de onda digitais. Um formato padrão para este tipo de arquivo é o
VCD.
Para que seja gerado um arquivo em VCD durante a simulação, a execução deverá ser ativada usando-se:

ghdl -r <nome_do_bloco_do_topo> --vcd=<arquivo_de_ondas.vcd>

No exemplo anterior, poderia ser usado:

ghdl -r principal --vcd=resultado.vcd

Neste caso, o arquivo resultado.vcd conteria a descrição das formas de onda geradas na simulação.
Há vários programas capazes de ler arquivos em VCD e apresentar as ondas nele descritas. Um bom exemplo
é o GTKWave [8]. Após sua instalação, ele poderá ser ativado, na janela de comandos, usando-se:

gtkwave <arquivo_de_ondas.vcd> &

No exemplo anterior, usar-se-ia:

gtkwave resultado.vcd &


Ricardo Pires - Lógica Configurável (FPGA) 39

B Des rição de Sistema para Teste


Após o término da descrição de um sistema ou de um módulo em VHDL, deve-se simular seu funcionamento,
para que se detectem eventuais erros de projeto, antes de implementá-lo em FPGA (ou em circuito integrado
de aplicação específica). No apêndice A, vê-se como o simulador GHDL pode ser usado com esta finalidade.
Do ponto de vista de descrições em VHDL, para se fazer uma simulação, normalmente, usa-se um módulo
especial, chamado de mesa de teste (ou testbench, em inglês). A função desta mesa de teste é fornecer estímulos
de teste ao módulo recém-projetado. Evidentemente, após a correção de eventuais erros de projeto, a mesa de
teste não será implementada em hardware. Sua finalidade se restringe ao teste do projeto.
Como exemplo de uso de mesa de teste, suponha que se deseja testar a porta e descrita a seguir (já vista
na página 11 neste texto):

entity porta_e is
port (
entrada_1, entrada_2 : in bit;
saida : out bit);
end porta_e;

architecture basica of porta_e is


begin
calcula_saida: process (entrada_1, entrada_2)
begin
saida <= entrada_1 and entrada_2;
end process calcula_saida;
end basica;

Neste caso, a mesa de teste pode ser representada conforme a figura 10.

testada
fio_1
fio_s
fio_2

processo
testa

Figura 10: Mesa de teste

Sua descrição em VHDL é:


Ricardo Pires - Lógica Configurável (FPGA) 40

entity mesa_de_teste is
end mesa_de_teste;

architecture teste of mesa_de_teste is


signal fio_1, fio_2, fio_s : bit;
begin teste
testada : entity work.porta_e
port map (
entrada_1 => fio_1,
entrada_2 => fio_2,
saida => fio_s);
testa: process
begin process testa
fio_1 <= ’0’; fio_2 <= ’0’;
wait for 1 ns;
fio_1 <= ’0’; fio_2 <= ’1’;
wait for 1 ns;
fio_1 <= ’1’; fio_2 <= ’0’;
wait for 1 ns;
fio_1 <= ’1’; fio_2 <= ’1’;
wait for 1 ns;
wait;
end process testa;
end teste;

Nesta descrição, a interface não contém terminais. Isto ocorre porque a mesa de teste não se comunica com
seu exterior (ou seja, dados não atravessam a linha tracejada da figura 10). A mesa é “auto-suficiente”, não será
usada como componente de outro sistema.
Na parte de arquitetura, são usados dois blocos: a porta e a ser testada e um processo gerador de estímulos
de teste. O exemplar de porta e usado pela mesa de teste foi chamado de testada. O processo gerador de
estímulos foi chamado de testa.
Vê-se que sinais, chamados de fio_1, fio_2 e fio_s, conectam o processo à porta. É por eles que estímulos
de teste são levados do processo à porta.
Vê-se que, neste caso, o teste é exaustivo, ou seja, são fornecidas à porta todas as combinações possíveis de
valores de entrada. (Normalmente, testes exaustivos não são usados, devido ao número de combinações possíveis
ser muito grande.) Cada combinação de valores é aplicada durante um intervalo de 1 ns. Os intervalos entre
aplicações de estímulos são indispensáveis. Sem eles, estímulos diferentes seriam aplicados ao mesmo tempo às
mesmas entradas.
Nota-se, também, que há um comando wait; ao final do processo. Este wait; sem intervalo determinado
causa o término da simulação.

Referên ias
[1] https://www.xilinx.com/training/fpga/fpga-field-programmable-gate-array.htm, acesso em 30/01/2017
[2] http://www.xilinx.com, acesso em 30/01/2017

[3] http://ghdl.free.fr, acesso em 30/01/2017

[4] Spartan 3E FPGA Family: Complete Data Sheet, Xilinx, disponível em


https://www.xilinx.com/support/documentation/data_sheets/ds312.pdf, acesso em 30/01/2017

[5] ASHENDEN,P.J.
The Designer’s Guide to VHDL
3rd Edition, 2008, Morgan Kauffman Publishers, Estados Unidos
Ricardo Pires - Lógica Configurável (FPGA) 41

[6] D’AMORE,R.
VHDL - Descrição e Síntese de Circuitos Digitais
2012, Grupo GEN, São Paulo

[7] http://www.ieee.org, acesso em 30/01/2017

[8] http://gtkwave.sourceforge.net, acesso em 30/01/2017

Vous aimerez peut-être aussi