VHDL Tutorial
VHDL Tutorial
Tutorial VHDL
Peter J. Ashenden
EDA CONSULTOR, ASHENDEN DESIGNS PTY.
LTD. www.ashhenden.com.au
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
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.
4 Conceitos fundamentais
• 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.
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;
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
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 ;
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
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
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
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
FIGURA 2-6
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
esperar; estímulo do
processo final ; final da arquitetura test_reg4;
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.
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
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:
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
11
Machine Translated by Google
•
pode conter apenas letras alfabéticas ('A' a 'Z' e 'a' a 'z'), dígitos decimais ('0' a '9') e o caractere
sublinhado ('_');
Caso de letras não é significativo. Alguns exemplos de identificadores básicos válidos são
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
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
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
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
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
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:
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:
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
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
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
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
à 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:
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:
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
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
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
deixa claro que um fator pode ser seguido por um dos símbolos do operador e, em seguida, outro fator.
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.
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 ;
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
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
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
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
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:
* multiplicação
Tipos escalares 19
EXEMPLO
Os valores de small_int são restritos a estar dentro do intervalo –128 a 127. Se declararmos algumas
variáveis:
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:
Finalmente, o operador abs pode ser aplicado a um valor de tempo , por exemplo:
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 é
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 é
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 octal_digit é ('0', '1', '2', '3', '4', '5', '6', '7');
alu_op := subtrair;
last_digit := '7';
Personagens
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',
cmd_char := 'P';
terminador := cr;
Booleanos
O tipo booleano predefinido é definido como
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
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
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
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:
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
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
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'.
Se Declarações
if_statement ÿ
[ if_label : ]
if boolean_expression then
{ sequencial_statement }
{ elsif boolean_expression then
{ sequencial_statement } }
[ else
Machine Translated by Google
{ sequencial_statement } ]
end if [ if_label ] ;
se en = '1' então
valor_armazenado := data_in;
fim se;
se sel = 0 então
resultado <= entrada_0; –– executado se sel = 0
senão resultado <= input_1; –– executado se sel /= 0 end
if;
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
FIGURA 3-3
o termostato da entidade
é a porta (temper_desejada, temp_atual: em inteiro;
aquecedor_on : out boolean );
termostato da entidade final ;
––––––––––––––––––––––––––––––––––––––––––––––––––– ––
Declarações de Caso
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:
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
poderíamos escrever uma alternativa incluindo três desses valores como escolhas:
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
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
––––––––––––––––––––––––––––––––––––––––––––––––––– ––
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 ] ;
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 ;
––––––––––––––––––––––––––––––––––––––––––––––––––– ––
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.
loop_name : loop
…
sair nome_do_loop;
…
fim do loop nome_do_loop ;
Machine Translated by Google
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 ;
––––––––––––––––––––––––––––––––––––––––––––––––––– ––
Declarações Sequenciais 31
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 ] ;
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
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
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
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:
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
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 ;
EXEMPLO
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
––––––––––––––––––––––––––––––––––––––––––––––––––– ––
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.
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
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:
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:
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:
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:
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;
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
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
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
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;
––––––––––––––––––––––––––––––––––––––––––––––––––– ––
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
A'esquerda = 1 A'direita = 4
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
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.
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 é
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:
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:
Ambos são exemplos de uma nova forma de indicação de subtipo que ainda não vimos. A regra de
sintaxe é
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
Por exemplo:
Vetores de bits
O VHDL também fornece um tipo de array irrestrito predefinido chamado bit_vector, declarado
como
Por exemplo, subtipos para representar bytes de dados em um processador little-endian podem ser declarados
como
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
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
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;
––––––––––––––––––––––––––––––––––––––––––––––––––– ––
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:
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
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:
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:
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
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
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
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
entidade top_level é
entidade final top_level;
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.
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:
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
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):
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
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.
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:
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;
––––––––––––––––––––––––––––––––––––––––––––––––––– ––
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
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
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:
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
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:
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
process_statement ÿ
process_label : process [ ( signal_name { , … } ) ] [ is ]
{ process_declarative_item }
começar
{ sequencial_statement }
final do processo [ process_label ] ;
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 .
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.
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:
Um caso muito comum na modelagem de funções é escrever uma atribuição de sinal condicional
sem condições, como no exemplo a seguir:
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 é:
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.
instrução_instanciação_componente ÿ
instanciação_label :
entidade entity_name ( architecture_identifier )
mapa de portas ( port_association_list );
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:
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:
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
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;
––––––––––––––––––––––––––––––––––––––––––––––––––– ––
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
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
clr
clique
––––––––––––––––––––––––––––––––––––––––––––––––––– ––
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 .
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 é
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
FIGURA 4-6
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 );
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 ;
59
Machine Translated by Google
60 Subprogramas
EXEMPLO
FIGURA 5-1
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;
…
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.
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
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
Parâmetros de procedimento 63
estouro := transporte =
'1'; procedimento final addu;
Um procedimento para adicionar dois vetores de bits representando inteiros sem sinal.
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
…
receive_packet (recuperado_dados, recuperado_relógio, pacote);
…
finalize o processo packet_assembler;
…
Um esboço de um modelo de um receptor de rede, incluindo um procedimento com parâmetros de sinal de modo in.
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
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
…
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;
…
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.
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
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:
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 ] ;
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
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 é
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
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:
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:
6
Pacotes e Cláusulas de Uso
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.
69
Machine Translated by Google
work.cpu_types.status_value
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 é
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 ] ;
EXEMPLO
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;
––––––––––––––––––––––––––––––––––––––––––––––––––– ––
…
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.
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
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:
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:
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
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', '–');
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:
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
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.
EXEMPLO
FIGURA 7-1
––––––––––––––––––––––––––––––––––––––––––––––––––– ––
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.
somas.
FIGURA 7-2
…
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 ;
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
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 é
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:
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:
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.
entidade reg
é genérica ( largura : positiva );
Machine Translated by Google
Estrutura de parametrização 81
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:
EXEMPLO
cria uma instância com portas de 32 bits. No mesmo design, podemos incluir outra instância, como
segue:
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
––––––––––––––––––––––––––––––––––––––––––––––––––– ––
Uma entidade e um corpo de arquitetura para um registro com tamanho de porta parametrizado.