0% acharam este documento útil (0 voto)
66 visualizações

VHDL Tutorial

Enviado por

Danilo
Direitos autorais
© © All Rights Reserved
Levamos muito a sério os direitos de conteúdo. Se você suspeita que este conteúdo é seu, reivindique-o aqui.
Formatos disponíveis
Baixe no formato PDF, TXT ou leia on-line no Scribd
0% acharam este documento útil (0 voto)
66 visualizações

VHDL Tutorial

Enviado por

Danilo
Direitos autorais
© © All Rights Reserved
Levamos muito a sério os direitos de conteúdo. Se você suspeita que este conteúdo é seu, reivindique-o aqui.
Formatos disponíveis
Baixe no formato PDF, TXT ou leia on-line no Scribd
Você está na página 1/ 84

Machine Translated by Google

Tutorial VHDL

Peter J. Ashenden
EDA CONSULTOR, ASHENDEN DESIGNS PTY.
LTD. www.ashhenden.com.au

© 2004 por Elsevier Science (EUA)


Todos os direitos reservados
Machine Translated by Google
Machine Translated by Google

1
Introdução

O objetivo deste tutorial é descrever a linguagem de modelagem VHDL. O VHDL inclui recursos
para descrever a estrutura lógica e a função de sistemas digitais em vários níveis de abstração,
desde o nível do sistema até o nível do portão. Pretende-se, entre outras coisas, como uma
linguagem de modelagem para especificação e simulação. Também podemos usá-lo para síntese
de hardware se nos restringirmos a um subconjunto que possa ser traduzido automaticamente em
hardware.
O VHDL surgiu do programa Very High Speed Integrated Circuits (VHSIC) do governo dos
Estados Unidos. No decorrer deste programa, ficou claro que havia a necessidade de uma linguagem
padrão para descrever a estrutura e a função dos circuitos integrados (CIs). Assim, o VHSIC
Hardware Description Language (VHDL) foi desenvolvido. Posteriormente, foi desenvolvido sob os
auspícios do Institute of Electrical and Electronic Engineers (IEEE) e adotado na forma do IEEE
Standard 1076, Standard VHDL Language Reference Manual, em 1987. Esta primeira versão padrão
da linguagem é frequentemente referida como VHDL-87.

Como todos os padrões IEEE, o padrão VHDL está sujeito a revisão pelo menos a cada cinco
anos. Comentários e sugestões de usuários do padrão de 1987 foram analisados pelo grupo de
trabalho do IEEE responsável pelo VHDL, e em 1992 foi proposta uma versão revisada do padrão.
Isso acabou sendo adotado em 1993, dando-nos o VHDL-93. Uma nova rodada de revisão do
padrão foi iniciada em 1998. Esse processo foi concluído em 2001, dando-nos a versão atual da
linguagem, VHDL-2002.
Este tutorial descreve os recursos de idioma que são comuns a todas as versões do idioma.
Eles são expressos usando a sintaxe do VHDL-93 e versões subsequentes.
Existem alguns aspectos de sintaxe que são incompatíveis com a versão original do VHDL-87. No
entanto, a maioria das ferramentas agora suporta pelo menos VHDL-93, portanto, diferenças
sintáticas não devem causar problemas.
O tutorial não cobre o idioma de forma abrangente. Em vez disso, ele apresenta os recursos
básicos de linguagem necessários para começar a modelar sistemas digitais relativamente simples.
Para uma cobertura completa, o leitor deve consultar The Designer's Guide to VHDL, 2nd Edition,
de Peter J. Ashenden, publicado pela Morgan Kaufman Publishers (ISBN 1-55860-674-2).

1
Machine Translated by Google
Machine Translated by Google

2
Conceitos fundamentais

2.1 Modelagem de Sistemas Digitais


O termo sistemas digitais abrange uma variedade de sistemas, desde componentes de baixo nível
até projetos completos de sistema em um chip e em nível de placa. Se quisermos abranger essa
gama de visões de sistemas digitais, devemos reconhecer a complexidade com a qual estamos
lidando. Não é humanamente possível compreender tais sistemas complexos em sua totalidade.
Precisamos encontrar métodos para lidar com a complexidade, para que possamos, com algum
grau de confiança, projetar componentes e sistemas que atendam aos seus requisitos.

A maneira mais importante de enfrentar esse desafio é adotar uma metodologia sistemática
de projeto. Se começarmos com um documento de requisitos para o sistema, podemos projetar
uma estrutura abstrata que atenda aos requisitos. Podemos então decompor essa estrutura em
uma coleção de componentes que interagem para realizar a mesma função. Cada um desses
componentes pode, por sua vez, ser decomposto até chegarmos a um nível em que temos alguns
componentes primitivos prontos que executam uma função necessária. O resultado desse processo
é um sistema composto hierarquicamente, construído a partir dos elementos primitivos.

A vantagem desta metodologia é que cada subsistema pode ser projetado independentemente
dos outros. Quando usamos um subsistema, podemos pensar nele como uma abstração em vez
de considerar sua composição detalhada. Portanto, em qualquer estágio específico do processo de
design, precisamos apenas prestar atenção à pequena quantidade de informações relevantes para
o foco atual do design. Somos salvos de sermos oprimidos por uma grande quantidade de detalhes.

Usamos o termo modelo para significar nossa compreensão de um sistema. O modelo


representa a informação que é relevante e abstrai dos detalhes irrelevantes.
A implicação disso é que pode haver vários modelos do mesmo sistema, uma vez que informações
diferentes são relevantes em contextos diferentes. Um tipo de modelo pode se concentrar em
representar a função do sistema, enquanto outro tipo pode representar a forma como o sistema é
composto de subsistemas.
Há uma série de motivações importantes para formalizar essa ideia de modelo, incluindo

• expressar os requisitos do sistema de forma completa e inequívoca

• documentar a funcionalidade de um sistema

• testar um projeto para verificar se ele funciona corretamente


3
Machine Translated by Google

4 Conceitos fundamentais

• verificar formalmente as propriedades de um projeto

• sintetizar uma implementação em uma tecnologia de destino (por exemplo, ASIC ou FPGA)

O fator unificador é que queremos alcançar a máxima confiabilidade no processo de projeto com
custo e tempo de projeto mínimos. Precisamos garantir que os requisitos sejam claramente especificados
e compreendidos, que os subsistemas sejam usados corretamente e que os projetos atendam aos
requisitos. Um dos principais contribuintes para o custo excessivo é ter que revisar um projeto após a
fabricação para corrigir erros. Ao evitar erros e fornecer melhores ferramentas para o processo de projeto,
os custos e atrasos podem ser contidos.

2.2 Conceitos de Modelagem VHDL


Nesta seção, veremos os conceitos básicos de VHDL para modelagem comportamental e estrutural. Isso
fornecerá uma noção do VHDL e uma base para trabalhar em capítulos posteriores. Como exemplo,
veremos maneiras de descrever um registrador de quatro bits, mostrado na Figura 2-1.

Usando a terminologia VHDL, chamamos o módulo reg4 de entidade de design e as entradas e


saídas são portas. A Figura 2-2 mostra uma descrição em VHDL da interface para esta entidade. Este é
um exemplo de uma declaração de entidade. Ele introduz um nome para a entidade e lista as portas de
entrada e saída, especificando que elas carregam valores de bit ('0' ou '1') dentro e fora da entidade. A
partir disso, vemos que uma declaração de entidade descreve a visão externa da entidade.

FIGURA 2-1

reg4
d0 q0
d1 q1
d2 q2
d3 q3

dentro

clique

Um módulo de registro de quatro bits. O registrador é denominado reg4 e possui seis entradas, d0, d1, d2, d3, en e
clk, e quatro saídas, q0, q1, q2 e q3.

FIGURA 2-2

entidade reg4
é porta (d0, d1, d2, d3, en, clk: bit de
entrada; q0, q1, q2, q3: bit de
saída ); entidade final reg4;

Uma descrição de entidade VHDL de um registrador de quatro bits.


Machine Translated by Google

Conceitos de modelagem VHDL 5

Elementos do Comportamento

Em VHDL, uma descrição da implementação interna de uma entidade é chamada de corpo de arquitetura
da entidade. Pode haver vários corpos de arquitetura diferentes de uma interface para uma entidade,
correspondendo a implementações alternativas que executam a mesma função. Podemos escrever um corpo
de arquitetura comportamental de uma entidade, que descreve a função de forma abstrata. Esse corpo de
arquitetura inclui apenas instruções de processo, que são coleções de ações a serem executadas em
sequência.
Essas ações são chamadas de instruções sequenciais e são muito parecidas com os tipos de instruções
que vemos em uma linguagem de programação convencional. Os tipos de ações que podem ser executadas
incluem avaliar expressões, atribuir valores a variáveis, execução condicional, execução repetida e chamadas
de subprogramas. Além disso, há uma instrução sequencial exclusiva das linguagens de modelagem de
hardware, a instrução de atribuição de sinal . Isso é semelhante à atribuição de variável, exceto que faz com
que o valor em um sinal seja atualizado em algum momento futuro.

Para ilustrar essas ideias, vejamos um corpo de arquitetura comportamental para a entidade reg4 ,
mostrado na Figura 2-3. Nesse corpo de arquitetura, a parte após a primeira palavra-chave begin inclui uma
instrução de processo, que descreve como o registrador se comporta. Ele começa com o nome do processo,
armazenamento e termina com as palavras-chave end process.

FIGURA 2-3

comportamento da arquitetura de reg4 é


iniciar armazenamento: processo é

variável stored_d0, stored_d1,


stored_d2, stored_d3: bit; começar esperar até clk = '1'; se en = '1' então

armazenado_d0 := d0;
armazenado_d1 := d1;
armazenado_d2 := d2;
armazenado_d3 := d3;
fim se; q0 <= armazenado_d0
após 5 ns; q1 <= armazenado_d1 após
5 ns; q2 <= armazenado_d2 após 5 ns;
q3 <= armazenado_d3 após 5 ns;
armazenamento final do processo ;

comportamento final da arquitetura ;

Um corpo de arquitetura comportamental da entidade reg4 .

A instrução de processo define uma sequência de ações que devem ocorrer quando o sistema é
simulado. Essas ações controlam como os valores nas portas da entidade mudam ao longo do tempo; ou
seja, eles controlam o comportamento da entidade. Este processo pode modificar os valores das portas da
entidade usando instruções de atribuição de sinal.
A forma como este processo funciona é a seguinte. Quando a simulação é iniciada, os valores do sinal
são ajustados para '0' e o processo é ativado. As variáveis do processo (listadas
Machine Translated by Google

6 Conceitos fundamentais

após a variável de palavra-chave) são inicializados com '0', então as instruções são executadas em
ordem. A primeira instrução é uma instrução de espera que faz com que o processo seja suspenso.
Enquanto o processo está suspenso, ele é sensível ao sinal clk . Quando clk muda o valor para '1', o
processo é retomado.
A próxima instrução é uma condição que testa se o sinal en é '1'. Se for, as instruções entre as
palavras-chave then e end if são executadas, atualizando as variáveis do processo usando os valores
dos sinais de entrada. Após a instrução condicional if, existem quatro instruções de atribuição de sinal
que fazem com que os sinais de saída sejam atualizados 5 ns depois.

Quando o processo chega ao final da lista de comandos, eles são executados novamente, a
partir da palavra-chave begin, e o ciclo se repete. Observe que enquanto o processo está suspenso,
os valores nas variáveis do processo não são perdidos. É assim que o processo pode representar o
estado de um sistema.

Elementos da Estrutura

Um corpo de arquitetura que é composto apenas de subsistemas interconectados é chamado de corpo


de arquitetura estrutural . A Figura 2-4 mostra como a entidade reg4 pode ser composta por D-flipflops.
Se formos descrever isso em VHDL, precisaremos de declarações de entidade e corpos de arquitetura
para os subsistemas, mostrados na Figura 2-5.
A Figura 2-6 é uma declaração do corpo da arquitetura VHDL que descreve a estrutura mostrada
na Figura 2-4. A declaração do sinal, antes do início da palavra-chave, define os sinais internos da
arquitetura. Neste exemplo, o sinal int_clk é declarado como portador de um valor de bit ('0' ou '1').
Em geral, os sinais VHDL podem ser declarados como portadores de valores arbitrariamente
complexos. Dentro do corpo da arquitetura, as portas da entidade também são tratadas como sinais.

Na segunda parte do corpo da arquitetura, são criadas várias instâncias de componentes ,


representando os subsistemas dos quais a entidade reg4 é composta. Cada instância do componente
é uma cópia da entidade que representa o subsistema, usando o corpo da arquitetura básica
correspondente. (O nome trabalho refere-se à biblioteca de trabalho atual, na qual se supõe que todas
as descrições de entidade e corpo de arquitetura sejam mantidas.)

O mapa de portas especifica a conexão das portas de cada instância do componente aos sinais
dentro do corpo da arquitetura envolvente. Por exemplo, bit0, uma instância da entidade d_ff , tem sua
porta d conectada ao sinal d0, sua porta clk conectada ao sinal int_clk e sua porta q conectada ao
sinal q0.

Bancadas de teste

Muitas vezes, testamos um modelo VHDL usando um modelo envolvente chamado banco de testes.
O nome vem da analogia com uma bancada de teste de hardware real, na qual um dispositivo em
teste é estimulado com geradores de sinal e observado com sondas de sinal. Uma bancada de testes
VHDL consiste em um corpo de arquitetura contendo uma instância do componente a ser testado e
processos que geram sequências de valores nos sinais conectados à instância do componente. O
corpo da arquitetura também pode conter processos que testam se a instância do componente produz
os valores esperados em seus sinais de saída. Alternativamente, podemos usar as facilidades de
monitoramento de um simulador para observar as saídas.
Machine Translated by Google

Conceitos de modelagem VHDL 7

FIGURA 2-4

bit0

d_ff
d0 q0
d q
clique

bit1

d_ff
d1 1º trimestre

d q
clique

bit2

d_ff
d2 q2
d q
clique

bit3

d_ff
d3 3º trimestre

d q
clique
portão

e2
dentro
int_clk
uma
S
clique
b

Uma composição estrutural da entidade reg4 .

FIGURA 2-5

entidade d_ff é
porta ( d, clk : bit de entrada ; q : bit de
saída ); fim d_ff; arquitetura básica de d_ff é
começar ff_behavior : processo é começar
esperar até clk = '1'; q <= d após 2 ns; fim
do processo ff_comportamento;
arquitetura final básica;

––––––––––––––––––––––––––––––––––––––––––––––––––– ––

entidade and2 é
a porta (a, b: bit de entrada ; y: bit de
saída ); fim e2;
Machine Translated by Google

8 Conceitos fundamentais

arquitetura básica de and2 é begin


and2_behavior : processo é begin
y <= aeb após 2 ns; espere em a,
b; processo final
e2_comportamento;
arquitetura final básica;

Declarações de entidade e corpos de arquitetura para D-flipflop e duas entradas e porta.

FIGURA 2-6

A estrutura de arquitetura do reg4 é


sinal int_clk : bit; begin
bit0 : entidade work.d_ff(basic)

mapa de portas (d0, int_clk, q0);


bit1 : entidade trabalho.d_ff(básico)
mapa de portas (d1, int_clk, q1);
bit2 : entidade trabalho.d_ff(básico)
mapa de portas (d2, int_clk, q2);
bit3 : entidade trabalho.d_ff(básico)
mapa de portas (d3, int_clk,
q3); gate : entidade work.and2(basic)
mapa de portas (en, clk, int_clk);
estrutura de arquitetura final ;

Um corpo de arquitetura estrutural VHDL da entidade reg4 .

Um modelo de bancada de teste para a implementação comportamental do registro reg4


é mostrado na Figura 2-7. A declaração da entidade não possui lista de portas, pois a bancada
de teste é totalmente independente. O corpo da arquitetura contém sinais que são conectados
às portas de entrada e saída da instância do componente dut, o dispositivo em teste. O
estímulo rotulado de processo fornece uma sequência de valores de teste nos sinais de
entrada executando instruções de atribuição de sinal, intercaladas com instruções de espera.
Podemos usar um simulador para observar os valores nos sinais q0 a q3 para verificar se o
registro funciona corretamente. Quando todos os valores de estímulo forem aplicados, o
processo de estímulo aguarda indefinidamente, completando assim a simulação.

FIGURA 2-7

entidade test_bench é
entidade final test_bench;
arquitetura test_reg4 do test_bench é
sinal d0, d1, d2, d3, en, clk, q0, q1, q2, q3: bit; começar
Machine Translated by Google

Conceitos de modelagem VHDL 9

dut: entidade work.reg4(behav)


mapa de portas (d0, d1, d2, d3, en, clk, q0, q1, q2, q3);
estímulo : o processo
começa d0 <= '1'; d1 <=
'1'; d2 <= '1'; d3 <= '1'; pt <= '0'; cl <= '0';
aguarde 10 ns; pt <= '1'; aguarde 10 ns; clk =
'1', '0' após 10 ns; aguarde 20 ns; d0 <= '0'; d1
<= '0'; d2 <= '0'; d3 <= '0'; pt <= '0'; aguarde 10
ns; clk <= '1', '0' após 10 ns; aguarde 20 ns;

esperar; estímulo do
processo final ; final da arquitetura test_reg4;

Um banco de teste VHDL para o modelo de registro reg4 .

Análise, Elaboração e Execução


Uma das principais razões para escrever um modelo de um sistema é permitir-nos simulá-lo. Isso envolve
três etapas: análise, elaboração e execução. Análise e elaboração também são necessárias na preparação
para outros usos do modelo, como síntese lógica.

Na primeira etapa, a análise, a descrição VHDL de um sistema é verificada quanto a vários tipos de
erros. Como a maioria das linguagens de programação, o VHDL tem sintaxe e semântica rigidamente
definidas. A sintaxe é o conjunto de regras gramaticais que governam como um modelo é escrito. As regras
de semântica governam o significado de um programa. Por exemplo, faz sentido realizar uma operação de
adição em dois números, mas não em dois números pro.
cessões.

Durante a fase de análise, a descrição VHDL é examinada e os erros sintáticos e semânticos estáticos
são localizados. Todo o modelo de um sistema não precisa ser analisado de uma só vez. Em vez disso, é
possível analisar unidades de projeto, como declarações de entidade e corpo de arquitetura, separadamente.
Se o analisador não encontrar erros em uma unidade de projeto, ele cria uma representação intermediária
da unidade e a armazena em uma biblioteca. O mecanismo exato varia entre as ferramentas VHDL.

A segunda etapa na simulação de um modelo, a elaboração, é o ato de trabalhar através da hierarquia


do projeto e criar todos os objetos definidos nas declarações. O produto final da elaboração do projeto é uma
coleção de sinais e processos, com cada processo possivelmente contendo variáveis. Um modelo deve ser
redutível a uma coleção de sinais e processos para simulá-lo.

A terceira etapa da simulação é a execução do modelo. A passagem do tempo é simulada em etapas


discretas, dependendo de quando os eventos ocorrem. Daí o termo simulação de eventos discretos é usado.
Em algum momento de simulação, um processo pode ser estimulado alterando o valor de um sinal ao qual é
sensível. O processo é retomado e pode agendar novos valores a serem dados aos sinais em algum momento
simulado posterior. Isso é chamado de agendamento de uma transação nesse sinal. Se o novo valor for
diferente do
Machine Translated by Google

10 Conceitos fundamentais

valor anterior no sinal, ocorre um evento e outros processos sensíveis ao sinal podem ser retomados.

A simulação começa com uma fase de inicialização, seguida pela execução repetitiva de um ciclo de
simulação. Durante a fase de inicialização, cada sinal recebe um valor inicial, dependendo de seu tipo. O
tempo de simulação é definido como zero, então cada processo
instância é ativada e suas instruções sequenciais executadas. Normalmente, um processo
incluir uma instrução de atribuição de sinal para agendar uma transação em um sinal em algum
tempo de simulação posterior. A execução de um processo continua até atingir um estado de espera, que
faz com que o processo seja suspenso.
Durante o ciclo de simulação, o tempo de simulação é avançado primeiro para a próxima vez
em que uma transação em um sinal foi agendada. Em segundo lugar, todas as transações
programadas para esse horário são realizadas. Isso pode fazer com que alguns eventos ocorram em alguns
sinais. Terceiro, todos os processos sensíveis a esses eventos são retomados e
permissão para continuar até chegar a uma instrução de espera e suspender. Novamente, os processos
geralmente executam atribuições de sinais para agendar outras transações em sinais.
Quando todos os processos estiverem novamente suspensos, o ciclo de simulação é repetido. Quando
a simulação chega ao estágio em que não há mais transações agendadas,
pára, uma vez que a simulação está completa.
Machine Translated by Google

3
VHDL é como um
Linguagem de programação

3.1 Elementos Lexicais e Sintaxe


Quando aprendemos uma nova linguagem, precisamos aprender a escrever os elementos básicos, como
números e identificadores. Também precisamos aprender a sintaxe, isto é, as regras gramaticais que governam
como formamos construções de linguagem. Descreveremos brevemente os elementos lexicais e nossa notação
para as regras gramaticais e, em seguida, começaremos a introduzir os recursos da linguagem.

O VHDL usa caracteres no conjunto de caracteres ISO 8859 Latin-1 de 8 bits. Isso inclui letras maiúsculas
e minúsculas (incluindo letras com sinais diacríticos, como 'à', 'ä' e assim por diante), dígitos de 0 a 9, pontuação
e outros caracteres especiais.

Comentários

Quando estamos escrevendo um modelo de hardware em VHDL, é importante anotar o código com comentários.
Um modelo VHDL consiste em várias linhas de texto. Um comentário pode ser adicionado a uma linha escrevendo
dois traços juntos, seguidos pelo texto do comentário.
Por exemplo:

… uma linha de descrição VHDL … –– um comentário descritivo

O comentário se estende dos dois traços até o final da linha e pode incluir qualquer texto que desejarmos,
pois não faz parte formalmente do modelo VHDL. O código de um modelo pode incluir linhas em branco e linhas
que contenham apenas comentários, começando com dois traços. Podemos escrever comentários longos em
linhas sucessivas, cada uma começando com dois traços, por exemplo:

–– Os seguintes modelos de
código –– a seção de controle do
sistema … algum código VHDL …

Identificadores

Os identificadores são usados para nomear itens em um modelo VHDL. Um identificador

11
Machine Translated by Google

12 VHDL é como uma linguagem de programação


pode conter apenas letras alfabéticas ('A' a 'Z' e 'a' a 'z'), dígitos decimais ('0' a '9') e o caractere
sublinhado ('_');

• deve começar com uma letra alfabética;



não pode terminar com um caractere de sublinhado; e

não pode incluir dois caracteres sublinhados sucessivos.

Caso de letras não é significativo. Alguns exemplos de identificadores básicos válidos são

Um contador X0 Next_Value generate_read_cycle

Alguns exemplos de identificadores básicos inválidos são

last@value –– contém um caractere ilegal para um identificador 5bit_counter –– começa com


um caractere não alfabético
_A0 –– começa com um sublinhado
A0_ –– termina com um sublinhado clock__pulse –– dois
sublinhados sucessivos

Palavras reservadas

Alguns identificadores, chamados palavras reservadas ou palavras-chave, são reservados para uso especial
em VHDL, portanto, não podemos usá-los como identificadores para itens que definimos. A lista completa de
palavras reservadas é mostrada na Figura 3-1.

FIGURA 3-1

abdômen desconectar loop procedimento sla


acesso para baixo literal de adiado de sll
afinal else ligação da porta de pacote
alias e elsif biblioteca
arquitetura saída de rótulos processo subtipo sra srl

da mod
protegido então
entidade final de mapa puro para
atributo transportar
nanda registro
de
novo
de o tipo
declaração de matriz
arquivo para função próximo intervalo unidades não
iniciar o nem rejeitado afetadas até
gerar
não rem
genérico
nulo relatório usar
grupo
barramento de
guardado de variável
de buffer do corpo do bloco retorno
em
caso ro
se impuro abrir esperar
constante de selecione
em ou quando
configuração do outros o sinal
entrada enquanto com
componente fora compartilhado
inercial é xnor
de gravidade
gratuitamente

Palavras reservadas em VHDL.


Machine Translated by Google

Elementos Lexicais e Sintaxe 13

Números
Existem duas formas de números que podem ser escritos em código VHDL: literais inteiros e
literais reais. Um literal inteiro simplesmente representa um número inteiro e consiste em dígitos
sem ponto decimal. Literais reais, por outro lado, podem representar
números. Eles sempre incluem um ponto decimal, que é precedido por pelo menos um
dígito e seguido por pelo menos um dígito. Alguns exemplos de literais inteiros decimais são

23 0 146

Alguns exemplos de literais reais são

23,1 0,0 3,14159

Os literais inteiros e reais também podem usar a notação exponencial, na qual o número é seguido
pela letra 'E' ou 'e' e um valor exponencial. Isso indica um poder
de 10 pelo qual o número é multiplicado. Para literais inteiros, o expoente não deve
ser negativo, enquanto para literais reais, pode ser positivo ou negativo. Alguns exemplos de literais
inteiros usando notação exponencial são

46E5 1E+12 19e00

Alguns exemplos de literais reais usando notação exponencial são

1.234E09 98.6E + 21 34.0e – 08

Personagens

Um literal de caractere pode ser escrito em código VHDL colocando-o entre aspas simples
marcas. Qualquer um dos caracteres imprimíveis no conjunto de caracteres padrão (incluindo um espaço
caractere) pode ser escrito dessa maneira. Alguns exemplos são

'UMA'
-- Letra maiúscula
'com' -- letra minúscula
',' –– a vírgula do caractere de pontuação
'''
–– aspas simples do caractere de pontuação
''
–– o espaço do caractere separador

Cordas
Um literal de string representa uma sequência de caracteres e é escrito envolvendo o
caracteres entre aspas duplas. A string pode incluir qualquer número de caracteres (incluindo zero),
mas deve caber inteiramente em uma linha. Alguns exemplos são

"Uma linha"
"Podemos incluir qualquer caractere de impressão (por exemplo, &%@^*) em uma string!!"
"00001111ZZZZ"
""
–– cadeia vazia
Machine Translated by Google

14 VHDL é como uma linguagem de programação

Se precisarmos incluir um caractere de aspas duplas em uma string, escrevemos dois caracteres
de aspas duplas juntos. O par é interpretado como apenas um caractere na string. Por exemplo:

"Uma string em uma string: ""Uma string"". "

Se precisarmos escrever uma string maior do que cabe em uma linha, podemos usar o
operador de concatenação (“&”) para unir duas substrings. Por exemplo:

"Se uma string não couber em uma


linha, " & "podemos dividi-la em partes em linhas separadas."

Strings de bits

VHDL inclui valores que representam bits (dígitos binários), que podem ser '0' ou '1'.
Um literal de cadeia de bits representa uma sequência desses valores de bits. Ele é representado por
uma sequência de dígitos, entre aspas duplas e precedido por um caractere que especifica a base
dos dígitos. O especificador base pode ser um dos seguintes:

• B para binário,
• O para octal (base 8) e

• X para hexadecimal (base 16).

Por exemplo, alguns literais bitstring especificados em binário são

B"0100011"B"10"b"1111_0010_0001"B""

Observe que podemos incluir caracteres sublinhados em literais de cadeia de bits para tornar o
literal mais legível. O especificador base pode estar em maiúsculas ou minúsculas. O último dos
exemplos acima denota uma cadeia de bits vazia.
Se o especificador de base for octal, os dígitos de '0' a '7' podem ser usados. Cada representante de dígito
reenvia exatamente três bits na sequência. Alguns exemplos são

O"372" –– equivalente a B"011_111_010" o"00"


–– equivalente a B"000_000"

Se o especificador de base for hexadecimal, os dígitos '0' a '9' e 'A' a 'F' ou 'a' a 'f' (representando
10 a 15) podem ser usados. Em hexadecimal, cada dígito representa exatamente quatro bits. Alguns
exemplos são

X"FA" –– equivalente a B"1111_1010" x"0d"


–– equivalente a B"0000_1101"

Descrições de sintaxe

Neste tutorial, descrevemos regras de sintaxe usando uma notação baseada no Extended Backus-
Naur Form (EBNF). A ideia por trás do EBNF é dividir a linguagem em categorias sintáticas. Para
cada categoria sintática escrevemos uma regra que descreve como construir uma cláusula VHDL
dessa categoria combinando elementos lexicais e cláusulas de outras categorias. Escrevemos uma
regra com a categoria sintática que estamos definindo no
Machine Translated by Google

Elementos Lexicais e Sintaxe 15

à esquerda de um sinal “ÿ” (lido como “é definido para ser”) e um padrão à direita. O tipo mais simples
de padrão é uma coleção de itens em sequência, por exemplo:

atribuição_variável ÿ destino := expressão ;

Esta regra indica que uma cláusula VHDL na categoria “variable_assignment” é definida como
uma cláusula na categoria “target”, seguida do símbolo “:=”, seguida de uma cláusula na categoria
“expression”, seguida do símbolo “;”.
O próximo tipo de regra a ser considerado é aquele que permite um componente opcional em
uma cláusula. Indicamos a parte opcional colocando-a entre os símbolos “[” e “]”. Por exemplo:

função_chamada ÿ nome [ ( lista_de_associação ) ]

Isso indica que uma chamada de função consiste em um nome que pode ser seguido por uma lista
de associação entre parênteses. Observe o uso dos símbolos de contorno para escrever o padrão na
regra, em oposição aos símbolos sólidos normais que são elementos lexicais de VHDL.

Em muitas regras, precisamos especificar que uma cláusula é opcional, mas se estiver presente,
pode ser repetida quantas vezes forem necessárias. Por exemplo, nesta regra:

process_statement ÿ
processo é
{ process_declarative_item }
begin { sequencia_statement } end
process ;

as chaves especificam que um processo pode incluir zero ou mais itens declarativos de processo e
zero ou mais instruções sequenciais. Um caso que surge com frequência nas regras de VHDL é um
padrão que consiste em alguma categoria seguida por zero ou mais repetições dessa categoria. Nesse
caso, usamos pontos dentro das chaves para representar a categoria repetida, em vez de escrevê-la
novamente por completo. Por exemplo, a regra

case_statement ÿ
expressão case é
case_statement_alternative
{ … } end case ;

indica que uma instrução case deve conter pelo menos uma alternativa de instrução case, mas pode
conter um número arbitrário de alternativas de instrução case adicionais conforme necessário. Se
houver uma sequência de categorias e símbolos precedendo as chaves, os pontos representam
apenas o último elemento da sequência. Assim, no exemplo acima, os pontos representam apenas a
alternativa de instrução case, não a sequência “expressão case is case_statement_alternative ”.

Também usamos a notação de pontos onde é necessária uma lista de uma ou mais repetições
de uma cláusula, mas é necessário algum símbolo delimitador entre as repetições. Por exemplo, a
regra
Machine Translated by Google

16 VHDL é como uma linguagem de programação

lista_identificadora ÿ identificador { , … }

especifica que uma lista de identificadores consiste em um ou mais identificadores e que, se houver mais
de um, eles são separados por símbolos de vírgula. Observe que os pontos sempre representam uma
repetição da categoria imediatamente anterior ao símbolo da chave esquerda. Assim, na regra acima, é o
identificador que se repete, não a vírgula.
Muitas regras de sintaxe permitem que uma categoria seja composta por uma de várias
nativos, especificados usando o símbolo “I” . Por exemplo, a regra

mode ÿ in I out I inout

especifica que a categoria “modo” pode ser formada a partir de uma cláusula composta por uma das
palavras reservadas escolhidas entre as alternativas listadas.
A notação final que usamos em nossas regras de sintaxe é o agrupamento entre parênteses, usando
os símbolos “(“ e “)”. Estes servem simplesmente para agrupar parte de um padrão, para que possamos
evitar qualquer ambiguidade que possa surgir de outra forma. Por exemplo, a inclusão de parênteses na
regra

termo ÿ fator { ( * I / I mod I rem ) fator }

deixa claro que um fator pode ser seguido por um dos símbolos do operador e, em seguida, outro fator.

Esta notação EBNF é suficiente para descrever a gramática completa do VHDL.


No entanto, muitas vezes há outras restrições em uma descrição VHDL que se relacionam com o significado
dos construtos usados. Para expressar tais restrições, muitas regras incluem informações adicionais
relacionadas ao significado de um recurso de linguagem. Por exemplo, a regra mostrada acima descrevendo
como uma chamada de função é formada é aumentada assim:

function_call ÿ function_name [ ( parameter_association_list ) ]

O prefixo em itálico em uma categoria sintática no padrão simplesmente fornece informações semânticas.
Essa regra indica que o nome não pode ser qualquer nome, mas deve ser o nome de uma função. Da
mesma forma, a lista de associações deve descrever os parâmetros fornecidos à função.

Neste tutorial, apresentaremos cada novo recurso do VHDL descrevendo sua taxa de sintaxe usando
regras EBNF e, em seguida, descreveremos o significado e o uso do recurso por meio de exemplos. Em
muitos casos, começaremos com uma versão simplificada da sintaxe para facilitar o aprendizado da
descrição e retornaremos aos detalhes completos em uma seção posterior.

3.2 Constantes e Variáveis


Constantes e variáveis são objetos nos quais os dados podem ser armazenados para uso em um modelo.
A diferença entre eles é que o valor de uma constante não pode ser alterado após sua criação, enquanto o
valor de uma variável pode ser alterado quantas vezes forem necessárias usando instruções de atribuição
de variável.
Machine Translated by Google

Constantes e Variáveis 17

Ambas as constantes e variáveis precisam ser declaradas antes que possam ser usadas em um
modelo. Uma declaração simplesmente introduz o nome do objeto, define seu tipo e pode dar a ele um
valor inicial. A regra de sintaxe para uma declaração constante é

declaração_constante ÿ
identificador constante { , … } : subtype_indication := expressão ;

Aqui estão alguns exemplos de declarações constantes:

constante number_of_bytes : integer := 4;


constante number_of_bits : integer := 8 * number_of_bytes;
constante e : real := 2,718281828; constante prop_delay :
tempo := 3 ns; constante size_limit, count_limit : integer := 255;

A forma de uma declaração de variável é semelhante a uma declaração de constante. A regra de


sintaxe é

declaração_variável ÿ
identificador de variável { , … } : subtype_indication [ := expressão ] ;

A expressão de inicialização é opcional; se o omitirmos, o valor inicial padrão somado pela variável
quando ela é criada depende do tipo. Para tipos escalares, o valor inicial padrão é o valor mais à
esquerda do tipo. Por exemplo, para inteiros, é o menor inteiro representável. Alguns exemplos de
declarações de variáveis são

índice variável : inteiro := 0;


variável soma, média, maior : real;
variável start, finish : time := 0 ns;

Declarações de constantes e variáveis podem aparecer em vários lugares em um modelo VHDL,


inclusive nas partes de declaração de processos. Nesse caso, o objeto declarado pode ser usado
apenas dentro do processo. Uma restrição sobre onde uma declaração de variável pode ocorrer é que
ela não pode ser colocada de forma que a variável seja acessível a mais de um processo. Isso é para
evitar os efeitos estranhos que poderiam ocorrer se os processos modificassem a variável em ordem
indeterminada.
Uma vez que uma variável foi declarada, seu valor pode ser modificado por um comando de atribuição.
A sintaxe de uma instrução de atribuição de variável é dada pela regra

variável_atribuição_instrução ÿ nome := expressão ;

O nome em uma instrução de atribuição de variável identifica a variável a ser alterada e a expressão
é avaliada para produzir o novo valor. O tipo deste valor deve corresponder ao tipo da variável. Aqui
estão alguns exemplos de atribuição
declarações:

contador_programa :=
0; índice := índice + 1;

A primeira atribuição define o valor da variável program_counter como zero, substituindo qualquer valor
anterior. O segundo exemplo incrementa o valor de index em um.
Machine Translated by Google

18 VHDL é como uma linguagem de programação

3.3 Tipos Escalares


Um tipo escalar é aquele cujos valores são indivisíveis. Nesta seção, revisamos VHDLs
tipos escalares predefinidos. Também mostraremos como definir novos tipos de enumeração.

Subtipos
Em muitos modelos, queremos declarar objetos que devem assumir apenas um intervalo restrito
de valores. Fazemos isso primeiro declarando um subtipo, que define um conjunto restrito de valores
de um tipo base. As regras de sintaxe simplificadas para uma declaração de subtipo são

subtype_declaration ÿ identificador de subtipo é subtype_indication ;


subtipo_indicação ÿ
type_mark range simple_expression ( até eu downto ) simple_expression

Veremos outras formas de indicações de subtipo mais tarde. A declaração do subtipo


define o identificador como um subtipo do tipo base especificado pela marca de tipo, com
a restrição de intervalo que restringe os valores para o subtipo.

Tipos inteiros
Em VHDL, os tipos inteiros têm valores que são números inteiros. O tipo predefinido
integer inclui todos os números inteiros representáveis em um computador host específico.
O padrão da linguagem exige que o tipo integer inclua pelo menos os números –
2.147.483.647 a +2.147.483.647 (–231 + 1 a +231 – 1), mas implementações VHDL
pode estender o alcance.
Existem também dois subtipos inteiros predefinidos

natural, contendo os inteiros de 0 ao maior inteiro, e

positivo, contendo os inteiros de 1 ao maior inteiro.

Onde a lógica de um projeto indica que um número não deve ser negativo, é bom
style para usar um desses subtipos em vez do inteiro do tipo base. Desta forma, nós
pode detectar quaisquer erros de projeto que causem incorretamente a produção de números negativos.
As operações que podem ser executadas em valores de tipos inteiros incluem o fa
operações aritméticas miliares:

+ adição, ou identidade unária

– subtração ou negação unária

* multiplicação

divisão (com truncamento)


/
mod modulo (mesmo sinal do operando direito)

rem resto (mesmo sinal do operando esquerdo)


valor absoluto
abdômen

** exponenciação (o operando direito deve ser não negativo)


Machine Translated by Google

Tipos escalares 19

EXEMPLO

Aqui está uma declaração que define um subtipo de inteiro:

subtipo small_int é o intervalo de inteiros –128 a 127;

Os valores de small_int são restritos a estar dentro do intervalo –128 a 127. Se declararmos algumas
variáveis:

desvio variável : small_int; ajuste


de variável : inteiro;

podemos usá-los nos cálculos:

desvio := desvio + ajuste;

Tipos de ponto flutuante

Os tipos de ponto flutuante em VHDL são usados para representar números reais com uma parte de
mantissa e uma parte de expoente. O tipo de ponto flutuante predefinido real inclui o maior intervalo permitido
pela representação de ponto flutuante do host. Na maioria das implementações, este será o intervalo da
representação de precisão dupla IEEE de 64 bits.
As operações que podem ser realizadas em valores de ponto flutuante incluem operações aritméticas
de adição e identidade unária (“+”), subtração e negação unária (“– ”), multiplicação (“*”), divisão (“/”), valor
absoluto (abs) e exponenciação (“**”).
Para os operadores binários (aqueles que recebem dois operandos), os operandos devem ser do mesmo
tipo. A exceção é que o operando direito do operador de exponenciação deve ser um inteiro.

Tempo

O VHDL possui um tipo predefinido chamado time que é usado para representar tempos e atrasos de
simulação. Podemos escrever um valor de tempo como um literal numérico seguido por uma unidade de
tempo. Por exemplo:

5 ns 22 us 471,3 ms

Observe que devemos incluir um espaço antes do nome da unidade. Os nomes de unidade válidos são

fs ps ns us ms seg min h

O tipo time inclui valores positivos e negativos. VHDL também tem uma rede
subtipo multado de tempo, delay_length, que inclui apenas valores não negativos.
Muitos dos operadores aritméticos podem ser aplicados a valores de tempo , mas com algumas
restrições. Os operadores de adição, subtração, identidade e negação podem ser aplicados para produzir
resultados do tipo tempo. Um valor de tempo pode ser multiplicado ou dividido por um valor inteiro ou real
para gerar um valor de tempo , e dois valores de tempo podem ser divididos para gerar um número inteiro.
Por exemplo:

18 ns / 2,0 = 9 ns, 33 ns / 22 ps = 1500


Machine Translated by Google

20 VHDL é como uma linguagem de programação

Finalmente, o operador abs pode ser aplicado a um valor de tempo , por exemplo:

abdominais 2 ps = 2 ps, abdominais (–2 ps) = 2 ps

Tipos de enumeração

Muitas vezes, ao escrever modelos de hardware em um nível abstrato, é útil usar um conjunto
de nomes para os valores codificados de alguns sinais, em vez de se comprometer com um nível de bit
codificação imediatamente. Os tipos de enumeração VHDL nos permitem fazer isso. Para definir um tipo
de enumeração, precisamos usar uma declaração de tipo. A regra de sintaxe é

type_declaration ÿ identificador de tipo é type_definition ;

Uma declaração de tipo nos permite introduzir um novo tipo, distinto de outros tipos. Um
forma de definição de tipo é uma definição de tipo de enumeração. Veremos outras formas
mais tarde. A regra de sintaxe para definições de tipo de enumeração é

enumeration_type_definition ÿ ( ( identificador I character_literal ) { , … } )

Isso simplesmente lista todos os valores no tipo. Cada valor pode ser um iden
tifier ou um literal de caractere. Um exemplo que inclui apenas identificadores é

tipo alu_function é (desabilitar, passar, adicionar, subtrair, multiplicar, dividir);

Um exemplo incluindo apenas literais de caracteres é

tipo octal_digit é ('0', '1', '2', '3', '4', '5', '6', '7');

Dadas as duas declarações de tipo acima, podemos declarar variáveis:

variável alu_op : alu_function;


variável last_digit : octal_digit := '0';

e fazer atribuições para eles:

alu_op := subtrair;
last_digit := '7';

Personagens

O caractere de tipo de enumeração predefinido inclui todos os caracteres no ISO


8859 Latin-1 conjunto de caracteres de 8 bits. A definição de tipo é mostrada na Figura 3-2. Ele contém
uma mistura de identificadores (para caracteres de controle) e literais de caracteres (para caracteres
gráficos). O caractere na posição 160 é um caractere de espaço sem quebra, distinto
do caractere de espaço comum e o caractere na posição 173 é um hífen suave.

FIGURA 3-2

tipo de caractere é (
nul, soh, bs, ht, dle, stx, etx, eot, eq, ack, bel,
dc1, lf, vt, ff, cr, então, e,
dc2, dc3 dc4, nak, sin, etb,
Machine Translated by Google

Tipos escalares 21

pode, sub,
em, rsp, '$', '!', esc,
'&', ')', ',', '.', '1', '4', '6', '9',
fsp,'<', '>', 'A',gsp,
'D', 'F', 'I', 'L', usp,
' ', '(', 'd', 'f', 'i', 'l', 'n',
'"','q',
'*', 't', 'v', 'y',
'#','|', '~', c129,
'N',c130,
'Q', 'T',
c131,
'V',
'%',
c132,
'Y', '\', c133,
'^ ', 'a', ''',
'0', c134, c135, '2', ':', '+', '–', '/',
'8', '3', ';', '5', '=', '7',
'@', '?',
'B', 'C', 'E', 'G',
'H', 'J', 'K', 'M', 'O',
'P', 'R', 'S', 'U', 'DENTRO',

'X', 'Z', '[', ']', '_',


'`', 'b', 'c', 'e', 'g',
'h', 'j', 'k', 'm', 'o',
'p', 'r', 's', 'u', 'dentro',

'x', 'z', '{', '}', do,


c128,
c136, c137, c138, c139, c140, c141, c142, c143,
c144, c145, c146, c147, c148, c149, c150, c151,
c152, c153, c154, c155, c156, c157, c158, c159,
'', '¨', '¢', '®', '±', '´', '£',
'¡', '¤', '¦', '©', '¬', '¥',É ',' Ì ',' Î ',' Ô
'¶', '¹', '¼', '¾', 'Á ',' Ä ',' Æ ',' '§',
'°', '¸', ',' Ñ ',' Ö ',' Ù 'ª',
',' Ü ',' Þ ',' á ','«',
'ä', 'æ', 'é', 'ì', 'î', 'ñ', 'ô', 'ö','-','ù', 'ü', 'þ', '¯',
'À', 'È' , '²', '³', 'µ', '·',
'Ð', 'Ø', 'º', '»', '½', '¿',
'à', 'è', 'Â', 'Ã', 'Å', 'Ç',
'ð', 'ø', 'Ê', 'Ë', 'Í', 'EU',

'Ò', 'Ó', 'Õ', '×',


'Ú', 'Û', 'Ý', 'ß',
'â', 'ã', 'å', 'ç',
'ê', 'ë', 'í', 'eu',

'ò', 'ó', 'õ', '÷',


'ú', 'û', 'ý', 'ÿ');

A definição do caractere de tipo de enumeração predefinido .

Para ilustrar o uso do tipo de caractere , declaramos as variáveis da seguinte forma:

variável cmd_char, terminador : caractere;

e depois faça as tarefas

cmd_char := 'P';
terminador := cr;

Booleanos
O tipo booleano predefinido é definido como

tipo boolean é (falso, verdadeiro);

Este tipo é usado para representar valores de condição, que podem controlar a execução de um modelo
comportamental. Existem vários operadores que podemos aplicar a valores de tipos diferentes para produzir
valores booleanos, ou seja, os operadores relacionais e lógicos. o
Machine Translated by Google

22 VHDL é como uma linguagem de programação

operadores relacionais igualdade (“=”) e desigualdade (“/=”) podem ser aplicados a operandos de
qualquer tipo, desde que ambos sejam do mesmo tipo. Por exemplo, as expressões

123 = 123, 'A' = 'A', 7 ns = 7 ns

todos produzem o valor true, e as expressões

123 = 456, 'A' = 'z', 7 ns = 2 us

dê o valor falso.
Os operadores relacionais que testam a ordenação são menor que (“<”), menor que ou igual a
(“<=”), maior que (“>”) e maior que ou igual a operadores (“>=”) .
Eles só podem ser aplicados a valores de tipos ordenados, incluindo todos os tipos escalares
descritos neste capítulo.
Os operadores lógicos and, or, nand, nor, xor, xnor e não recebem operandos que devem
ser valores booleanos, e produzem resultados booleanos.

Bits

Como o VHDL é usado para modelar sistemas digitais, é útil ter um tipo de dados para representar
os valores dos bits. O bit de tipo de enumeração predefinido atende a esse propósito. Está definido
como

bit de tipo é ('0', '1');

Os operadores lógicos que mencionamos para valores booleanos também podem ser aplicados
a valores do tipo bit e produzem resultados do tipo bit. O valor '0' corresponde a falso e '1' corresponde
a verdadeiro. Assim, por exemplo:

'0' e '1' = '0', '1 ' x ou '1' = '0'

A diferença entre os tipos boolean e bit é que os valores booleanos são usados para modelar
condições abstratas, enquanto os valores de bit são usados para modelar níveis lógicos de hardware.
Assim, '0' representa um nível lógico baixo e '1' representa um nível lógico alto.

Lógica Padrão
O IEEE padronizou um pacote chamado std_logic_1164 que permite modelar sinais digitais levando
em consideração alguns efeitos elétricos. Um dos tipos definidos neste pacote é um tipo de
enumeração chamado std_ulogic, definido como

tipo std_ulogic é ( 'U', –– não inicializado


'X', –– Forçando Desconhecido
'0', –– Forçando zero
'1', –– Forçando um
'Z', –– Alta Impedância
'W', –– Fraco Desconhecido
'L', –– Zero fraco
'H', –– Fraco
'-'); –– Não importa
Machine Translated by Google

Declarações Sequenciais 23

Esse tipo pode ser usado para representar sinais acionados por drivers ativos (força forçada),
drivers resistivos, como pull-ups e pull-downs (força fraca) ou drivers de três estados, incluindo um
estado de alta impedância. Cada tipo de driver pode conduzir um valor “zero”, “um” ou
“desconhecido”. Um valor “desconhecido” é determinado por um modelo quando não é capaz de
determinar se o sinal deve ser “zero” ou “um”. Além desses valores, o valor mais à esquerda no tipo
representa um valor “não inicializado”. Se declararmos sinais do tipo std_ulogic , por padrão eles
assumem 'U' como seu valor inicial. O valor final em std_ulogic é um valor “don't care”. Isso às
vezes é usado por ferramentas de síntese lógica e também pode ser usado ao definir vetores de
teste, para denotar que o valor de um sinal a ser comparado com um vetor de teste não é importante.

Mesmo que o tipo std_ulogic e os outros tipos definidos no pacote std_logic_1164 não sejam
realmente construídos na linguagem VHDL, podemos escrever modelos como se fossem, com um
pouco de preparação. Por enquanto, descrevemos algumas “mágicas” para incluir no início de um
modelo que usa o pacote; explicamos os detalhes mais tarde.
Se incluirmos a linha

biblioteca ieee; use ieee.std_logic_1164.all;

precedendo cada entidade ou corpo de arquitetura que usa o pacote, podemos escrever modelos
como se os tipos fossem construídos na linguagem.
Com esta preparação em mãos, podemos agora criar constantes, variáveis e sinais do
tipo std_ulogic. Além de atribuir valores do tipo, também podemos usar os operadores lógicos
e, ou, não e assim por diante. Cada um deles opera em valores std_ulogic e retorna um
resultado std_ulogic de 'U', 'X', '0' ou '1'.

3.4 Declarações Sequenciais


Nesta seção, veremos como os dados podem ser manipulados dentro de processos usando
instruções sequenciais, assim chamadas porque são executadas em sequência. Já vimos uma das
instruções seqüenciais básicas, a instrução de atribuição de variável.
As declarações que examinamos nesta seção lidam com ações de controle dentro de um modelo;
por isso, são frequentemente chamadas de estruturas de controle. Eles permitem a seleção entre
cursos alternativos de ação, bem como a repetição de ações.

Se Declarações

Em muitos modelos, o comportamento depende de um conjunto de condições que podem ou não


ser verdadeiras durante o curso da simulação. Podemos usar uma instrução if para expressar esse
comportamento. A regra de sintaxe para uma instrução if é

if_statement ÿ
[ if_label : ]
if boolean_expression then
{ sequencial_statement }
{ elsif boolean_expression then
{ sequencial_statement } }
[ else
Machine Translated by Google

24 VHDL é como uma linguagem de programação

{ sequencial_statement } ]
end if [ if_label ] ;

Um exemplo simples de uma instrução if é

se en = '1' então
valor_armazenado := data_in;
fim se;

A expressão booleana após a palavra-chave if é a condição usada para controlar se a instrução


após a palavra-chave será ou não executada . Se a condição for avaliada como verdadeira, a
instrução será executada. Também podemos especificar ações a serem executadas se a condição
for falsa. Por exemplo:

se sel = 0 então
resultado <= entrada_0; –– executado se sel = 0
senão resultado <= input_1; –– executado se sel /= 0 end
if;

Aqui, como os comentários indicam, a primeira instrução de atribuição de sinal é executada se a


condição for verdadeira, e a segunda instrução de atribuição de sinal é executada se a condição for
falsa.
Podemos construir uma forma mais elaborada de instrução if to para verificar várias condições
diferentes, por exemplo:

se modo = imediato então


operando := immed_operando;
elsif opcode = carregar ou opcode = adicionar ou opcode = subtrair então
operando := operando_memória;
else operando := endereço_operando;
fim se;

Em geral, podemos construir uma instrução if com qualquer número de cláusulas elsif (incluindo
nenhuma), e podemos incluir ou omitir a cláusula else . A execução da instrução if começa pela
avaliação da primeira condição. Se for falsa, as condições sucessivas são avaliadas, na ordem, até
que uma seja considerada verdadeira, caso em que as instruções correspondentes são executadas.
Se nenhuma das condições for verdadeira e incluímos uma cláusula else , as instruções após a
palavra-chave else são executadas.

EXEMPLO

Um termostato de aquecedor pode ser modelado como uma entidade com duas entradas
inteiras, uma que especifica a temperatura desejada e outra que está conectada a um
termômetro, e uma saída booleana que liga e desliga um aquecedor. O termostato liga o
aquecedor se a temperatura medida cair abaixo de dois graus abaixo da temperatura desejada,
e desliga o aquecedor se a temperatura medida subir acima de dois graus acima da temperatura
desejada.
Machine Translated by Google

Declarações Sequenciais 25

A Figura 3-3 mostra os corpos de entidade e arquitetura para o termostato. A declaração


de entidade define as portas de entrada e saída. O processo no corpo da arquitetura inclui as
portas de entrada na lista de sensibilidade após a palavra-chave processo. Esta é uma lista de
sinais aos quais o processo é sensível. Quando qualquer um desses sinais muda de valor, o
processo recomeça e executa as instruções sequenciais. Depois de executar a última instrução,
o processo é suspenso novamente. A instrução if compara a temperatura real com a temperatura
desejada e liga ou desliga o aquecedor conforme necessário.

FIGURA 3-3

o termostato da entidade
é a porta (temper_desejada, temp_atual: em inteiro;
aquecedor_on : out boolean );
termostato da entidade final ;
––––––––––––––––––––––––––––––––––––––––––––––––––– ––

exemplo de arquitetura de termostato é begin


controller : processo (desired_temp,
actual_temp) é begin if atual_temp < desejado_temp – 2 then
heater_on <= true;

elsif atual_temp > desejado_temp + 2 then


aquecedor_on <= false; fim se; controlador de
processo final ;

exemplo de arquitetura final ;

Uma entidade e um corpo de arquitetura para um termostato de aquecedor.

Declarações de Caso

Se tivermos um modelo no qual o comportamento depende do valor de uma única expressão,


podemos usar uma instrução case. As regras de sintaxe são as seguintes:

case_statement ÿ
[ case_label : ]
case expression is
( when columns => { sequencial_statement } )
{ … } end case [ case_label ] ; escolhas ÿ
( expressão_simples I intervalo_discreto I outros ) { |
…}

Por exemplo, suponha que estamos modelando uma unidade aritmética/lógica, com um controle
input, func, declarado como do tipo enumeração:

tipo alu_func é (pass1, pass2, soma, subtrai);


Machine Translated by Google

26 VHDL é como uma linguagem de programação

Poderíamos descrever o comportamento usando uma instrução case:

case func é
quando pass1 =>
result := operando1;
quando pass2 => resultado :=
operando2; quando
adicionar =>
resultado := operando1 + operando2;
quando subtrair => resultado := operando1 –
operando2; caso final;

No início desta instrução case está a expressão seletora, entre as palavras-chave case e is. O valor
desta expressão é usado para selecionar quais declarações devem ser executadas. O corpo da instrução
case consiste em uma série de alternativas. Cada alternativa começa com a palavra-chave quando e é
seguida por uma ou mais opções e uma sequência de instruções. As opções são valores que são
comparados com o valor da expressão seletora. Deve haver exatamente uma escolha para cada valor
possível.
A instrução case encontra a alternativa cujo valor de escolha é igual ao valor da expressão seletora e
executa as instruções nessa alternativa.
Podemos incluir mais de uma escolha em cada alternativa escrevendo as escolhas
separados por “|” símbolo. Por exemplo, se o tipo opcodes for declarado como

tipo opcodes é (nop,


add, subtrair, load, store, jump, jumpsub, branch, halt);

poderíamos escrever uma alternativa incluindo três desses valores como escolhas:

quando carregar | adicionar | subtrair =>


operando := operando_memória;

Se tivermos várias alternativas em uma instrução case e quisermos incluir uma alternativa para lidar
com todos os valores possíveis da expressão seletora não mencionadas nas alternativas anteriores,
podemos usar a escolha especial other . Por exemplo, se a variável opcode for uma variável do tipo
opcodes, declarada acima, podemos escrever

caso opcode é
quando carrega | adicionar | subtrair =>
operando := operando_memória;
quando armazenar | salto | jumpsub | ramo =>
operando := endereço_operando; quando
outros => operando := 0; caso final;

Neste exemplo, se o valor de opcode for diferente das opções listadas na primeira e na segunda
alternativas, a última alternativa será selecionada. Pode haver apenas uma alternativa que use a escolha
de outras e, se estiver incluída, deve ser a última alternativa na instrução case. Uma alternativa que inclui
a escolha dos outros pode não incluir quaisquer outras escolhas.
Machine Translated by Google

Declarações Sequenciais 27

Um ponto importante a ser observado sobre as escolhas em uma instrução case é que todas
elas devem ser escritas usando valores localmente estáticos . Isso significa que os valores das
escolhas devem ser determinados durante a fase de análise do processamento do projeto.

EXEMPLO

Podemos escrever um modelo comportamental de um multiplexador de condição de ramificação


com uma seleção de entrada sel; duas entradas de código de condição cc_z e cc_c; e uma saída tomada.
As entradas e saídas do código de condição são do tipo de lógica padrão IEEE, e a entrada de
seleção é do tipo branch_fn, que assumimos ser declarado em outro lugar como

tipo branch_fn é (br_z, br_nz, br_c, br_nc);

Veremos mais tarde como definimos um tipo para uso em uma declaração de entidade. A
declaração de entidade que define as portas e um corpo de arquitetura comportamental são
mostrados na Figura 3-4. O corpo da arquitetura contém um processo que é sensível às
entradas. Ele faz uso de uma instrução case para selecionar o valor a ser atribuído à saída.

FIGURA 3-4

biblioteca ieee; use ieee.std_logic_1164.all;


entidade cond_mux é porta ( sel : em barnch_fn;
cc_z, cc_c : em std_ulogic; take : out
std_ulogic ); entidade final cond_mux;

––––––––––––––––––––––––––––––––––––––––––––––––––– ––

demo de arquitetura de cond_mux é


begin out_select : processo (sel, cc_z,
cc_c) é begin case sel é quando br_z => pega
<= cc_z; quando br_nz => tomado <= não
cc_z; quando br_c => tomado <= cc_c;
quando br_nc => tomado <= não
cc_c; caso final; finalizar
processo out_select;

final de demonstração de arquitetura ;

Uma entidade e um corpo de arquitetura para um multiplexador de condição brnach.


Machine Translated by Google

28 VHDL é como uma linguagem de programação

Declarações de loop e saída


Frequentemente, precisamos escrever uma sequência de instruções que deve ser executada repetidamente.
Usamos uma instrução de loop para expressar esse comportamento. A regra de sintaxe para um loop simples
que itera indefinidamente é

loop_statement ÿ
[ loop_label : ]
loop
{ sequencial_statement }
fim do loop [ loop_label ] ;

Normalmente precisamos sair do loop quando surge alguma condição. Podemos usar uma instrução exit para
sair de um loop. A regra de sintaxe é

exit_statement ÿ
[ label : ] exit [ loop_label ] [ when boolean_expression ] ;

A forma mais simples de declaração de saída é apenas

saída;

Quando esta instrução é executada, quaisquer instruções restantes no loop são ignoradas e o controle é
transferido para a instrução após as palavras-chave de fim de loop . Então, em um loop, podemos escrever

se condição então
sair; fim se;

onde condição é uma expressão booleana. Como esse talvez seja o uso mais comum da instrução exit, o VHDL
fornece uma maneira abreviada de escrevê-la, usando a cláusula when . Usamos uma instrução exit com a
cláusula when em um loop da forma

ciclo

sair quando condição;

laço final;
… –– controle transferido para aqui
–– quando a condição se torna verdadeira dentro do loop

EXEMPLO

A Figura 3-5 é um modelo para um contador que começa em zero e incrementa em cada transição
de clock de '0' para '1'. Quando o contador atinge 15, ele volta a zero na próxima transição de clock. O
contador possui uma entrada de reset assíncrona que, quando '1', faz com que a saída de contagem seja
zerada. A saída permanece em zero enquanto a entrada de reset for '1' e recomeça a contagem na próxima
transição de clock após o reset mudar para '0'.
Machine Translated by Google

Declarações Sequenciais 29

FIGURA 3-5

contador de entidade
é porta ( clk, reset : in bit; count : out natural ); contador
de entidade final ;
––––––––––––––––––––––––––––––––––––––––––––––––––– ––

comportamento da arquitetura do contador é


begin incrementer : processo é variável

count_value : natural := 0; contagem


inicial <= valor_conta ; loop loop espere até
clk = '1' ou reset = '1'; sair quando reset = '1';
valor_contagem := (valor_contagem + 1) mod
16; contagem <= valor_conta; laço final; ––
neste ponto, reset = '1' count_value :=
0; contagem <= valor_conta; espere até
reset = '0'; laço final; incrementador de
processo final ;

comportamento da arquitetura final ;

Uma entidade e um corpo de arquitetura do contador revisado, incluindo uma entrada de redefinição .

O corpo da arquitetura contém dois loops aninhados. O loop interno lida com a operação
de contagem normal. Quando reset muda para '1', a instrução exit faz com que o loop interno
seja encerrado. O controle é transferido para a instrução logo após o final do loop interno. O
valor de contagem e as saídas de contagem são redefinidos, e o processo aguarda o reinício
para retornar a '0', após o qual o processo é retomado e o loop externo se repete.

Em alguns casos, podemos desejar transferir o controle de um loop interno e também de um


loop de contenção. Podemos fazer isso rotulando o loop externo e usando o rótulo na instrução
exit. Nós podemos escrever

loop_name : loop

sair nome_do_loop;

fim do loop nome_do_loop ;
Machine Translated by Google

30 VHDL é como uma linguagem de programação

Isso rotula o loop com o nome loop_name, para que possamos indicar qual loop
exit na instrução exit. O rótulo do loop pode ser qualquer identificador válido. A instrução exit referente a esse
rótulo pode ser localizada dentro de instruções de loop aninhado.

Enquanto Loops

Podemos aumentar a instrução de loop básica introduzida anteriormente para formar um loop while,
que testa uma condição antes de cada iteração. Se a condição for verdadeira, a iteração prossegue. Se for
falso, o loop é encerrado. A regra de sintaxe para um loop while é

instrução_loop ÿ
[ loop_label : ]
while loop boolean_expression
{ sequencial_statement }
fim do loop [ loop_label ] ;

A única diferença entre esta forma e a instrução de loop básico é que nós
adicionamos a palavra-chave while e a condição antes da palavra-chave loop . Todos os
coisas que dissemos sobre a instrução loop básico também se aplicam a um loop while. A condição é testada
antes de cada iteração do laço while, incluindo a primeira iteração.
Isso significa que, se a condição for falsa antes de iniciarmos o loop, ela será encerrada imediatamente, sem
que nenhuma iteração seja executada.

EXEMPLO

Podemos desenvolver um modelo para uma entidade cos que calcula a função cosseno
de uma entrada teta usando a relação

ÿ2 ÿ4 ÿ6
porque ÿ = 1 – -----
+ ----- + – ----- …
2! 4! 6!

Adicionamos termos sucessivos da série até que os termos se tornem menores que um
milionésimo do resultado. As declarações da entidade e do corpo da arquitetura são mostradas
na Figura 3-6. A função cosseno é calculada usando um laço while que incrementa n por dois e o usa
para calcular o próximo termo com base no termo anterior.
A iteração prossegue enquanto o último termo calculado for maior em magnitude do que
um milionésimo da soma. Quando o último termo cai abaixo desse limite, o while
loop é encerrado.

FIGURA 3-6

entidade cos é
porta ( theta : em real; resultado : fora real );
cos da entidade final ;
––––––––––––––––––––––––––––––––––––––––––––––––––– ––

série arquitetura de cos é


começar
Machine Translated by Google

Declarações Sequenciais 31

soma : processo (teta) é soma


variável , termo : real;
variável n : natural; soma
inicial := 1,0; termo := 1,0; n := 0;
enquanto termo abs > abs
(soma / 1.0E6) loop n := n +
2; termo := (–termo) *
teta**2 / real(((n–1) * n)); soma := soma +
termo; laço final; resultado <= soma;
finalização do processo ;

série de arquitetura final ;

Uma entidade e um corpo de arquitetura para um módulo de cosseno.

Para loops
Outra maneira de aumentar a instrução de loop básico é o loop for. Um loop for inclui uma
especificação de quantas vezes o corpo do loop deve ser executado.
A regra de sintaxe para um loop for é

loop_statement ÿ
[ loop_label : ]
para identificador no loop de
intervalo_discreto { sequencial_statement }
fim do loop [ loop_label ] ;

Um intervalo discreto pode ser da forma

simple_expression ( para eu downto ) simple_expression

representando todos os valores entre os limites esquerdo e direito, inclusive. O identificador é


chamado de parâmetro do loop e, para cada iteração do loop, ele assume valores sucessivos do
intervalo discreto, começando pelo elemento esquerdo. Por exemplo, neste loop for:

para count_value em 0 a 127 loop


count_out <= count_value;
aguarde 5 ns; laço final;

o identificador valor_contagem assume os valores 0, 1, 2 e assim por diante, e para cada valor, as
instruções de atribuição e espera são executadas. Assim, ao sinal count_out serão atribuídos
valores 0, 1, 2 e assim por diante, até 127, em intervalos de 5 ns.
Dentro da sequência de instruções no corpo do loop for, o parâmetro do loop é uma constante.
Isso significa que podemos usar seu valor incluindo-o em uma expressão, mas
Machine Translated by Google

32 VHDL é como uma linguagem de programação

não pode fazer atribuições a ele. Ao contrário de outras constantes, não precisamos declará-la.
Em vez disso, o parâmetro loop é declarado implicitamente no loop for. Ele só existe quando o loop
está em execução, e não antes ou depois dele.
Assim como as instruções de loop básicas, os loops for podem incluir instruções sequenciais
arbitrárias, incluindo instruções de saída, e podemos rotular um loop for escrevendo o rótulo antes da
palavra-chave for .

EXEMPLO

Agora reescrevemos o modelo de cosseno na Figura 3-6 para calcular o resultado somando
os 10 primeiros termos da série com um laço for. A declaração da entidade não é alterada. O
corpo da arquitetura revisada é mostrado na Figura 3-7.

FIGURA 3-7

arquitetura fixed_length_series de cos é


begin summation : process (theta) é
variável sum, term : real; soma
inicial := 1,0; termo := 1,0; para n
em 1 a 9 loop term := (–term) *
theta**2 / real(((2*n–1) * 2*n));
soma := soma + termo; laço final;
resultado <= soma; finalização do
processo ;

final da arquitetura fixed_length_series;

O corpo de arquitetura revisado para o módulo cosseno.

Declarações de Asserção

Uma das razões para escrever modelos de sistemas de computador é verificar se um projeto funciona
corretamente. Podemos testar parcialmente um modelo aplicando entradas de amostra e verificando
se as saídas atendem às nossas expectativas. Se não o fizerem, teremos então a tarefa de determinar
o que deu errado dentro do projeto. Essa tarefa pode ser facilitada usando declarações de asserção
que verificam se as condições esperadas são atendidas no modelo. Uma instrução de asserção é
uma instrução seqüencial, portanto, pode ser incluída em qualquer lugar no corpo de um processo. A
regra de sintaxe para uma declaração de asserção é

assertion_statement ÿ
assert boolean_expression
[ expressão de relatório ] [ expressão de gravidade ] ;

A forma mais simples de declaração de asserção inclui apenas a palavra-chave assert seguida
por uma expressão booleana que esperamos ser verdadeira quando a declaração de estado-
Machine Translated by Google

Declarações Sequenciais 33

mento é executado. Se a condição não for atendida, dizemos que ocorreu uma violação de afirmação .
Se ocorrer uma violação de asserção durante a simulação de um modelo, o simulador relata o fato. Por
exemplo, se escrevermos

assert valor_inicial <= valor_max;

e valor_inicial for maior que valor_max quando a instrução for executada durante a simulação, o
simulador nos informará.
Podemos fazer com que o simulador forneça informações extras incluindo uma cláusula de relatório
em uma declaração de afirmação, por exemplo:

assert valor_inicial <= max_value


relatório "valor inicial muito grande";

A string que fornecemos é usada para fazer parte da mensagem de violação de declaração.
O VHDL predefine um tipo de enumeração nível de gravidade, definido como

o tipo de nível de gravidade é (nota, aviso, erro, falha);

Podemos incluir um valor desse tipo em uma cláusula de gravidade de uma declaração de asserção.
Este valor indica o grau em que a violação da asserção afeta a operação do modelo. Alguns exemplos
são:

assert packet_length /= 0
reportar aviso de gravidade "pacote de
rede vazio recebido" ;
assert clock_pulse_width >= min_clock_width erro
de gravidade ;

Se omitirmos a cláusula de relatório , a string padrão na mensagem de erro será "Violação de


declaração". Se omitirmos a cláusula de gravidade , o valor padrão será error. O valor de severidade
geralmente é usado por um simulador para determinar se deve ou não continuar a execução após uma
violação de asserção. A maioria dos simuladores permite que o usuário especifique um limite de
gravidade, além do qual a execução é interrompida.

EXEMPLO

Um uso importante para declarações de asserção é na verificação de restrições de tempo que


se aplicam a um modelo. Por exemplo, em um registro acionado por borda, quando o relógio muda
de '0' para '1', a entrada de dados é amostrada, armazenada e transmitida para a saída. Vamos
supor que a entrada do clock deve permanecer em '1' por pelo menos 5 ns. A Figura 3-8 é um
modelo para um registrador que inclui uma verificação da largura de pulso de clock legal.

FIGURA 3-8

entidade edge_triggered_register é
porta ( clock : in bit; d_in : in
real; d_out : out real ); entidade
final edge_triggered_register;
Machine Translated by Google

34 VHDL é como uma linguagem de programação

––––––––––––––––––––––––––––––––––––––––––––––––––– ––

arquitetura check_timing de edge_triggered_register é begin


store_and_check : processo (clock) é variável stored_value : real;
variável pulse_start : hora; start case clock é quando '1' =>
pulse_start := now; valor_armazenado := d_in; d_out <=
valor_armazenado;

quando '0' =>


assert now = 0 ns ou (agora – pulse_start) >= 5 ns reporte
"pulso de clock muito curto";
caso final;
finaliza o processo store_and_check;
final arquitetura check_timing;

Uma entidade e um corpo de arquitetura para um registro disparado por borda, incluindo uma verificação de tempo para a largura
de pulso correta na entrada do relógio.

O corpo da arquitetura contém um processo que é sensível a mudanças na entrada do


relógio. Quando o relógio muda de '0' para '1', a entrada é armazenada, e o tempo de simulação
atual, acessado usando a função predefinida agora, é registrado na variável pulse_start. Quando
o relógio muda de '1' para '0', a diferença entre pulse_start e o tempo de simulação atual é
verificada pela asserção
demonstração.

3.5 Tipos e Operações de Array


Uma matriz consiste em uma coleção de valores, todos do mesmo tipo entre si. A posição de cada
elemento em uma matriz é dada por um valor escalar chamado seu índice. Para criar um objeto array
em um modelo, primeiro definimos um tipo array em uma declaração de tipo. A regra de sintaxe para
uma definição de tipo de matriz é

array_type_definition ÿ array
( discreto_range ) de element_subtype_indication

Isso define um tipo de matriz especificando o intervalo de índice e o tipo de elemento ou subtipo. Um
intervalo discreto é um subconjunto de valores de um tipo discreto (um tipo inteiro ou de enumeração).
Pode ser especificado como mostrado pela regra de sintaxe simplificada

intervalo_discreto ÿ
marca_tipo
I simple_expression ( para I downto ) simple_expression
Machine Translated by Google

Tipos de matriz e operações 35

Ilustramos essas regras para definir arrays com uma série de exemplos. Aqui está um exemplo
simples para começar, mostrando a declaração de um tipo de array para representar palavras de
dados:

tipo palavra é array (0 a 31) de bit;

Cada elemento é um bit, e os elementos são indexados de 0 a 31. Uma alternativa


declaração de um tipo de palavra, mais apropriada para sistemas “little-endian”, é

tipo palavra é array (31 até 0) de bit;

A diferença aqui é que os valores de índice começam em 31 para o elemento mais à esquerda
em valores desse tipo e continuam até 0 para o mais à direita. Os valores de índice de uma matriz não
precisam ser numéricos. Por exemplo, dada esta declaração de um tipo de enumeração:

tipo controller_state é (inicial, ocioso, ativo, erro);

poderíamos então declarar um array da seguinte forma:

tipo state_counts é array (idle to error) de natural;

Se precisarmos de um elemento de matriz para cada valor em um tipo de índice, precisamos apenas
nomear o tipo de índice na declaração de matriz sem especificar o intervalo. Por exemplo:

o subtipo coeff_ram_address é um intervalo inteiro de 0


a 63; tipo coeff_array é array (coeff_ram_address) de real;

Uma vez que tenhamos declarado um tipo de array, podemos definir objetos desse tipo, incluindo
constantes, variáveis e sinais. Por exemplo, usando os tipos declarados acima, podemos declarar
variáveis da seguinte forma:

variável buffer_register, data_register : palavra;


contadores de variáveis : state_counts; variável
coeff : coeff_array;

Cada um desses objetos consiste na coleção de elementos descritos pela declaração de tipo
correspondente. Um elemento individual pode ser usado em uma expressão ou como destino de uma
atribuição referindo-se ao objeto array e fornecendo um valor de índice, por exemplo:

coef(0) := 0,0;

Se active for uma variável do tipo controller_state, podemos escrever

contadores(ativo) := contadores(ativo) + 1;

Um objeto array também pode ser usado como um único objeto composto. Por exemplo, o
tarefa

data_register := buffer_register;
Machine Translated by Google

36 VHDL é como uma linguagem de programação

copia todos os elementos do array buffer_register nos elementos correspondentes do array data_register.

Agregados de matriz

Frequentemente, também precisamos escrever valores de array literais, por exemplo, para inicializar uma
variável ou constante de um tipo de array. Podemos fazer isso usando uma construção VHDL chamada
agregado de matriz, de acordo com a regra de sintaxe

agregado ÿ ( ( [ escolhas => ] expressão ) { , … } )

Vejamos primeiro a forma de agregado sem a parte de escolhas. Simplesmente con


consiste de uma lista dos elementos entre parênteses, por exemplo:

tipo ponto é array (1 a 3) de real;


origem constante : ponto := (0,0, 0,0, 0,0);
variável view_point : point := (10,0, 20,0, 0,0);

Essa forma de agregação de matriz usa associação posicional para determinar qual valor na lista
corresponde a qual elemento da matriz. O primeiro valor é o elemento com o índice mais à esquerda, o
segundo é o próximo índice à direita e assim por diante, até o último valor, que é o elemento com o índice
mais à direita. Deve haver uma correspondência um-para-um entre os valores na agregação e os elementos
na matriz.
Uma forma alternativa de agregação usa associação nomeada, na qual o valor de índice para cada
elemento é escrito explicitamente usando a parte de opções mostrada na regra de sintaxe. As escolhas
podem ser especificadas exatamente da mesma maneira que as alternativas de uma instrução case. Por
exemplo, a declaração da variável e a inicialização podem ser reescritas como

variável view_point : point := (1 => 10,0, 2 => 20,0, 3 => 0,0);

EXEMPLO

A Figura 3-9 é um modelo para uma memória que armazena 64 coeficientes de números reais,
inicializados em 0,0. Assumimos que o tipo coeff_ram_address foi declarado anteriormente como
acima. O corpo da arquitetura contém um processo com uma variável de matriz representando o
armazenamento de coeficientes. A matriz é inicializada usando uma agregação na qual todos os
elementos são 0,0. O processo é sensível a todas as portas de entrada. Quando rd é '1', a matriz é
indexada usando o valor de endereço para ler um coeficiente. Quando wr é '1', o valor do endereço é
usado para selecionar qual coeficiente mudar.

FIGURA 3-9

entidade coeff_ram
é porta ( rd, wr : em bit; addr : em coeff_ram_address;
d_in : em real; d_out : out real ); entidade final
coeff_ram;
––––––––––––––––––––––––––––––––––––––––––––––––––– ––

O resumo da arquitetura do coeff_ram é


iniciado
Machine Translated by Google

Tipos de matriz e operações 37

memory: processo (rd, wr, addr, d_in) é


tipo coeff_array é array (coeff_ram_address) de real; variável coeff :
coeff_array := (outros => 0.0); começar se rd = '1' então d_out <=
coeff(addr); fim se; se wr = '1' então coeff(addr) := d_in; fim se; memória de
processo final ; final arquitetura abstrata;

Uma entidade e um corpo de arquitetura para um módulo de memória que armazena coeficientes de números reais. O
armazenamento de memória é implementado usando um array.

Atributos da matriz

O VHDL fornece vários atributos para consultar informações sobre os intervalos de índice de tipos e objetos
de array. Os atributos são escritos seguindo o tipo de array ou o nome do objeto com o símbolo ' e o nome
do atributo. Dado algum tipo de array ou objeto A, e um inteiro N entre 1 e o número de dimensões de A,
VHDL define os seguintes atributos:

A'esquerda
Limite esquerdo do intervalo de índice de A

Corretamente Limite direito do intervalo de índice de A

A'range Faixa de índice de A

A'reverse_range Reverso do intervalo de índice de A

A'comprimento Comprimento do intervalo de índice de A

Por exemplo, dada a declaração de array

tipo A é array (1 a 4) de booleano;

alguns valores de atributo são

A'esquerda = 1 A'direita = 4

A'range é de 1 a 4 A'reverse_range é 4 até 1

A'comprimento = 4

Os atributos podem ser usados ao escrever loops para iterar sobre elementos de uma matriz. Por
exemplo, dada uma variável de matriz free_map que é uma matriz de bits, podemos escrever um loop for
para contar o número de bits '1' sem saber o tamanho real do
variedade:

contagem :=
0; para índice no loop free_map'range se
free_map(index) = '1' então
Machine Translated by Google

38 VHDL é como uma linguagem de programação

contagem := contagem
+ 1; fim se; laço final;

Os atributos 'range e 'reverse_range podem ser usados em qualquer lugar em um modelo VHDL
onde uma especificação de intervalo é necessária, como uma alternativa para especificar os limites
esquerdo e direito e a direção do intervalo. Assim, podemos usar os atributos em definições de tipo e
subtipo, em restrições de subtipo, em especificações de parâmetros de loop for, em escolhas de
instruções case e assim por diante. A vantagem de adotar essa abordagem é que podemos especificar
o tamanho do array em um lugar no modelo e em todos os outros lugares usar array em tributos. Se
precisarmos alterar o tamanho do array posteriormente por algum motivo, precisamos apenas alterar o
modelo em um lugar.

Tipos de matrizes sem restrições


Os tipos de array que vimos até agora neste capítulo são chamados de arrays restritos , uma vez que a
definição de tipo restringe os valores de índice a estarem dentro de um intervalo específico. O VHDL
também nos permite definir tipos de arrays irrestritos , nos quais apenas indicamos o tipo dos valores de
índice, sem especificar limites. Uma definição de tipo de matriz irrestrita é descrita pela regra de sintaxe
alternativa

array_type_definition ÿ
array ( type_mark range <> ) de element_subtype_indication

O símbolo “<>”, geralmente chamado de “caixa”, pode ser considerado um espaço reservado para o
intervalo do índice, a ser preenchido posteriormente quando o tipo for usado. Um exemplo de uma
declaração de tipo de matriz irrestrita é

tipo amostra é array ( intervalo natural <>) de inteiro;

Um ponto importante para entender sobre os tipos de array irrestritos é que quando declaramos um
objeto desse tipo, precisamos fornecer uma restrição que especifique os limites do índice. Podemos
fazer isso de várias maneiras. Uma maneira é fornecer a restrição quando um objeto é criado, por
exemplo:

variável short_sample_buf : amostra(0 a 63);

Isso indica que os valores de índice para a variável short_sample_buf são números naturais no
intervalo crescente de 0 a 63. Outra maneira de especificar a restrição é declarar um subtipo do tipo de
matriz irrestrita. Os objetos podem então ser criados usando este subtipo, por exemplo:

subtipo long_sample é amostra(0 a 255);


variável new_sample_buf, old_sample_buf : long_sample;

Ambos são exemplos de uma nova forma de indicação de subtipo que ainda não vimos. A regra de
sintaxe é

subtype_indication ÿ type_mark [ ( discreto_range ) ]


Machine Translated by Google

Tipos de matriz e operações 39

A marca de tipo é o nome do tipo de array irrestrito e as especificações de intervalo discreto restringem o tipo
de índice a um subconjunto de valores usados para indexar o array ele
mentos.

Cordas

O VHDL fornece um tipo de array irrestrito predefinido chamado string, declarado como

tipo string é array ( intervalo positivo <>) de caractere;

Por exemplo:

constante LCD_display_len : positivo :=


20; subtipo LCD_display_string é string(1 a LCD_display_len);
variável LCD_display : LCD_display_string := (outros => ' ');

Vetores de bits

O VHDL também fornece um tipo de array irrestrito predefinido chamado bit_vector, declarado
como

tipo bit_vector é array ( intervalo natural <>) de bit;

Por exemplo, subtipos para representar bytes de dados em um processador little-endian podem ser declarados
como

byte de subtipo é bit_vector(7 até 0 );

Alternativamente, podemos fornecer a restrição quando um objeto é declarado, por exemplo:

variável channel_busy_register : bit_vector(1 a 4);

Matrizes de lógica padrão

O pacote de lógica padrão std_logic_1164 fornece um tipo de array irrestrito para vetores de valores de lógica
padrão. É declarado como

tipo std_ulogic_vector é array ( intervalo natural <> ) de std_ulogic;

Podemos definir subtipos do tipo de vetor de lógica padrão, por exemplo:

subtipo std_ulogic_word é std_ulogic_vector(0 a 31);

Ou podemos criar diretamente um objeto do tipo vetorial de lógica padrão:

sinal csr_offset : std_ulogic_vector(2 até 1 );

Portas de matriz sem restrições

Um uso importante de um tipo de array irrestrito é especificar o tipo de uma porta de array.
Esse uso nos permite escrever uma interface de entidade de maneira geral, para que ela possa se conectar a
sinais de matriz de qualquer tamanho ou com qualquer intervalo de valores de índice. quando nós instanciarmos
Machine Translated by Google

40 VHDL é como uma linguagem de programação

a entidade, os limites de índice do sinal de matriz conectado à porta são usados como os limites da
porta.

EXEMPLO

Suponha que desejamos modelar uma família de portas e, cada uma com um número
diferente de entradas. Declaramos a interface da entidade conforme mostrado na Figura 3-10. A
porta de entrada é do tipo bit_vector irrestrito. O processo no corpo da arquitetura executa uma
operação lógica e na matriz de entrada. Ele usa o atributo 'range para determinar o intervalo de
índice do array, pois o intervalo de índice não é conhecido até que a entidade seja instanciada.

FIGURA 3-10

entidade and_multiple é
porta ( i : in bit_vector; y : out bit );
entidade final e_múltiplo;
––––––––––––––––––––––––––––––––––––––––––––––––––– ––

arquitetura comportamental de and_multiple é begin


and_reducer : process ( i ) é variável result : bit;
resultado inicial := '1'; for index in i'range loop
result := result e i(index); laço final; y <=
resultado; final do processo and_reducer;

arquitetura final comportamental;

Uma entidade e um corpo de arquitetura para uma porta and com uma porta de entrada de matriz irrestrita.

Para ilustrar o uso da entidade porta de múltiplas entradas, suponha que temos os seguintes
sinais:

valor_contagem do sinal : bit_vector(7 até 0 ); sinal


terminal_count : bit;

Instanciamos a entidade, conectando sua porta de entrada ao sinal bit-vetor:

tc_gate : entidade work.and_multiple(comportamental)


mapa de portas (i => valor_contagem, y => contagem_terminal);

Para esta instância, a porta de entrada é restringida pelo intervalo de índice do sig
final A instância atua como uma porta e oito entradas.
Machine Translated by Google

Tipos de matriz e operações 41

Operações de matriz e referência

O VHDL fornece vários operadores para operar em arrays inteiros, combinando elementos em
pares. Primeiro, os operadores lógicos (and, or, nand, nor, xor e
xnor) pode ser aplicado a duas matrizes unidimensionais de elementos bit ou booleanos, e
o operador not pode ser aplicado a uma única matriz de elementos bit ou booleanos. o
declarações e declarações a seguir ilustram esse uso de operadores lógicos quando aplicados a vetores de bits:

subtipo pixel_row é bit_vector (0 a 15);


variável current_row, mask : pixel_row;
current_row := current_row e não máscara;
current_row := current_row xor X"FFFF";

Em segundo lugar, os operadores de deslocamento podem ser usados com uma matriz unidimensional de bits ou
Valores booleanos como operando esquerdo e um valor inteiro como operando direito. o
operadores são:

sll deslocamento para a esquerda lógico

Srl deslocamento para a direita lógico

sla deslocar aritmética para a esquerda

sra deslocamento aritmético para a direita

Função vire à esquerda

ro vire à direita

Terceiro, os operadores relacionais podem ser aplicados a arrays de qualquer tipo de elemento discreto.
Os dois operandos não precisam ter o mesmo comprimento, desde que tenham o mesmo
tipo de elemento. Os elementos são comparados em pares da esquerda para a direita até que seja encontrado um par que
são desiguais ou o fim de um ou outro operando é alcançado. Por exemplo, quando aplicados a cadeias de caracteres,
os operadores relacionais testam de acordo com a ordenação do dicionário.
Finalmente, o operador de concatenação (“&”) une dois valores de array de ponta a ponta. Por
exemplo, b"0000" & b"1111" produz b"0000_1111". O operador de concatenação pode
também ser aplicado a dois operandos, um dos quais é uma matriz e o outro é
um único elemento escalar, ou dois valores escalares para produzir uma matriz de comprimento 2. Alguns
exemplos são

"abc" & 'd' = "abcd"


'w' & "xyz" = "wxyz"
'a' e 'b' = "ab"

Fatias de matriz

Frequentemente queremos nos referir a um subconjunto contíguo de elementos de um array, mas não ao
matriz inteira. Podemos fazer isso usando a notação de fatia , na qual especificamos a esquerda e
valores de índice corretos de parte de um objeto de matriz. Por exemplo, dadas as matrizes a1 e a2
declarado da seguinte forma:
Machine Translated by Google

42 VHDL é como uma linguagem de programação

tipo array1 é array (1 a 100) de inteiro;


tipo array2 é array (100 até 1) de inteiro;
variável a1 : array1; variável a2 : array2;

podemos nos referir ao array slice a1(11 a 20), que é um array de 10 elementos com índices
de 11 a 20. Da mesma forma, o slice a2(50 downto 41) é um array de 10 elementos mas
com um intervalo de índice descendente .
Machine Translated by Google

4
Construções básicas de modelagem

4.1 Declarações da Entidade


Vamos primeiro examinar as regras de sintaxe para uma declaração de entidade e depois mostrar alguns
exemplos. As regras de sintaxe são

entidade_declaração ÿ
identificador de
entidade é [ port ( port_interface_list ); ]
end [ entidade ] [ identificador ] ;
lista_interface ÿ ( identificador { , … } :
[ modo ] indicação_subtipo ) { ; … }
mode ÿ in I out I inout

O identificador em uma declaração de entidade nomeia o módulo para que possa ser consultado
posteriormente. Se o identificador for incluído no final da declaração, deverá repetir o nome da entidade.
A cláusula port nomeia cada uma das portas, que juntas formam a interface para a entidade. Podemos
pensar nas portas como análogas aos pinos de um circuito; eles são os meios pelos quais a informação
entra e sai do circuito. Em VHDL, cada porta de uma entidade possui um tipo, que especifica o tipo de
informação que pode ser comunicada, e um modo, que especifica se a informação flui para dentro ou para
fora da entidade através da porta. Um exemplo simples de uma declaração de entidade é

entidade adicionadora
é porta ( a, b na
: palavra; soma :
fora da palavra );
adicionador de entidade final ;

Este exemplo descreve uma entidade chamada somador, com duas portas de entrada e uma porta
de saída, todas do tipo palavra, que assumimos estar definida em outro lugar. Podemos listar as portas
em qualquer ordem; não temos que colocar as entradas antes das saídas.
Neste exemplo temos portas de entrada e saída. Também podemos ter portas bidirecionais, com
modo inout, para modelar dispositivos que detectam e direcionam dados alternadamente por meio de um
pino. Tais modelos devem lidar com a possibilidade de mais de um dispositivo conectado conduzir um
determinado sinal ao mesmo tempo. O VHDL fornece um mecanismo para isso, a resolução do sinal, ao
qual retornaremos mais tarde.
Observe que a cláusula port é opcional. Assim, podemos escrever uma declaração de entidade como

43
Machine Translated by Google

44 Construções básicas de modelagem

entidade top_level é
entidade final top_level;

que descreve um módulo completamente independente. Como o nome neste exemplo


implica, esse tipo de módulo geralmente representa o nível superior de uma hierarquia de design.

4.2 Órgãos de Arquitetura

A operação interna de um módulo é descrita por um corpo de arquitetura. Um corpo de arquitetura geralmente
aplica algumas operações aos valores nas portas de entrada, gerando
valores a serem atribuídos às portas de saída. As operações podem ser descritas por processos, que contêm
instruções sequenciais operando em valores, ou por uma coleção de
componentes que representam sub-circuitos. Onde a operação requer geração de valores intermediários, estes
podem ser descritos usando sinais, análogos aos
fios de um módulo. A regra de sintaxe para corpos de arquitetura mostra o esquema geral:

arquitetura_corpo ÿ
identificador de arquitetura de entity_name é
{ block_declarative_item }
começar
{ declaração_concorrente }
end [ arquitetura ] [ identificador ] ;

O identificador nomeia este corpo de arquitetura em particular, e o nome da entidade especifica qual
módulo tem sua operação descrita por este corpo de arquitetura. Se o identificador estiver incluído no final do
corpo da arquitetura, ele deverá repetir o nome do
corpo de arquitetura. Pode haver vários corpos de arquitetura diferentes correspondentes
para uma única entidade, cada uma descrevendo uma forma alternativa de implementar o módulo
Operação. Os itens declarativos de bloco em um corpo de arquitetura são declarações necessárias para
implementar as operações. Os itens podem incluir declarações de tipo e constante, declarações de sinal e outros
tipos de declarações que veremos mais adiante
capítulos.
As declarações concorrentes em um corpo de arquitetura descrevem a operação do módulo. Uma forma
de declaração concorrente, que já vimos, é um processo
demonstração. Analisamos os processos primeiro porque eles são os mais fundamentais
forma de declaração concorrente. Todas as outras formas podem, em última análise, ser reduzidas a uma ou
mais processos. Declarações concorrentes são assim chamadas porque conceitualmente podem
ser ativados e realizar suas ações em conjunto, ou seja, simultaneamente. Compare isso
com as instruções sequenciais dentro de um processo, que são executadas uma após a outra.
A simultaneidade é útil para modelar a maneira como os circuitos reais se comportam.
Quando precisamos fornecer sinais internos em um corpo de arquitetura, devemos definir
usando declarações de sinal. A sintaxe para uma declaração de sinal é muito semelhante à
isso para uma declaração de variável:

declaração_sinal ÿ
identificador de sinal { , … } : subtype_indication [ := expressão ] ;
Machine Translated by Google

Descrições Comportamentais 45

Esta declaração simplesmente nomeia cada sinal, especifica seu tipo e opcionalmente inclui um valor
inicial para todos os sinais declarados pela declaração.
Um ponto importante que mencionamos anteriormente é que as portas da entidade também são
visíveis para os processos dentro do corpo da arquitetura e são usadas da mesma forma que os sinais.
Isso corresponde à nossa visão das portas como pinos externos de um circuito: do ponto de vista interno,
um pino é apenas um fio com uma conexão externa. Portanto, faz sentido para o VHDL tratar as portas
como sinais dentro de uma arquitetura da entidade.

4.3 Descrições Comportamentais


No nível mais fundamental, o comportamento de um módulo é descrito por instruções de atribuição de
sinal dentro dos processos. Podemos pensar em um processo como a unidade básica de descrição
comportamental. Um processo é executado em resposta a mudanças nos valores dos sinais e usa os
valores presentes dos sinais que lê para determinar novos valores para outros sinais. Uma atribuição de
sinal é uma instrução sequencial e, portanto, só pode aparecer dentro de um processo. Nesta seção,
veremos em detalhes a interação entre sinais e
processos.

Atribuição de Sinal
Em todos os exemplos que vimos até agora, usamos uma forma simples de instrução de atribuição de
sinal. Cada atribuição apenas fornece um novo valor para um sinal. O que ainda não abordamos é a
questão do tempo: quando o sinal assume o novo valor? Isso é fundamental para a modelagem de
hardware, em que os eventos ocorrem ao longo do tempo. Primeiro, vejamos a sintaxe de uma instrução
básica de atribuição de sinal em um pro
cesso:

signal_assignment_statement ÿ name
<= ( value_expression [ após time_expression ] ) { , … } ;

A regra de sintaxe nos diz que podemos especificar uma ou mais expressões, cada uma com um
tempo de atraso opcional. São esses tempos de atraso em uma atribuição de sinal que nos permitem
especificar quando o novo valor deve ser aplicado. Por exemplo, considere a seguinte atribuição:

y <= não ou_a_b após 5 ns;

Isso especifica que o sinal y deve assumir o novo valor em um momento 5 ns depois daquele em que a
instrução é executada. Assim, se a atribuição acima for executada no tempo 250 ns, e or_a_b tiver o
valor '1' nesse momento, então o sinal y assumirá o valor '0' no tempo 255 ns. Observe que a própria
instrução é executada em tempo modelado zero.
A dimensão de tempo referida quando o modelo é executado é o tempo de simulação, ou seja, o
tempo em que se considera que o circuito que está sendo modelado opera. Medimos o tempo de
simulação começando do zero no início da execução e aumentando em etapas discretas à medida que
os eventos ocorrem no modelo. Um simulador deve ter um relógio de tempo de simulação e, quando
uma instrução de atribuição de sinal é executada, o atraso especificado é adicionado ao tempo de
simulação atual para determinar quando o novo valor deve ser aplicado ao sinal. Dizemos que a
atribuição de sinal agenda uma transação para o sinal,
Machine Translated by Google

46 Construções básicas de modelagem

onde a transação consiste no novo valor e no tempo de simulação em que deve ser aplicado. Quando o tempo de

simulação avança para o horário em que uma transação está agendada, o sinal é atualizado com o novo valor.
Dizemos que o sinal está ativo durante esse ciclo de simulação. Se o novo valor não for igual ao valor antigo que ele
substitui em um sinal, dizemos que um evento ocorre no sinal. A importância dessa distinção é que os processos
respondem a eventos em sinais, não em transações.

As regras de sintaxe para atribuições de sinal mostram que podemos agendar um número de transações para
um sinal, a ser aplicado após diferentes atrasos. Por exemplo, um processo de driver de clock pode executar a
seguinte atribuição para gerar as próximas duas bordas de um sinal de clock (assumindo que T_pw é uma constante
que representa a largura do pulso de clock):

clk <= '1' após T_pw, '0' após 2*T_pw;

Se esta instrução for executada no tempo de simulação 50 ns e T_pw tiver o valor 10 ns, uma transação é
agendada para o tempo 60 ns para definir clk para '1' e uma segunda transação é agendada para o tempo 70 ns
para definir clk para ' 0'. Se assumirmos que clk tem o valor '0' quando a atribuição é executada, ambas as transações
produzem eventos em clk.

EXEMPLO

Podemos escrever um processo que modela um multiplexador de duas entradas como mostrado na
Figura 4-1. O valor da porta sel é usado para selecionar qual atribuição de sinal executar para determinar o
valor de saída.

FIGURA 4-1

mux : processo (a, b, sel) é start


case sel é quando '0' =>

z <= a após prop_delay;


quando '1' =>
z <= b após prop_delay; caso
final; final do processo mux;

Um processo que modela um multiplexador de duas entradas.

Dizemos que um processo define um driver para um sinal se e somente se ele contém pelo menos uma
instrução de atribuição de sinal para o sinal. Se um processo contém instruções de atribuição de sinais para vários
sinais, ele define drivers para cada um desses sinais. Um driver é uma fonte para um sinal na medida em que
fornece valores a serem aplicados ao sinal. Uma regra importante a ser lembrada é que, para sinais normais, pode
haver apenas uma fonte. Isso significa que não podemos escrever dois processos diferentes, cada um contendo
instruções de atribuição de sinal para um sinal. Se quisermos modelar coisas como barramentos ou sinais com fio
ou com fio, devemos usar um tipo especial de sinal chamado sinal resolvido, que discutiremos mais adiante.
Machine Translated by Google

Descrições Comportamentais 47

Atributos de sinal
O VHDL fornece uma série de atributos para que os sinais encontrem informações sobre seu histórico de
transações e eventos. Dado um sinal S e um valor T do tipo time, VHDL define os seguintes atributos:

S'delayed(T) Um sinal que assume os mesmos valores que S , mas é atrasado por
tempo T

S'evento True se houver um evento em S no ciclo de simulação atual, false caso contrário.

S'last_event O intervalo de tempo desde o último evento em S.

S'last_value O valor de S imediatamente antes do último evento em S.

Esses atributos são frequentemente usados para verificar o comportamento de tempo dentro de um modelo.
Por exemplo, podemos verificar se um sinal d atende a um requisito de tempo mínimo de configuração de Tsu
antes de uma borda de subida em um clock clk do tipo std_ulogic da seguinte forma:

se clk'event e (clk = '1' ou clk = 'H') e


(clk'last_value = '0' ou clk'last_value = 'L') então
assert d'last_event >= Tsu
report "Erro de tempo: d alterado dentro do tempo de configuração do clk";
fim se;

EXEMPLO

Podemos testar a borda de subida de um sinal de clock para modelar um flip-flop acionado por borda.
O flipflop carrega o valor de sua entrada D em uma borda ascendente de clk, mas limpa as saídas de forma
assíncrona sempre que clr for '1'. A declaração de entidade e um corpo de arquitetura comportamental são
mostrados na Figura 4-2.

FIGURA 4-2

entidade edge_triggered_Dff é
porta ( D : em bit; clk : em bit; clr : em bit;
Q: bit de
saída ); entidade final edge_triggered_Dff;
––––––––––––––––––––––––––––––––––––––––––––––––––– ––

arquitetura comportamental de edge_triggered_Dff é begin


state_change : processo (clk, clr) é begin if clr = '1' then

Q <= '0' após 2 ns;


elsif clk'event e clk = '1' então
Q <= D após 2 ns;
fim se; final do processo
state_change;
Machine Translated by Google

48 Construções básicas de modelagem

arquitetura final comportamental;

Uma entidade e um corpo de arquitetura para um flipflop acionado por borda, usando o atributo 'evento para
verificar alterações no sinal clk .

Declarações de espera

Agora que vimos como alterar os valores dos sinais ao longo do tempo, o próximo passo na modelagem
comportamental é especificar quando os processos respondem a mudanças nos valores dos sinais. Isso é feito
usando instruções de espera. Uma instrução de espera é uma instrução sequencial com a seguinte regra de sintaxe:

wait_statement ÿ wait
[ on signal_name { , … } ] [ até
boolean_expression ] [ for
time_expression ] ;

O propósito da instrução wait é fazer com que o processo que executa a instrução suspenda a execução. A
cláusula de sensibilidade , cláusula de condição e cláusula de tempo limite especificam quando o processo deve
retomar a execução subsequentemente. Podemos incluir qualquer combinação dessas cláusulas ou podemos omitir
todas as três. Passemos por cada cláusula e descrevamos o que ela especifica.

A cláusula de sensibilidade, começando com a palavra on, permite especificar uma lista de sinais aos quais o
processo responde. Se incluirmos apenas uma cláusula de sensibilidade em uma instrução de espera, o processo
será retomado sempre que qualquer um dos sinais listados mudar de valor, ou seja, sempre que ocorrer um evento
em qualquer um dos sinais. Esse estilo de instrução de espera é útil em um processo que modela um bloco de lógica
combinatória, pois qualquer alteração nas entradas pode resultar em novos valores de saída; por exemplo:

half_add : processo é
soma inicial <= a xor b
após T_pd; transportar <=
aeb após T_pd ; espere em a,
b; finaliza o processo half_add;

Essa forma de processo é tão comum na modelagem de sistemas digitais que o VHDL fornece a notação
abreviada que vimos em muitos exemplos nos capítulos anteriores. Um processo com uma lista de sensibilidade
em seu cabeçalho é exatamente equivalente a um processo com uma instrução de espera no final, contendo uma
cláusula de sensibilidade nomeando os sinais na lista de sensibilidade. Assim, o processo half_add acima pode ser
reescrito como

half_add : processo (a, b) é a


soma inicial <= a xor b após
T_pd; transportar <= aeb após
T_pd ; finaliza o processo
half_add;
Machine Translated by Google

Descrições Comportamentais 49

A cláusula de condição em uma instrução de espera, começando com a palavra até, nos permite
especificar uma condição que deve ser verdadeira para que o processo seja retomado. Por exemplo, a
instrução de espera

espere até clk = '1';

faz com que o processo de execução seja suspenso até que o valor do sinal clk mude para '1'. A
expressão de condição é testada enquanto o processo está suspenso para determinar se o processo
deve ser retomado. Se a instrução wait não incluir uma cláusula de sensibilidade, a condição será testada
sempre que ocorrer um evento em qualquer um dos sinais mencionados na condição.

Se uma instrução de espera incluir uma cláusula de sensibilidade, bem como uma cláusula de
condição, a condição será testada apenas quando ocorrer um evento em qualquer um dos sinais da
cláusula de sensibilidade. Por exemplo, se um processo for suspenso na seguinte instrução de espera:

espere no clk até reset = '0';

a condição é testada em cada alteração no valor de clk, independentemente de quaisquer alterações no


reset.
A cláusula timeout em uma instrução de espera, começando com a palavra for, nos permite
especificar um intervalo máximo de tempo de simulação para o qual o processo deve ser suspenso. Se
também incluirmos uma cláusula de sensibilidade ou condição, isso pode fazer com que o processo seja
retomado mais cedo. Por exemplo, a instrução de espera

espere até trigger = '1' por 1 ms;

faz com que o processo em execução seja suspenso até que o gatilho mude para '1', ou até que 1 ms do
tempo de simulação tenha decorrido, o que ocorrer primeiro. Se incluirmos apenas uma cláusula de
tempo limite em uma instrução de espera, o processo será suspenso pelo tempo determinado.
Se nos referirmos à regra de sintaxe para uma instrução de espera mostrada na página 48,
note que é legal escrever

esperar;

Este formulário faz com que o processo de execução seja suspenso pelo restante da simulação. Vimos
um exemplo disso em bancadas de teste que suspendem indefinidamente após a aplicação de todos os
estímulos.

Atrasos Delta
Voltemos agora ao tópico dos atrasos na atribuição de sinais. Em muitas das atribuições de sinal de
exemplo nos capítulos anteriores, omitimos a parte de atraso dos elementos de forma de onda. Isso é
equivalente a especificar um atraso de 0 fs. O valor deve ser aplicado ao sinal no tempo de simulação
atual. No entanto, é importante observar que o valor do sinal não muda assim que a instrução de
atribuição de sinal é executada.
Em vez disso, a atribuição agenda uma transação para o sinal, que é aplicada após a suspensão do
processo. Assim, o processo não vê o efeito da atribuição até a próxima vez que é retomado, mesmo
que seja no mesmo tempo de simulação. Por esta razão, um atraso de 0 fs em uma atribuição de sinal é
chamado de atraso delta.
Machine Translated by Google

50 Construções básicas de modelagem

Para entender por que os atrasos delta funcionam dessa maneira, é necessário rever o
ciclo de simulação. Lembre-se de que o ciclo de simulação consiste em duas fases: uma fase de
atualização do sinal seguida por uma fase de execução do processo. Na fase de atualização do sinal,
o tempo de simulação é adiantado para o horário da primeira transação agendada, e o
valores em todas as transações programadas para este horário são aplicados aos seus
sinais. Isso pode causar eventos em alguns sinais. Na execução do processo
fase, todos os processos que respondem a esses eventos são retomados e executados até que
suspenda novamente nas instruções de espera. O simulador então repete o ciclo de simulação.
Vamos agora considerar o que acontece quando um processo executa uma atribuição de sinal
instrução com atraso delta, por exemplo:

dados <= X"00";

Suponha que isso seja executado no tempo de simulação t durante a fase de execução do processo de
o ciclo de simulação atual. O efeito da atribuição é agendar uma transação
para colocar o valor X"00" nos dados no tempo t. A transação não é aplicada imediatamente,
já que o simulador está na fase de execução do processo. Daí o processo continua
executando, com os dados inalterados. Quando todos os processos estiverem suspensos, o simulador
inicia o próximo ciclo de simulação e atualiza o tempo de simulação. Desde os primeiros
transação está agora no tempo t, o tempo de simulação permanece inalterado. O simulador agora
aplica o valor X"00" na transação agendada aos dados e, em seguida, retoma quaisquer processos
que respondam ao novo valor.

Declarações de Processo

Temos usado processos bastante extensivamente em exemplos neste e em anteriores


capítulos, então vimos a maioria dos detalhes de como eles são escritos e usados. Para
resumir, vamos agora olhar para a sintaxe formal para uma declaração de processo e rever
operação do processo. A regra de sintaxe é

process_statement ÿ
process_label : process [ ( signal_name { , … } ) ] [ is ]
{ process_declarative_item }
começar

{ sequencial_statement }
final do processo [ process_label ] ;

Os itens declarativos em uma declaração de processo podem incluir declarações de constantes,


tipos e variáveis, bem como outras declarações que veremos mais adiante. As declarações seqüenciais
que formam o corpo do processo podem incluir qualquer uma daquelas que
introduzido anteriormente, mais atribuição de sinal e instruções de espera. Quando um processo é
ativado durante a simulação, ele começa a ser executado a partir da primeira instrução sequencial e
continua até chegar ao último. Em seguida, ele começa novamente a partir do primeiro. Este seria
um loop infinito, sem progresso na simulação, se não fosse pela
inclusão de instruções de espera, que suspendem a execução do processo até que algum
evento ocorre. As instruções de espera são as únicas instruções que levam mais de zero tempo de
simulação para serem executadas. É somente através da execução de instruções de espera que o
tempo de simulação avança.
Machine Translated by Google

Descrições Comportamentais 51

Um processo pode incluir uma lista de sensibilidade entre parênteses após a palavra-chave processo.
A lista de sensibilidade identifica um conjunto de sinais que o processo monitora para eventos. Se a lista
de sensibilidade for omitida, o processo deverá incluir uma ou mais instruções de espera.
Por outro lado, se a lista de sensibilidade for incluída, o corpo do processo não poderá incluir nenhuma
instrução de espera. Em vez disso, há uma instrução de espera implícita, logo antes das palavras-chave
do processo final , que inclui os sinais listados na lista de sensibilidade como sinais em uma cláusula on .

Declarações de Atribuição de Sinal Condicional


A instrução de atribuição de sinal condicional é uma instrução concorrente que é um atalho para um
processo que contém uma coleção de atribuições de sinal comuns dentro de uma instrução if. A regra de
sintaxe é

conditional_signal_assignment ÿ name
<= { waveform quando boolean_expression else } waveform
[ quando boolean_expression ] ;

A atribuição de sinal condicional nos permite especificar qual de um número de formas de onda deve
ser atribuída a um sinal dependendo dos valores de algumas condições. Por exemplo, a declaração a
seguir é uma descrição funcional de um multiplexador, com quatro entradas de dados (d0, d1, d2 e d3),
duas entradas de seleção (sel0 e sel1) e uma saída de dados (z). Todos esses sinais são do tipo bit.

z <=d0 quando sel1 = '0' e sel0 = '0' else d1


quando sel1 = '0' e sel0 = '1' else d2
quando sel1 = '1' e sel0 = '0' else d3
quando sel1 = ' 1' e sel0 = '1';

A instrução é sensível a todos os sinais mencionados nas expressões e às condições à direita da


seta de atribuição. Assim, sempre que qualquer um desses valores mudar, a atribuição condicional é
reavaliada e uma nova transação agendada no driver para o sinal de destino.

Se olharmos mais de perto o modelo do multiplexador, notamos que a última condição é redundante,
pois os sinais sel0 e sel1 são do tipo bit. Se nenhuma das condições anteriores for verdadeira, o sinal deve
sempre ser atribuído à última forma de onda. Assim, podemos reescrever o exemplo como:

z <=d0 quando sel1 = '0' e sel0 = '0' else d1


quando sel1 = '0' e sel0 = '1' else d2
quando sel1 = '1' e sel0 = '0' else d3;

Um caso muito comum na modelagem de funções é escrever uma atribuição de sinal condicional
sem condições, como no exemplo a seguir:

PC_incr : next_PC <= PC + 4 após 5 ns;


Machine Translated by Google

52 Construções básicas de modelagem

Declarações de Atribuição de Sinal Selecionado

A instrução de atribuição de sinal selecionada é semelhante em muitos aspectos à instrução condicional


instrução de atribuição de sinal. É um atalho para um processo que contém um número de
atribuições de sinal comuns dentro de uma instrução case. A regra de sintaxe é

selected_signal_assignment ÿ
com seleção de expressão
nome <= { forma de onda quando escolhas , }
forma de onda quando escolhas ;

Esta declaração nos permite escolher entre um número de formas de onda para ser tão
assinado para um sinal dependendo do valor de uma expressão. Um exemplo é:

com alu_function select


resultado <= a + b após Tpd a quando alu_add | alu_add_unsigned,
– b após Tpd a quando alu_sub | alu_sub_unsigned,
e b após Tpd quando alu_and,
a ou b após Tpd quando alu_or,
a após Tpd quando alu_pass_a;

Uma instrução de atribuição de sinal selecionada é sensível a todos os sinais na expressão seletora e
nas expressões à direita da seta de atribuição. Esse
significa que a atribuição de sinal selecionada acima é sensível a alu_function, a e b.

4.4 Descrições Estruturais


Uma descrição estrutural de um sistema é expressa em termos de subsistemas interligados por sinais. Cada
subsistema pode, por sua vez, ser composto por uma interconexão de subsistemas, e assim por diante, até
chegarmos finalmente a um nível que consiste em componentes primitivos, descritos puramente em termos
de seu comportamento. Assim, o sistema de nível superior pode ser
considerado como tendo uma estrutura hierárquica. Nesta seção, veremos como escrever
corpos de arquitetura estrutural para expressar essa organização hierárquica.

Instanciação de Entidades e Mapas de Portas

Vimos anteriormente neste capítulo que as declarações concorrentes em uma arquitetura


body descrevem uma implementação de uma interface de entidade. Para escrever uma estrutura
implementação, devemos usar uma instrução concorrente chamada instrução de instanciação de componente ,
cuja forma mais simples é governada pela regra de sintaxe

instrução_instanciação_componente ÿ
instanciação_label :
entidade entity_name ( architecture_identifier )
mapa de portas ( port_association_list );

Esta forma de instrução de instanciação de componentes executa instanciação direta de


uma entidade. Podemos pensar na instanciação do componente como a criação de uma cópia do nome
entidade, com o corpo de arquitetura correspondente substituído pelo componente in-
Machine Translated by Google

Descrições Estruturais 53

posição. O mapa de portas especifica quais portas da entidade estão conectadas a quais sinais no
corpo da arquitetura envolvente. A regra de sintaxe simplificada para uma lista de associação de
porta é

port_association_list ÿ
( [ port_name => ] signal_name ) { , … }

Cada elemento na lista de associações associa uma porta da entidade a um sinal do corpo da
arquitetura envolvente.
Vejamos alguns exemplos para ilustrar instruções de instanciação de componentes e
a associação de portas com sinais. Suponha que temos uma entidade declarada como

entidade DRAM_controller é
porta (rd, wr, mem: bit de
entrada ; ras, cas, we, ready: bit de saída );
entidade final DRAM_controller;

e uma arquitetura correspondente chamada fpld. Podemos criar uma instância dessa entidade da
seguinte forma:

main_mem_controller : entidade work.DRAM_controller(fpld) mapa de


portas ( cpu_rd, cpu_wr, cpu_mem,
mem_ras, mem_cas, mem_we, cpu_rdy );

Neste exemplo, o nome trabalho refere-se à biblioteca de trabalho atual na qual as entidades e
os corpos de arquitetura estão armazenados. Voltaremos ao tópico das bibliotecas na próxima seção.
O mapa de portas deste exemplo lista os sinais no corpo da arquitetura envolvente aos quais as
portas da cópia da entidade estão conectadas. A associação posicional é usada: cada sinal listado
no mapa de portas é conectado à porta na mesma posição na declaração da entidade. Assim, o sinal
cpu_rd está conectado à porta rd, o sinal cpu_wr está conectado à porta wr e assim por diante.

Uma maneira melhor de escrever uma instrução de instanciação de componente é usar named
association, conforme mostrado no exemplo a seguir:

main_mem_controller : entidade work.DRAM_controller(fpld) mapa de


portas ( rd => cpu_rd, wr => cpu_wr, mem => cpu_mem, ready
=> cpu_rdy, ras => mem_ras, cas => mem_cas, we
=> mem_we );

Aqui, cada porta é explicitamente nomeada junto com o sinal ao qual está conectada. A ordem em
que as conexões são listadas é irrelevante.

EXEMPLO

Na Figura 4-2, observamos um modelo comportamental de um flip-flop acionado por borda.


Podemos usar o flip-flop como base de um registrador de 4 bits disparado por borda. A Figura
4-3 mostra a declaração de entidade e um corpo de arquitetura estrutural.
Machine Translated by Google

54 Construções básicas de modelagem

FIGURA 4-3

entidade reg4
é porta (clk, clr, d0, d1, d2, d3: bit de entrada; q0, q1, q2, q3: bit de
saída ); entidade final reg4;
––––––––––––––––––––––––––––––––––––––––––––––––––– ––

estrutura de arquitetura de reg4 é


begin bit0 : entidade
work.edge_triggered_Dff(comportamental) mapa de portas
(d0, clk, clr, q0); bit1 : entidade
work.edge_triggered_Dff(comportamental) mapa de portas
(d1, clk, clr, q1); bit2 : mapa de portas da entidade
work.edge_triggered_Dff(comportamental) (d2, clk, clr, q2);
bit3 : entidade work.edge_triggered_Dff(comportamental)
mapa de portas (d3, clk, clr, q3);

estrutura de arquitetura final ;

Uma entidade e um corpo de arquitetura estrutural para um registrador de 4 bits disparado por borda, com uma entrada
assíncrona clara.

Podemos usar a entidade registradora, juntamente com outras entidades, como parte de uma arquitetura
estrutural para o contador decimal de dois dígitos representado pelo esquema da Figura 4-4.

Suponha que um dígito seja representado como um vetor de bits de comprimento quatro, descrito pela
declaração do subtipo

dígito do subtipo é bit_vector(3 até 0 );

A Figura 4-5 mostra a declaração de entidade para o contador, juntamente com um esboço do corpo da
arquitetura estrutural. Este exemplo ilustra vários pontos importantes sobre instâncias de componentes e
mapas de portas. Primeiro, as duas instâncias de componentes val0_reg e val1_reg são ambas instâncias do
mesmo par entidade/arquitetura. Isso significa que duas cópias distintas da estrutura de arquitetura de reg4
são criadas, uma para cada uma das instâncias do componente. Voltamos a este ponto quando discutimos o
tema da elaboração na próxima seção. Em segundo lugar, em cada um dos mapas de portas, as portas da
entidade que está sendo instanciada estão associadas a elementos separados de sinais de matriz. Isso é
permitido, pois um sinal do tipo composto, como um array, pode ser tratado como uma coleção de sinais, um
por elemento. Terceiro, alguns dos sinais conectados às instâncias do componente são sinais declarados
dentro do corpo da arquitetura envolvente, registrados, enquanto o sinal clk é uma porta do contador de
entidade . Isso ilustra novamente o ponto em que dentro de um corpo de arquitetura, as portas da entidade
correspondente são tratadas como sinais.

FIGURA 4-5

contador de entidade é
porta ( clk, clr : bit de
entrada ; q0, q1 : dígito de
saída ); contador de entidade final ;
Machine Translated by Google

Descrições Estruturais 55

FIGURA 4-4

incr0 val0_reg buf0

add_1 reg4 buf4


d0 y0 y1y2 d0 q0 q1 a0 a0 q0(0)
d1 y3 d1 q2 a1 a1 q0(1)
d2 d2 q3 a2 a2 q0(2)
d3 d3 a3 a3 q0(3)
clr
clique

incr1 val1_reg buf1

add_1 reg4 buf4


d0 y0 y1y2 d0 q0 q1 a0 a0 q1(0)
d1 y3 d1 q2 a1 a1 q1(1)
d2 d2 q3 a2 a2 q1(2)
d3 d3 a3 a3 q1(3)
clr
clique

clr
clique

Um esquema para um contador de dois dígitos usando a entidade reg4 .

––––––––––––––––––––––––––––––––––––––––––––––––––– ––

arquitetura cadastrada de contador é


sinal current_val0, current_val1, next_val0, next_val1 : digit;
begin val0_reg: entidade work.reg4(struct) port map ( d0 =>
next_val0(0), d1 => next_val0(1), d2 => next_val0(2), d3 =>
next_val0(3), q0 => current_val0( 0), q1 => valor_atual0(1),
q2 => valor_atual0(2), q3 => valor_atual0(3),
clk => clk, clr => clr ); val1_reg : entidade
work.reg4(struct) mapa de portas ( d0 =>
next_val1(0), d1 => next_val1(1), d2 =>
next_val1(2), d3 => next_val1(3), q0 => current_val1(0 ), q1 =>
current_val1(1), q2 => current_val1(2), q3 => current_val1(3),
clk => clk, clr => clr );
Machine Translated by Google

56 Construções básicas de modelagem

incr0 : entidade trabalho.add_1(boolean_eqn) …;


incr1 : entidade trabalho.add_1(boolean_eqn) …;
buf0 : entidade trabalho.buf4(básico) …; buf1 :
entidade trabalho.buf4(básico) …; arquitetura
final registrada;

Uma declaração de entidade de um contador decimal de dois dígitos, com um esboço de um corpo de arquitetura usando a
entidade reg4 .

4.5 Bibliotecas e Cláusulas de Biblioteca


Anteriormente, mencionamos que um projeto é analisado para verificar se ele está de acordo com as regras de
sintaxe e semântica do VHDL. Uma unidade de projeto analisada, como uma declaração de entidade ou corpo de
arquitetura, é colocada em uma biblioteca de projeto. Um conjunto de ferramentas VHDL deve fornecer alguns meios
de usar várias bibliotecas de design separadas. Quando um projeto é analisado, nomeamos uma das bibliotecas
como biblioteca de trabalho, e o projeto analisado é armazenado nesta biblioteca. Usamos o nome de biblioteca
especial work em nossos modelos VHDL para fazer referência à biblioteca de trabalho atual. Vimos exemplos disso
nas instruções de instanciação de componentes deste capítulo, nas quais uma entidade analisada anteriormente é
instanciada em um corpo de arquitetura estrutural.

Se precisarmos acessar unidades de biblioteca armazenadas em outras bibliotecas, nos referimos às bibliotecas
como bibliotecas de recursos. Fazemos isso incluindo uma cláusula de biblioteca imediatamente anterior a uma
unidade de design que acessa as bibliotecas de recursos. A regra de sintaxe para uma cláusula de biblioteca é

library_clause ÿ identificador de biblioteca { , … } ;

Os identificadores são usados pelo analisador e pelo sistema operacional hospedeiro para localizar as
bibliotecas de projeto, de forma que as unidades contidas neles possam ser utilizadas na descrição que está sendo
analisada. A maneira exata como os identificadores são usados varia entre diferentes conjuntos de ferramentas e
não é definida pela especificação da linguagem VHDL. Observe que não precisamos incluir o trabalho do nome da
biblioteca em uma cláusula de biblioteca; a biblioteca de trabalho atual fica automaticamente disponível.

EXEMPLO

Suponha que estamos trabalhando em parte de um grande projeto de design chamado Wasp, e estamos
usando partes de células padrão fornecidas pela Widget Designs, Inc. Nosso administrador de sistema carregou
a biblioteca de design para as células Widget em um diretório chamado /local/widget /cells em nosso sistema
de arquivos de estação de trabalho, e nosso líder de projeto configurou outra biblioteca de design em /projects/
wasp/lib para algumas células internas que precisamos usar. Consultamos o manual do nosso analisador
VHDL e usamos comandos do sistema operacional para configurar o mapeamento apropriado dos identificadores
widget_cells e wasp_lib para esses diretórios de biblioteca. Podemos então instanciar entidades dessas
bibliotecas, juntamente com entidades que analisamos anteriormente, em nossa própria biblioteca de trabalho,
conforme mostrado na Figura 4-6.
Machine Translated by Google

Bibliotecas e cláusulas de biblioteca 57

FIGURA 4-6

biblioteca widget_cells, wasp_lib;


arquitetura cell_based do filtro é ––
declaração de sinais, etc

begin
clk_pad : entidade wasp_lib.in_pad
mapa de portas ( i => clk, z => filter_clk );
acumulo : entidade widget_cells.reg32
mapa de portas ( pt => accum_pt, clk => filter_clk, d => soma, q
=> resultado ); alu : entidade work.adder mapa de
portas ( a => alu_op1, b => alu_op2, y => soma, c => carry );

–– outras instanciações de componentes


final da arquitetura cell_based;

Um esboço de uma unidade de biblioteca referindo-se a entidades das bibliotecas de recursos widget_cells e wasp_lib.
Machine Translated by Google
Machine Translated by Google

5
Subprogramas

5.1 Procedimentos
Começamos nossa discussão de subprogramas com procedimentos. Existem dois aspectos no uso de
procedimentos em um modelo: primeiro o procedimento é declarado e, em outro lugar, o procedimento
é chamado. A regra de sintaxe para uma declaração de procedimento é

subprogram_body ÿ
identificador de procedimento [ ( parameter_interface_list ) ]
é { subprogram_declarative_part } begin
{ sequencial_statement } end [ procedure ] [ identificador ] ;

Por enquanto, veremos apenas os procedimentos sem a parte da lista de parâmetros; voltaremos aos
parâmetros na próxima seção.
O identificador em uma declaração de procedimento nomeia o procedimento. O nome pode ser
repetido ao final da declaração do procedimento. As instruções sequenciais no corpo de um
procedimento implementam o algoritmo que o procedimento deve executar e podem incluir qualquer
uma das instruções sequenciais que vimos nos capítulos anteriores.
Um procedimento pode declarar itens em sua parte declarativa para uso nas instruções do corpo do
procedimento. As declarações podem incluir tipos, subtipos, constantes, variáveis e declarações de
subprogramas aninhados. Os itens declarados não são acessíveis fora do procedimento; dizemos que
são locais ao procedimento.
As ações de um procedimento são invocadas por uma instrução de chamada de procedimento ,
que é mais uma instrução sequencial VHDL. Um procedimento sem parâmetros é chamado
simplesmente escrevendo seu nome, conforme mostrado pela regra de sintaxe

procedure_call_statement ÿ procedure_name ;

Quando a última instrução do procedimento for concluída, o procedimento retornará.


Podemos escrever uma declaração de procedimento na parte declarativa de um corpo de
arquitetura ou de um processo. Se um procedimento estiver incluído na parte declarativa de um corpo
de arquitetura, ele poderá ser chamado de dentro de qualquer um dos processos no corpo de
arquitetura. Por outro lado, declarar um procedimento dentro de um processo o esconde do uso por outros
são processos.

59
Machine Translated by Google

60 Subprogramas

EXEMPLO

O esquema na Figura 5-1 ilustra um procedimento para operações aritméticas definidas


dentro de um processo. O processo alu invoca do_arith_op com uma chamada de procedimento
demonstração.

FIGURA 5-1

arquitetura rtl de control_processor é tipo


func_code é (adicionar, subtrair); sinal
op1, op2, dest : inteiro; sinal Z_flag :
boolean; sinal func : func_code;

begin
alu : o processo é
procedimento do_arith_op é
variável resultado : inteiro;
começar a função do caso é

quando
adicionar => resultado :=
op1 + op2; quando subtrair
=> resultado := op1 –
op2; caso final; dest <= resultado
após Tpd;
Z_flag <= resultado = 0 após Tpd;
fim do procedimento do_arith_op;
começar

do_arith_op;

processo final alu;

arquitetura final rtl;

Um esboço de um corpo de arquitetura com um processo contendo um procedimento. O procedimento encapsula


parte do comportamento do processo e é invocado pela instrução de chamada de procedimento dentro do processo.

5.2 Parâmetros do Procedimento


Agora que examinamos o básico dos procedimentos, discutiremos os procedimentos que incluem
parâmetros. Quando escrevemos um procedimento parametrizado, incluímos informações na lista
de parâmetros sobre os parâmetros a serem passados para o procedimento. A regra de sintaxe
para uma declaração de procedimento na página 59 mostra onde a lista de parâmetros se encaixa.
A seguir está a regra de sintaxe para uma lista de parâmetros:
Machine Translated by Google

Parâmetros de procedimento 61

lista_interface ÿ
( [ constante I variável I sinal ]
identificador { , … } : [ modo ]
subtipo_indicação
[ := expressão_estática ] ) { ; … }
mode ÿ in I out I inout

A lista de parâmetros define os parâmetros formais do procedimento. Podemos pensar neles como espaços
reservados que representam os parâmetros reais, que devem ser fornecidos pelo chamador quando ele
chama o procedimento. Como a regra de sintaxe para uma lista de parâmetros é bastante complexa,
vamos começar com alguns exemplos simples e trabalhar a partir deles.

EXEMPLO

Primeiro, vamos reescrever o procedimento do_arith_op, da Figura 5-1, para que o código da
função seja passado como parâmetro. A nova versão é mostrada na Figura 5-2. Na lista de interfaces
de parâmetros, identificamos um parâmetro formal chamado op.
O modo do parâmetro formal está em, indicando que ele é usado para passar informações do
chamador para o procedimento. O tipo do parâmetro é func_code, indicando que as operações
realizadas no parâmetro formal devem ser apropriadas para um valor desse tipo e que o chamador
só pode passar um valor desse tipo como parâmetro real.

Agora que parametrizamos o procedimento, podemos chamá-lo de lugares diferentes passando


códigos de função diferentes a cada vez. Por exemplo, uma chamada em um lugar pode ser

do_arith_op (adicionar);

A chamada de procedimento simplesmente inclui o valor real do parâmetro entre parênteses. Neste
caso passamos o valor literal add como parâmetro real. Em outro lugar do modelo podemos passar o
valor da função do sinal mostrado no modelo da Figura 5-1:

do_arith_op(func);
Machine Translated by Google

62 Subprogramas

FIGURA 5-2

procedimento do_arith_op ( op : em func_code ) é


variável result : integer; start case op é quando
add => result := op1 + op2; quando subtrair =>
resultado := op1 – op2; caso final; dest <=
resultado após Tpd;

Z_flag <= resultado = 0 após Tpd;


fim do procedimento do_arith_op;

Um procedimento para realizar uma operação aritmética, parametrizado pelo tipo de operação.

A regra de sintaxe para uma lista de parâmetros também nos mostra que podemos especificar a
classe de um parâmetro formal, ou seja, se é uma constante, uma variável ou um sinal dentro do
procedimento. Se o modo do parâmetro estiver em, a classe será considerada constante, pois uma
constante é um objeto que não pode ser atualizado por atribuição. Normalmente, simplesmente
deixamos de fora a palavra-chave constante, contando com o modo para deixar nossas intenções claras.
Para um parâmetro de classe constante no modo, escrevemos uma expressão como o parâmetro real
éter.
Passemos agora aos parâmetros formais de mode out. Tal parâmetro nos permite trans
fornecer informações do procedimento de volta para o chamador. Aqui está um exemplo.

EXEMPLO

O procedimento na Figura 5-3 realiza a adição de dois números sem sinal representados
como vetores de bits do tipo word32, que supomos estar definido em outro lugar.
O procedimento tem dois parâmetros de modo a e b, permitindo que o chamador passe dois
valores de vetor de bits. O procedimento usa esses valores para calcular a soma e o sinalizador
de estouro. Dentro do procedimento, os dois parâmetros do modo de saída , resultado e estouro,
aparecem como variáveis. O procedimento realiza atribuições de variáveis para atualizar seus
valores, transferindo assim as informações de volta para o chamador.

FIGURA 5-3

procedimento addu ( a, b : em word32;


resultado: fora word32; overflow : out boolean ) é
soma variável : word32;
variável carry : bit := '0';
begin for index in
sum'reverse_range loop sum(index) :=
a(index) xor b(index) xor carry; transportar :=
(a(índice) e b(índice)) ou (carregar e (a(índice) xou b(índice))); laço final;
resultado := soma;
Machine Translated by Google

Parâmetros de procedimento 63

estouro := transporte =
'1'; procedimento final addu;

Um procedimento para adicionar dois vetores de bits representando inteiros sem sinal.

Uma chamada para este procedimento pode aparecer da seguinte forma:

variável PC, next_PC : word32;


variável overflow_flag : boolean;

addu( PC, X"0000_0004", next_PC, overflow_flag);

No exemplo acima, os parâmetros do modo de saída são da variável de classe . Como


essa classe é assumida para parâmetros out , geralmente deixamos de fora a variável de
especificação de classe. O modo out indica que a única maneira de o procedimento usar os
parâmetros formais é atualizá-los por atribuição de variáveis; ele pode não ler os valores dos
parâmetros. Para um modo de saída , parâmetro de classe variável, o chamador deve fornecer
uma variável como um parâmetro real.
O terceiro modo que podemos especificar para parâmetros formais é inout, que é uma
combinação dos modos in e out . Ele é usado para objetos que devem ser lidos e atualizados
por um procedimento. Assim como os parâmetros out , eles são considerados variáveis de
classe se a classe não for declarada explicitamente. Para parâmetros de variáveis do modo
inout , o chamador fornece uma variável como um parâmetro real. O valor desta variável é
utilizado para inicializar o parâmetro formal, que pode então ser utilizado nas instruções do procedimento.
O procedimento também pode realizar atribuições de variáveis para atualizar o parâmetro
formal. Quando o procedimento retorna, o valor do parâmetro formal é copiado de volta para a
variável de parâmetro real, transferindo as informações de volta para o chamador.

Parâmetros de sinal
A terceira classe de objeto que podemos especificar para parâmetros formais é o sinal, que
indica que o algoritmo executado pelo procedimento envolve um sinal passado pelo chamador.
Um parâmetro de sinal pode ser de qualquer um dos modos in, out ou inout.
A maneira como os parâmetros de sinal funcionam é um pouco diferente dos parâmetros
constantes e variáveis. Quando um chamador passa um sinal como parâmetro de modo em, ao
invés de passar o valor do sinal, ele passa o próprio objeto sinal. Qualquer referência ao
parâmetro formal dentro do procedimento é exatamente como uma referência ao próprio sinal
real.

EXEMPLO

Suponha que desejamos modelar a parte receptora de uma interface de rede. Ele
recebe pacotes de dados de comprimento fixo no sinal rx_data. Os dados são sincronizados
com as mudanças, de '0' para '1', do sinal de clock rx_clock. A Figura 5-4 é um esboço de
parte do modelo.
Machine Translated by Google

64 Subprogramas

FIGURA 5-4

arquitetura comportamental do receptor é


… –– declarações de tipo, etc
sinal recuperado_dados : bit; sinal
recuperado_clock : bit;

procedimento receive_packet ( sinal rx_data : em bit; sinal


rx_clock : em bit; data_buffer :
out packet_array ) é

começar para índice no loop packet_index_range


esperar até rx_clock = '1';
data_buffer(index) := rx_data; laço final;
fim do procedimento receive_packet; begin
packet_assembler : o processo é

pacote variável : packet_array; começar


receive_packet (recuperado_dados, recuperado_relógio, pacote);

finalize o processo packet_assembler;

arquitetura final comportamental;

Um esboço de um modelo de um receptor de rede, incluindo um procedimento com parâmetros de sinal de modo in.

Durante a execução do modelo, o processo packet_assembler chama o procedimento


receive_packet, passando os sinais recuperado_dados e recuperado_relógio como
parâmetros reais. A instrução wait menciona rx_clock e, como isso significa recuperado_clock,
o processo é sensível a alterações em recuperado_clock enquanto está suspenso.

Agora vamos ver os parâmetros de sinal do modo de saída. Nesse caso, o chamador deve
nomear um sinal como o parâmetro real e o procedimento recebe uma referência ao driver para
o sinal. Quando o procedimento executa uma instrução de atribuição de sinal no parâmetro
formal, as transações são agendadas no driver para o parâmetro de sinal real.

EXEMPLO

A Figura 5-5 é um esboço de um corpo de arquitetura para um gerador de sinal. O


procedimento generate_pulse_train tem parâmetros constantes de modo que especificam
as características de um trem de pulsos e um parâmetro de sinal de modo de saída no qual
ele gera o trem de pulsos necessário. O processo raw_signal_generator chama o processo
Machine Translated by Google

Parâmetros de procedimento 65

dure, fornecendo raw_signal como o parâmetro de sinal real para s. Uma referência ao driver
para raw_signal é passada para o procedimento e as transações são geradas nele.

FIGURA 5-5

biblioteca ieee; use ieee.std_logic_1164.all;


arquitetura top_level do signal_generator é
sinal raw_signal : std_ulogic;

procedimento generate_pulse_train ( largura, separação : em delay_length;


número : em natural; sinal s : out
std_ulogic ) é

começar para contagem em 1 para


numerar loop s <= '1', '0'
após largura; aguarde largura
+ separação; laço final;
procedimento final generate_pulse_train;
begin raw_signal_generator : o processo é
iniciado


generate_pulse_train ( largura => período / 2,
separação => período – período / 2,
número => pulso_count, s =>
raw_signal );

final do processo raw_signal_generator;

final arquitetura top_level;

Um esboço de um modelo para um gerador de sinal, incluindo um procedimento de gerador de pulso com um parâmetro de
sinal de modo de saída .

Tal como acontece com os parâmetros de classe variável, também podemos ter um parâmetro
de classe de sinal de modo inout. Quando o procedimento é chamado, o sinal e uma referência ao
seu driver são passados para o procedimento. As instruções dentro dele podem ler o valor do sinal,
incluí-lo em listas de sensibilidade em instruções de espera, consultar seus atributos e agendar
transações usando instruções de atribuição de sinal.

Valores padrão

A única parte restante de uma lista de parâmetros de procedimento que ainda temos que discutir é
a expressão de valor padrão opcional, mostrada na regra de sintaxe na página 61. Observe que só
podemos especificar um valor padrão para um parâmetro formal de mode in, e o parâmetro deve
ser da classe constante ou variável. Se incluirmos um valor padrão em um
Machine Translated by Google

66 Subprogramas

especificação do parâmetro, temos a opção de omitir um valor real quando o procedimento é chamado. Podemos
usar a palavra-chave open no lugar de um valor de parâmetro real ou, se o valor real estiver no final da lista de
parâmetros, simplesmente deixe-o de fora. Se omitirmos um valor real, o valor padrão será usado.

Parâmetros de matriz sem restrições


Em um capítulo anterior, descrevemos tipos de array irrestritos, nos quais o intervalo de índice do array foi
deixado sem especificação usando a notação “box” (“<>”) . Para esse tipo, restringimos os limites do índice
quando criamos um objeto, como uma variável ou um sinal. Outro uso de um tipo de array irrestrito é como o tipo
de parâmetro formal para procedimento. Esse uso nos permite escrever um procedimento de maneira geral, para
que ele possa operar em valores de array de qualquer tamanho ou com qualquer intervalo de valores de índice.
Quando chamamos o procedimento e fornecemos um array restrito como o parâmetro real, os limites do índice
do array real são usados como os limites do parâmetro formal do array.

EXEMPLO

A Figura 5-6 é um procedimento que encontra o índice do primeiro bit definido como '1' em um vetor
de bits. O parâmetro formal v é do tipo bit_vector, que é um tipo de array irrestrito. Observe que ao escrever
este procedimento, não nos referimos explicitamente aos limites de índice do parâmetro formal v, pois eles
não são conhecidos. Em vez disso, usamos o atributo 'range .

FIGURA 5-6

procedure find_first_set ( v : in bit_vector; found :


out boolean;
first_set_index : out natural ) é

comece para índice no loop


v'range se v(index) = '1'
então encontrado :=
true; first_set_index :=
índice; Retorna; fim se;
laço final; encontrado := falso;
fim do procedimento find_first_set;

Um procedimento para encontrar o primeiro bit definido em um vetor de bits.

Quando o procedimento é executado, os parâmetros formais representam o real


parâmetros fornecidos pelo chamador. Então, se chamarmos esse procedimento da seguinte forma:

variável int_req : bit_vector (7 até 0 ); variável


top_priority : natural; variável int_pending : boolean;

find_first_set ( int_req, int_pending, top_priority );


Machine Translated by Google

Funções 67

v'range retorna o intervalo 7 até 0, que é usado para garantir que o índice do parâmetro do
loop itere sobre os valores de índice corretos para v. Se fizermos uma chamada diferente:

variável free_block_map : bit_vector(0 to block_count–1);


variável first_free_block : natural; variável free_block_found :
boolean;

find_first_set ( free_block_map, free_block_found, first_free_block );

v'range retorna o intervalo de índice do array free_block_map, pois esse é o parâmetro real
correspondente a v.

5.3 Funções
Vamos agora voltar nossa atenção para o segundo tipo de subprograma em VHDL: funções.
A regra de sintaxe para uma declaração de função é muito semelhante à de uma declaração de
procedimento:

subprogram_body ÿ
identificador de função [ ( parameter_interface_list ) ] return type_mark é
{ subprogram_declarative_item }
begin { sequencial_statement } end
[ function ] [ identificador ] ;

O identificador na declaração nomeia a função. Pode ser repetido no final da declaração. Ao


contrário de um subprograma de procedimento, uma função calcula e retorna um resultado que pode
ser usado em uma expressão. A declaração da função especifica o tipo do resultado após o retorno da
palavra-chave. A lista de parâmetros de uma função tem a mesma forma que a de um procedimento,
com duas restrições. Primeiro, os parâmetros de uma função podem não ser da variável de classe. Se
a classe não for explicitamente mencionada, ela será considerada constante. Segundo, o modo de cada
parâmetro deve estar in. Se o modo não for especificado explicitamente, supõe-se que esteja in. Como
um procedimento, uma função pode declarar itens locais em sua parte declarativa para uso nas
instruções no corpo da função.

Uma função passa o resultado de sua computação de volta para seu chamador usando um retorno
instrução, dada pela regra de sintaxe

declaração_de_retorno ÿ expressão de retorno ;

Uma função deve incluir pelo menos uma instrução de retorno. O primeiro a ser executado faz com que
a função seja concluída e retorne seu resultado para o chamador. Uma função não pode simplesmente
executar no final do corpo da função, pois fazer isso não forneceria uma maneira de especificar um
resultado para retornar ao chamador.
Uma chamada de função se parece exatamente com uma chamada de procedimento. A regra de sintaxe é

function_call ÿ function_name [ ( parameter_association_list ) ]


Machine Translated by Google

68 Subprogramas

A diferença é que uma chamada de função é parte de uma expressão, em vez de ser uma instrução
sequencial por conta própria, como uma chamada de procedimento.

EXEMPLO

A função na Figura 5-7 determina o número representado em binário por um valor de vetor de
bits.

FIGURA 5-7

função bv_to_natural ( bv : em bit_vector ) return natural é


variável resultado : natural := 0;
start for index in bv'range loop 2 +
bit'pos(bv(index));
*
resultado :=
resultado final do
loop; retorno resultado;
função final bv_to_natural;

Uma função que converte a representação binária de um número sem sinal em um valor numérico.

Como exemplo de uso desta função, considere um modelo para um mem somente leitura
ory, que representa os dados armazenados como uma matriz de vetores de bits, como segue:

type rom_array é array ( intervalo natural de 0 a rom_size–1) de


bit_vector(0 a word_size–1);
variável rom_data : rom_array;

Se o modelo possui uma porta de endereço que é um vetor de bits, podemos usar a função para
converter o endereço em um valor natural para indexar o array de dados da ROM, como segue:

data <= rom_data ( bv_to_natural(endereço) ) após Taccess;


Machine Translated by Google

6
Pacotes e Cláusulas de Uso

6.1 Declarações e Órgãos do Pacote


Um pacote VHDL é simplesmente uma maneira de agrupar uma coleção de declarações
relacionadas que atendem a um propósito comum. Eles nos permitem separar a visão externa dos
itens que eles declaram da implementação desses itens. A visão externa é especificada em uma
declaração de pacote, enquanto a implementação é definida em um corpo de pacote separado. A
regra de sintaxe para escrever uma declaração de pacote é

package_declaration ÿ
identificador de pacote
é { package_declarative_item }
end [ pacote ] [ identificador ] ;

O identificador fornece um nome para o pacote, que podemos usar em outro lugar em um
modelo para fazer referência ao pacote. Dentro da declaração do pacote escrevemos uma coleção
de declarações, incluindo declarações de tipo, subtipo, constante, sinal e subprograma. Estas são
as declarações que são fornecidas aos usuários do pacote.
A Figura 6-1 é um exemplo simples de uma declaração de pacote.

FIGURA 6-1

o pacote cpu_types é
constante word_size : positive := 16;
constante address_size : positivo := 24;
palavra do subtipo é bit_vector (word_size – 1 até 0);
o endereço do subtipo é bit_vector(address_size – 1 até 0);
tipo status_value é ( interrompido, ocioso, busca, mem_read,
mem_write, io_read, io_write, int_ack );
final do pacote cpu_types;

Um pacote que declara algumas constantes e tipos úteis para um modelo de CPU.

Um pacote é outra forma de unidade de design, juntamente com declarações de entidade e


corpos de arquitetura. Ele é analisado separadamente e colocado na biblioteca de trabalho como
uma unidade de biblioteca pelo analisador. A partir daí, outras unidades de biblioteca podem fazer
referência a um item declarado no pacote usando o nome selecionado do item. O nome selecionado é

69
Machine Translated by Google

70 Pacotes e Cláusulas de Uso

formado escrevendo o nome da biblioteca, depois o nome do pacote e depois o nome do


item, todos separados por pontos; por exemplo:

work.cpu_types.status_value

Subprogramas em Declarações de Pacotes


Outro tipo de declaração que pode ser incluída em uma declaração de pacote é uma declaração de
subprograma - uma declaração de procedimento ou de função. Um aspecto importante de declarar
um subprograma em uma declaração de pacote é que escrevemos apenas o
cabeçalho do subprograma, ou seja, a parte que inclui o nome e a interface
list definindo os parâmetros (e tipo de resultado para funções). Deixamos de fora o corpo de
o subprograma. Por exemplo, suponha que temos uma declaração de pacote que define
um subtipo de vetor de bits:

subtipo word32 é bit_vector (31 até 0);

Podemos incluir no pacote um procedimento para adicionar valores de word32 que representam
inteiros com sinal. A declaração do procedimento na declaração do pacote é

procedimento add ( a, b : em word32;


resultado: fora word32; estouro: out boolean);

Observe que não incluímos a palavra-chave is ou qualquer uma das declarações locais ou
instruções necessárias para realizar a adição; estes são adiados para o corpo do pacote.
Cada declaração de pacote que inclui declarações de subprograma deve ter um corpo de pacote
correspondente para preencher os detalhes que faltam. No entanto, se uma declaração de pacote
inclui apenas outros tipos de declarações, como tipos, sinais ou declarações totalmente especificadas
constantes, nenhum corpo de pacote é necessário. A regra de sintaxe para o corpo do pacote é semelhante
à da interface, mas com a inclusão da palavra-chave body:

package_body ÿ
identificador do corpo do pacote é
{ package_body_declarative_item }
end [ corpo do pacote ] [ identificador ] ;

Os itens declarados em um corpo de pacote devem incluir as declarações completas de todos


os subprogramas definidos na declaração do pacote correspondente. Estas declarações completas
deve incluir os cabeçalhos do subprograma exatamente como estão escritos na declaração do
pacote. Um corpo de pacote também pode incluir declarações de tipos adicionais, subtipos,
constantes e subprogramas. Esses itens são usados para implementar os subprogramas definidos
na declaração do pacote. Observe que os itens declarados na declaração do pacote não podem
ser declarados novamente no corpo (além dos subprogramas e
constantes, conforme descrito acima), uma vez que são automaticamente visíveis no corpo.

EXEMPLO

A Figura 6-2 mostra os contornos de uma declaração de pacote e um corpo de pacote


declarando funções aritméticas para valores de vetores de bits. As funções tratam vetores de bits
Machine Translated by Google

Cláusulas de uso 71

como representando inteiros com sinal em forma binária. Apenas os cabeçalhos das
funções são incluídos na declaração do pacote. O corpo do pacote contém os corpos de
função completos. Também inclui uma função, mult_unsigned, não definida na declaração
do pacote. Ele é usado internamente no corpo do pacote para implementar o operador de
multiplicação assinado.

FIGURA 6-2

pacote bit_vector_signed_arithmetic é
função add ( bv1, bv2 : bit_vector ) return bit_vector;
função sub ( bv : bit_vector ) return bit_vector; função
mult ( bv1, bv2 : bit_vector ) return bit_vector;

fim do pacote bit_vector_signed_arithmetic;
––––––––––––––––––––––––––––––––––––––––––––––––––– ––

corpo do pacote bit_vector_signed_arithmetic é


função add ( bv1, bv2 : bit_vector ) return bit_vector is …
function sub ( bv : bit_vector ) return bit_vector is … function
mult_unsigned ( bv1, bv2 : bit_vector ) return bit_vector is

começar

fim da função mult_unsigned;
função mult ( bv1, bv2 : bit_vector ) return bit_vector é start
se bv1(bv1'left) = '0' e bv2(bv2'left) = '0' então return
mult_unsigned(bv1, bv2); elsif bv1(bv1'left) = '0' e
bv2(bv2'left) = '1' então retorne –mult_unsigned(bv1,
–bv2); elsif bv1(bv1'left) = '1' e bv2(bv2'left) = '0' então
retorne –mult_unsigned(–bv1, bv2); senão return
mult_unsigned(–bv1, –bv2); fim se; função final mult;


fim do corpo do pacote bit_vector_signed_arithmetic;

Um esboço de uma declaração de pacote e corpo que definem funções aritméticas assinadas em inteiros
representados como vetores de bits.

6.2 Cláusulas de Uso

Vimos como podemos fazer referência a um item fornecido por um pacote escrevendo seu
nome selecionado, por exemplo, work.cpu_types.status_value. Este nome refere-se ao item
status_value no pacote cpu_types armazenado na biblioteca de trabalho. Se precisarmos nos referir
Machine Translated by Google

72 Pacotes e Cláusulas de Uso

para este objeto em muitos lugares em um modelo, ter que escrever o nome da biblioteca e o nome do
pacote torna-se tedioso e pode obscurecer a intenção do modelo. Podemos escrever uma cláusula use
para tornar os nomes do pacote diretamente visíveis. As regras de sintaxe são:

use_clause ÿ use selected_name { , … } ;


nome_selecionado ÿ identificador . identificador . ( identificador I todos )

Em uma forma de nome selecionado, o primeiro identificador é um nome de biblioteca e o segundo


é o nome de um pacote dentro da biblioteca. Este formulário nos permite fazer referência diretamente
aos itens dentro de um pacote sem ter que usar o nome completo selecionado. Por exemplo, poderíamos
escrever uma cláusula use

use work.cpu_types.word, work.cpu_types.address;

e, em seguida, consulte os nomes usados nas declarações:

variável data_word : palavra;


variável next_address : endereço;

Podemos colocar uma cláusula use em qualquer parte declarativa de um modelo. Uma maneira
de pensar em uma cláusula de uso é que ela “importa” os nomes dos itens listados para a parte do
modelo que contém a cláusula de uso.
A regra de sintaxe para uma cláusula use mostra que podemos escrever a palavra-chave all em
vez do nome de um item específico para importar de um pacote. Este formulário é muito útil, pois é uma
forma abreviada de importar todos os nomes definidos na interface de um pacote. Por exemplo, se
estivermos usando o pacote de lógica padrão IEEE como base para os tipos de dados em um projeto,
podemos importar tudo do pacote de lógica padrão com uma cláusula de uso da seguinte forma:

use ieee.std_logic_1164.all;

Cláusulas de uso podem ser incluídas no início de uma unidade de projeto, bem como em partes
declarativas dentro de uma unidade de projeto. Vimos anteriormente como podemos incluir cláusulas
de biblioteca e de uso no início de uma unidade de design, como uma interface de entidade ou um
corpo de arquitetura. Essa área de uma unidade de design é chamada de cláusula de contexto. Os
nomes importados aqui ficam diretamente visíveis em toda a unidade de design. Por exemplo, se
quisermos usar os tipos de lógica padrão IEEE na declaração de uma entidade, podemos escrever a
unidade de projeto da seguinte forma:

biblioteca ieee; use ieee.std_logic_1164.all;


entidade logic_block é porta ( a, b : em
std_ulogic; y, z : out std_ulogic ); fim
entidade logic_block;

Os nomes importados por uma cláusula de uso dessa forma ficam diretamente visíveis em toda a
unidade de design após a cláusula de uso. Além disso, se a unidade de design for uma unidade primária
(como uma declaração de entidade ou uma declaração de pacote), a visibilidade é estendida a qualquer
unidade secundária correspondente. Assim, se incluirmos uma cláusula use no
Machine Translated by Google

Cláusulas de uso 73

unidade, não precisamos repeti-lo na unidade secundária, pois os nomes são automaticamente
visível lá.
Machine Translated by Google
Machine Translated by Google

7
Sinais Resolvidos

7.1 IEEE Std_Logic_1164 Subtipos Resolvidos


O VHDL fornece um mecanismo muito geral para especificar qual valor resulta da conexão de
várias saídas. Ele faz isso por meio de subtipos resolvidos e sinais resolvidos, que são uma
extensão dos sinais básicos que usamos nos capítulos anteriores. No entanto, a maioria dos
projetos simplesmente usa os subtipos resolvidos definidos no pacote de lógica padrão,
std_logic_1164. Neste tutorial, restringiremos nossa atenção a esses subtipos.

Primeiro, lembre-se de que o pacote fornece o tipo básico std_ulogic, definido como

tipo std_ulogic é ('U', 'X', '0', '1', 'Z', 'W', 'L', 'H', '–');

e um tipo de array std_ulogic_vector, definido como

tipo std_ulogic_vector é array ( intervalo natural <> ) de std_ulogic;

Não mencionamos isso antes, mas o “u” em “ulogic” significa não resolvido.
Sinais desses tipos não podem ter várias fontes. O pacote de lógica padrão também fornece um
subtipo resolvido chamado std_logic. Um sinal deste tipo pode ter múltiplas fontes. O pacote
também declara um tipo de array de elementos de lógica padrão, análogo ao tipo bit_vector ,
para uso na declaração de sinais de array:

tipo std_logic_vector é array ( intervalo natural <>) de std_logic;

O padrão define a maneira pela qual as contribuições de várias fontes são resolvidas para
produzir o valor final de um sinal. Se houver apenas um valor de condução, esse valor será
usado. Se um driver de um sinal resolvido direcionar um valor de forçamento ('X', '0' ou '1') e
outro direcionar um valor fraco ('W', 'L' r 'H'), o valor de forçamento domina. Por outro lado, se
ambos os drivers conduzirem valores diferentes com a mesma força, o resultado é o valor
desconhecido dessa força ('X' ou 'W'). O valor de alta impedância, 'Z', é dominado por valores
forçados e fracos. Se um valor “don't care” ('–') for resolvido com qualquer outro valor, o resultado
será o valor desconhecido 'X'. A interpretação do valor “don't care” é que o modelo não fez uma
escolha sobre seu estado de saída. Finalmente, se um valor “não inicializado” ('U') deve ser
resolvido com qualquer outro valor, o resultado é 'U', indicando que o modelo não inicializou
corretamente todas as saídas.

75
Machine Translated by Google

76 Sinais Resolvidos

Além desse subtipo de lógica multivalorada, o pacote std_logic_1164 declara


vários subtipos para modelagem lógica multivalorada mais restrita. O subtipo
declarações são

o subtipo X01 é resolvido std_ulogic intervalo 'X' a '1'; –– ('X','0','1')


o subtipo X01Z é resolvido std_ulogic intervalo 'X' a 'Z'; –– ('X','0','1','Z')
o subtipo UX01 é resolvido std_ulogic intervalo 'U' a '1'; –– ('U','X','0','1')
o subtipo UX01Z é resolvido std_ulogic intervalo 'U' a 'Z'; –– ('U','X','0','1','Z')

O pacote de lógica padrão fornece os operadores lógicos e, nand, or, nor, xor,
xnor e não para valores e vetores de lógica padrão, retornando valores no intervalo 'U',
'X', '0' ou '1'. Além disso, existem funções para converter entre os valores do total
tipo de lógica padrão, os subtipos mostrados acima e o bit e o vetor de bits predefinidos
tipos.

7.2 Sinais e Portas Resolvidos


Na discussão anterior de sinais resolvidos, nos limitamos ao simples
caso em que várias fontes conduzem um sinal. Qualquer porta de entrada conectada ao sinal resolvido
obtém o valor final resolvido como o valor da porta. Agora olhamos mais
detalhe no caso de uma porta do modo inout estar conectada a um sinal resolvido. o
valor visto pelo lado de entrada de tal porta é o valor final do sinal resolvido
conectado à porta. Uma porta inout modela uma conexão na qual o driver contribui para o valor do
sinal associado, e o lado de entrada do componente detecta
o sinal real em vez de usar o valor de condução.

EXEMPLO

Alguns protocolos de barramento assíncrono usam um mecanismo de sincronização


distribuído baseado em um sinal de controle e cabeado. Este é um único sinal conduzido por cada
módulo usando coletor aberto ativo-baixo ou drivers de dreno aberto e puxado para cima por
o terminador de ônibus. Se vários módulos no barramento precisarem esperar até que todos sejam
prontos para prosseguir com alguma operação, eles usam o sinal de controle da seguinte forma.
Inicialmente, todos os módulos conduzem o sinal para o estado '0'. Quando cada um estiver pronto para prosseguir,
ele desliga seu driver ('Z') e monitora o sinal de controle. Desde que qualquer módulo
ainda não está pronto, o sinal permanece em '0'. Quando todos os módulos estiverem prontos, o barramento
terminador puxa o sinal até o estado '1'. Todos os módulos percebem essa mudança e
prosseguir com a operação.
A Figura 7-1 mostra uma declaração de entidade para um módulo de barramento que possui uma porta do
tipo não resolvido std_ulogic para conexão a tal sinal de controle de sincronização. O corpo de
arquitetura para um sistema que compreende vários desses módulos também é
delineado. O sinal de controle é puxado por um estado de atribuição de sinal concorrente, que
atua como uma fonte com um valor de condução constante de 'H'. Este é um valor
tendo uma força fraca, que é substituída por qualquer outra fonte que acione '0'.
Ele pode puxar o sinal alto somente quando todas as outras fontes dirigem 'Z'.
Machine Translated by Google

Sinais e portas resolvidos 77

FIGURA 7-1

biblioteca ieee; use ieee.std_logic_1164.all; entidade

bus_module é porta ( synch : inout std_ulogic; … );


fim entidade bus_module;

––––––––––––––––––––––––––––––––––––––––––––––––––– ––

arquitetura top_level do bus_based_system é


sinal synch_control : std_logic;

começar synch_control_pull_up : synch_control <= 'H';

bus_module_1 : entidade trabalho.bus_module(comportamental)


mapa de portas ( synch => synch_control, … );
bus_module_2 : entidade trabalho.bus_module(comportamental)
mapa de portas ( synch => synch_control, … );

final arquitetura top_level;

Uma declaração de entidade para um módulo de barramento que usa um sinal cabeado e de sincronização e
um corpo de arquitetura que instancia a entidade, conectando a porta de sincronização a um sinal resolvido.

A Figura 7-2 mostra um esboço de um corpo de arquitetura comportamental para o módulo de


barramento. Cada instância aciona inicialmente sua porta de sincronização com '0'. Esse valor é
passado pela porta e usado como contribuição para o sinal resolvido da instância da entidade.
Quando uma instância está pronta para prosseguir com sua operação, ela altera seu valor de
condução para 'Z', modelando um driver open-collector ou open-drain sendo desligado. O processo
então é suspenso até que o valor visto na porta de sincronização mude para 'H'. Se outras instâncias
ainda estão conduzindo '0', suas contribuições dominam e o valor do sinal permanece '0'. Quando
todas as outras instâncias eventualmente alteram suas contribuições para 'Z', o valor 'H' contribuído
pela instrução pull up domina e o valor do sinal muda para 'H'. Este valor é passado de volta pelas
portas de cada instância, e todos os processos

somas.

FIGURA 7-2

arquitetura comportamental do bus_module é o


comportamento inicial: o processo é


iniciar
sincronização <= '0' após Tdelay_synch;

–– pronto para iniciar a
sincronização da operação <= 'Z' após
Tdelay_synch; espere até sincronizar =
'H'; –– prosseguir com a operação
Machine Translated by Google

78 Sinais Resolvidos


comportamento final do processo ;

arquitetura final comportamental;

Um esboço de um corpo de arquitetura comportamental para um módulo de barramento, mostrando o uso da porta de controle de
sincronização.
Machine Translated by Google

8
Constantes genéricas

8.1 Parametrização do Comportamento

O VHDL nos fornece um mecanismo, chamado genéricos, para escrever modelos parametrizados.
Podemos escrever uma entidade genérica incluindo uma lista genérica em sua declaração que define as
constantes genéricas formais que parametrizam a entidade. A regra de imposto syn estendida para
declarações de entidades incluindo genéricos é

entidade_declaração ÿ
identificador de
entidade é [ generic ( generic_interface_list ); ]
[ port ( port_interface_list ); ] end [ entidade ]
[ identificador ] ;

A lista de interface genérica é como uma lista de parâmetros, mas com a restrição de que só
podemos incluir objetos de classe constante, que devem estar no modo in. Como esses são os padrões
para uma lista genérica, podemos usar uma regra de sintaxe simplificada:

generic_interface_list ÿ
( identificador { ,subtype_indication
…}: [ := expressão ] ) { ; … }

Um exemplo simples de uma declaração de entidade incluindo uma lista de interface genérica é

entidade and2 é
genérica ( Tpd : time );
porta (a, b: bit de entrada ; y: bit de
saída ); entidade final e2;

Esta entidade inclui uma constante genérica, Tpd, do tipo predefinido time. O valor desta constante
genérica pode ser usado nas declarações de entidade e em qualquer corpo de arquitetura correspondente
à entidade. Neste exemplo, a intenção é que a constante genérica especifique o atraso de propagação
para o módulo, portanto, o valor deve ser usado em uma instrução de atribuição de sinal como o atraso.
Um corpo de arquitetura que faz isso é

arquitetura simples de and2 é


começar

79
Machine Translated by Google

80 Constantes genéricas

and2_function :
y <= aeb após Tpd ;
arquitetura final simples;

Uma constante genérica recebe um valor real quando a entidade é usada em uma instrução de
instanciação de componente. Fazemos isso incluindo um mapa genérico, conforme mostrado pela regra
de sintaxe estendida para instanciações de componentes:

component_instantiation_statement
ÿinstantiation_label : entity entity_name
( architecture_identifier ) [ mapa genérico
( generic_association_list ) ] port map
( port_association_list ) ;

A lista de associações genéricas é como outras formas de listas de associações, mas como as
constantes genéricas são sempre constantes de classe, os argumentos reais que fornecemos devem
ser expressões. Assim, a regra de sintaxe simplificada para uma lista de associação genérica é

generic_association_list ÿ
( [ generic_name => ] ( expressão que eu abro ) ) { , … }

Para ilustrar isso, vejamos uma instrução de instanciação de componente que usa a entidade and2
mostrada acima:

gate1 : entidade work.and2(simples)


mapa genérico ( Tpd => 2 ns )
mapa de portas ( a => sig1, b => sig2, y => sig_out );

O mapa genérico especifica que esta instância do módulo and2 usa o valor 2 ns para a constante
genérica Tpd; ou seja, a instância tem um atraso de propagação de 2 ns.
Podemos incluir outra instrução de instanciação de componente usando and2 no mesmo design, mas
com um valor real diferente para Tpd em seu mapa genérico, por exemplo:

gate2 : entidade work.and2(simples)


mapa genérico ( Tpd => 3 ns )
mapa de portas ( a => a1, b => b1, y => sig1 );

Quando o projeto é elaborado temos dois processos, um correspondente à instância gate1 de and2 ,
que usa o valor 2 ns para Tpd, e outro correspondente à instância gate2 de and2 , que usa o valor 3 ns.

8.2 Estrutura de Parametrização


O segundo uso principal de constantes genéricas em entidades é parametrizar sua estrutura.
Podemos usar o valor de uma constante genérica para especificar o tamanho de uma porta de matriz
usando a constante genérica em restrições nas declarações de porta. Para ilustrar, aqui está uma
declaração de entidade para um registro:

entidade reg
é genérica ( largura : positiva );
Machine Translated by Google

Estrutura de parametrização 81

porta ( d : em bit_vector(0 a largura – 1);


q : out bit_vector(0 a largura – 1); …);
registro de entidade final ;

Nesta declaração, exigimos que o usuário do registrador especifique a largura de porta desejada para
cada instância. A entidade então usa o valor da largura como uma restrição nas portas de entrada e saída.
Uma instanciação de componente usando essa entidade pode aparecer da seguinte forma:

sinal in_data, out_data : bit_vector(0 to bus_size – 1);


ok_reg : entidade work.reg


generic map ( width => bus_size ) port
map ( d => in_data, q => out_data, … );

EXEMPLO

Um modelo completo para o registro, incluindo a declaração da entidade e um corpo de


arquitetura, é mostrado na Figura 8-1. A constante genérica é usada para restringir as larguras das
portas de entrada e saída de dados na declaração da entidade. Também é usado no corpo da
arquitetura para determinar o tamanho do vetor de bits constante zero.
Este vetor de bits é o valor atribuído à saída do registrador quando este é resetado, portanto deve ser
do mesmo tamanho da porta do registrador.
Podemos criar instâncias da entidade de registro em um design, cada uma possivelmente tendo
portas de tamanhos diferentes. Por exemplo:

word_reg : entidade work.reg(comportamental)


mapa genérico ( largura => 32 ) mapa
de porta ( … );

cria uma instância com portas de 32 bits. No mesmo design, podemos incluir outra instância, como
segue:

subtipo state_vector é bit_vector(1 a 5);


state_reg : entidade work.reg(comportamental)


mapa genérico ( largura => state_vector'length ) mapa
de porta ( … );

Esta instância de registro possui portas de 5 bits, largas o suficiente para armazenar valores do subtipo
state_vector.

FIGURA 8-1

entidade reg é
genérica ( largura : positiva );
porta ( d : em bit_vector(0 a largura – 1);
q : out bit_vector(0 a largura – 1); clk,
reset: em bit); registro de entidade final ;
Machine Translated by Google

82 Constantes genéricas

––––––––––––––––––––––––––––––––––––––––––––––––––– ––

arquitetura comportamental de reg é


comportamento inicial : processo (clk,
reset) é
constante zero : bit_vector(0 a largura – 1) := (outros => '0');
começar se reset = '1' então q <= zero; elsif clk'event e clk = '1' então q
<= d; fim se; comportamento final do processo ; arquitetura final
comportamental;

Uma entidade e um corpo de arquitetura para um registro com tamanho de porta parametrizado.

Você também pode gostar