Baixe o app para aproveitar ainda mais
Prévia do material em texto
UNIVERSIDADE DE BRASÍLIA Apostila de VHDLTradução livre do material "The Low-Carb VHDL Tutorial" ©Copyright: 2004 por Bryan Mealy (27 - 08 - 2004) Tradutores: Francisco Frantz e Daniel Almeida 24/02/2012 Introdução VHDL é uma abreviação para "Very high-speed integrated circuit Hardware Description Language". Como hoje em dia se utiliza basicamente circuitos integrados para design de hardware, suprimiu-se uma parte da sigla para simplificação. Há dois principais motivos para descrever hardware usando VHDL. Primeiramente, o VHDL pode ser usado para modelar circuitos digitais. Isso permite fazer simulações e testes, e, talvez mais importante, criar um modelo na linguagem VHDL é uma ótima forma de aprendizado. Outro uso do VHDL (ou outras linguagens de descrição de hardware) é um dos primeiros passos na criação de complexos circuitos digitais, podendo testá-los sem a necessidade de construí-los fisicamente. Há outros simuladores lógicos disponíveis que permitem modelar o comportamento de circuitos digitais, que possuem uma abordagem gráfica para descrever os circuitos. Pode ser um método melhor para o aprendizado, mas, quanto mais complexos forem os circuitos digitais, mais tedioso e confuso fica esse método, uma vez que se baseia na interconexão de linhas e portas lógicas. O VHDL propicia uma descrição exata como o circuito digital funciona, sem ter de se preocupar com os detalhes das muitas conexões internas ao mesmo. O conhecimento de VHDL será uma ferramenta para modelar circuitos digitais de uma maneira inteligente. Finalmente, é possível criar circuitos funcionais usando o VHDL, o que permite implementar rapidamente circuitos relativamente complexos. A metodologia usada permite dedicar mais tempo ao projeto dos circuitos e menos a realmente construí-los em uma proto-board. Agora, deve-se descrever o circuito usando uma linguagem como o VHDL. Para fazer isso, é fundamental aprender a linguagem e dominar suas ferramentas envolvidas no processo. Sintaxe do VHDL Há diversos aspectos da linguagem VHDL que deve-se saber antes de prosseguir. Essa seção detalha a sintaxe básica de um código em VHDL, como o uso de parêntese e a atribuição de valores. São aspectos característicos da linguagem de programação, assim como existem em C, C++, Matlab e todas as outras. É interessante memorizar o que será abordado nessa seção, mas isso só é possível com a prática. Sensibilidade a letras maiúsculas O VHDL não diferencia caracteres maiúsculos e minúsculos. Isso quer dizer que ambas as sentenças na figura 1 tem o mesmo sentido para o compilador. O objetivo agora não é entender o que cada sentença executa: isso será visto na próxima seção. Dout <= A and B; doUt <= a AND b; Figura 1: exemplo da indiferença entre maiúsculas e minúsculas Espaços em branco O VHDL não é sensível a espaços em branco (espaço e "tab") no documento fonte. As linhas de código da figura 2 tem o mesmo significado. nQ <= In_a or In_b; nQ <= in_a OR in_b; Figure 2: Exemplo da indiferença para espaços em branco Comentários Isso é válido para todas as linguagens de programação: um uso adequado de comentários melhora a leitura e o entendimento de qualquer código. A regra geral é comentar toda linha ou seção de código que possa não ser óbvia para outro leitor. Isso pode parecer tolo, uma vez que o código executa com ou sem os comentários, mas é de fundamental importância, não apenas para outros leitores. Muitas vezes, faz-se um código em um dia e só se trabalha nele novamente depois de muito tempo. Na hora que o código é feito, se entende tudo ele contém. Mas depois, há a necessidade de parar e pensar o que cada linha está fazendo, o que poderia ser evitado com comentários bem colocados. Em empresas que trabalham com programação, há sempre uma política muito rígida em como devem ser comentados os códigos. É, portanto, uma boa prática já começar a treinar. No VHDL, os comentários começam com dois hífens ("--"), e o compilador ignora tudo que os seguem na linha. Não existe caractere que implemente comentários de mais de uma linha. A figura 3 mostra alguns tipos de comentários. ---------------------------Commentary example-------------------------------- -- This next section of code is used to blah-blah -- blah-blah blah-blah. This type of comment is the best -- fake for block-style commenting. PS <= NS_reg; -- Assign next_state value to present_state Figura 3 - três tipos típicos de comentário Parênteses O VHDL é relativamente frouxo no uso de parênteses. Como em outras linguagens, há ordens de procedência associadas com os vários operadores em VHDL. Mesmo sendo possível escrever um código que segue essa ordem, é uma boa prática colocar alguns parênteses para melhorar a leitura do código. As duas sentenças da figura 4 possuem o mesmo significado para o compilador. if x = ‘0’ and y = ‘0’ or z = ‘1’ then blah; blah; blah; end if; if ( ((x = ‘0’) and (y = ‘0’)) or (z = ‘1’) ) then blah; blah; blah; end if; Figura 4 - uso de parênteses e espaços para melhor leitura Sentenças Como em outras linguagens, toda sentença de código em VHDL deve ser terminada com ponto e vírgula (";"). Isso ajuda a remover eventuais erros de compilação do código, uma vez que é recorrente o erro de esquecer o ponto e vírgula. Entretanto, deve-se entender o que constitui uma sentença em VHDL para usá-los corretamente: o VHDL não é tão flexível quanto o Matlab, por exemplo, com colocações a mais ou a menos de pontos e vírgulas. Declarações if, case e loop Uma fonte comum de frustração enquanto se desenvolve um código em VHDL é o clássico erro bobo envolvendo essas declarações. As regras abaixo devem ser memorizadas para evitar esse tipo de erro, evitando assim, perder tempo procurando os erros. É altamente recomendável marcar esta seção para releitura quando estas declarações forem melhor detalhadas. • Todo if tem de ter um correspondente then (se .... então) • Todo if deve ser terminada por "end if" • Caso se queira usar um "else", a forma correta de fazê-lo é com "elsif" • Todo case é terminado com "end case" • Todo loop tem um "end loop" correspondente Identificadores Identificador se refere ao nome dado aos itens em VHDL para discerni-los uns dos outros. Em linguagens como C e Matlab, os nomes de variáveis e de funções são identificadores. Em VHDL, há os nomes de variáveis, de sinais e de portas lógicas (serão discutidos em breve). Há regras rígidas (precisam ser seguidas) e regras flexíveis (é bom serem seguidas) para usar identificadores, que podem tornar o código mais legível, compreensível e elegante se forem escolhidos apropriadamente. A lista e a tabela a seguir mostram regras gerais para escolha de identificadores. BONS IDENTIFICADORES IDENTIFICADORES RUINS data_bus_val Nome descritivo 3Bus_val WE Clássica sigla para "write enable" DDD div_flag mid_$num port_A last__value in_bus Provavelmente "input bus" start_val_ clk Clássico nome para "system clock" in @#$%%$ this_sucks Big_vAlUe pa sim-val Tabela 1: Identificadores desejáveis e indesejáveis • Identificadores devem realmente identificar o que representam, ou seja, ao olhar um identificador, deve-se ter uma ideia da sua informação e do seu propósito • Identificadores podem ter quantos caracteres se desejar. Quanto mais curto, mais legível; quanto mais longo, mais informação ele possui. Deve-se levar isso em conta na hora de programar. • Identificadores só podem conter combinações de letras (A-Z ou a-z), dígitos (0-9) e underlines ('_') • Identificadores só podem começar com caractere alfabético • Identificadores não podem terminar em underline nem possuir dois deles consecutivos Palavras reservadas Há uma lista depalavras às quais foi atribuído algum significado pela linguagem VHDL. Essas palavras, chamadas palavras reservadas, não podem ser usadas como identificadores por programadores. Há uma pequena lista mostrada a seguir, e a lista completa se encontra no apêndice A. access exit mod return while NOR after file new signal with XNOR alias for next shared constant NAND all function null then loop attribute generic of to rem block group on type wait body in open until AND buffer is out use OR bus label range variable XOR Tabela 2: Lista resumida de palavras reservadas em VHDL Estilo de programação Já falou-se disso neste material, mas deve-se reforçar esse ponto. O estilo de programação de refere à aparência do código escrito em VHDL. Com a liberdade dada pela indiferença entre maiúsculas e minúsculas e quanto a espaços em branco, há a tendência de se pensar no VHDL como uma linguagem próxima da anarquia. Entretanto, deve-se sempre pensar na legibilidade do código. Isso é primordialmente feito a parir dos identificadores, comentários, espaços em branco e parênteses desnecessários. Abaixo, lista-se boas práticas para chegar ao objetivo da legibilidade. • Se o código for legível para o programador, provavelmente será legível para outras pessoas que precisem ver o documento. Essas pessoas podem ser um colega do grupo de laboratório, um professor que avaliará o trabalho ou a pessoa que paga seu salário ao final do mês. No primeiro caso, pode não ser tão motivante fazer um código legível; nos outros dois, é altamente recomendável. • O código pode ser modelado a partir de algum outro código que se considere organizado e legível. Procurar um código na internet e seguir seu estilo é uma boa prática para programadores iniciantes. • Boas práticas durante a elaboração do código propiciam uma melhor depuração de erros, caso existam. O compilador de VHDL é eficiente na detecção de erros, mas geralmente não diz onde esses erros se encontram. Um código organizado reduz o tempo de procura desses erros. Unidades básicas do VHDL O VHDL descreve circuitos utilizando a abordagem das "caixas pretas". O circuito (e partes dele) podem ser representados como caixas, que possuem entradas e saídas. Considere o seguinte exemplo: Nesse caso, os pontos de interesse são as entradas (A,B e C) e a saída (F). Não interessa, ainda, o que acontece dentro da caixa preta para converter as entradas na saída. Essa parte da descrição do circuito, ou seja, a caixa preta que indica as entradas e saídas (interface), é chamada, em VHDL, de entidade (entity). Para saber como acontece essa medição da corrente, há diversas possibilidades de descrever o problema. Por exemplo, supondo que F = A.B + B.C, as figuras a seguir descrevem formas de descrever o que ocorre dentro da caixa preta: Nesse caso, detalhe-se como a saída é obtida a partir da entrada As interações que ocorrem dentro da caixa preta são definidos, em VHDL, como arquitetura (architecture). É na arquitetura que são definidos os parâmetros da caixa preta (resistência R) e os processos para a saída ser obtida. É interessante notar que, para uma mesma entidade, podem existir várias arquiteturas. Em VHDL, a entidade e a arquitetura são as unidades fundamentais para o projeto. Defini-se a "caixa preta" e as "coisas que vão dentro da caixa preta". Será sempre assim quando se trabalha com VHDL. A criação da entidade, como se pode imaginar, é muito simples, enquanto a arquitetura é a parte mais trabalhosa do projeto. Hoje em dia, é na melhoria da arquitetura de circuitos que se concentra grande parte dos esforços de engenheiros, e o VHDL é uma plataforma interessante para simular diversos tipos de arquitetura. A entidade Como á foi dito, a entidade é a versão em VHDL da caixa preta. Ela propicia um método de abstrair a funcionalidade de um circuito. A entidade simplesmente lista as entradas e saídas de um circuito digital. Em termos de VHDL, a caixa preta é descrita por uma declaração de entidade. A figura a seguir mostra como essa declaração é feita. entity entity_name is [port_clause] end entity_name; Figura 5: Forma genérica de uma declaração de entidade O identificador entity_name serve para fazer referência a entidade. A parte em colchetes, [port_clause] realmente especifica a interface (entradas e saídas) da entidade. Sua sintaxe está detalhada na figura abaixo. port ( port_name : mode data_type; port_name : mode data_type; port_name : mode data_type ); Figura 6: Detalhamento de [port clause] Uma "port" é essencialmente um sinal que interage com o "mundo" fora da caixa preta. Pode ser tanto um sinal de entrada na caixa preta quanto um sinal de saída dela. Colocando o código da figura 6 no lugar de [port_clause] na figura 5, a entidade está declarada por completo, ou seja, [port_clause] nada mais é do que uma lista de sinais do circuito que estão disponíveis ao "mundo". O port_name é um identificador usado para diferenciar os diversos sinais. Onde se encontra "mode" especifica-se a direção do sinal em relação à caixa preta: pode ser input (entrada) ou output (saída). Para os sinais de entrada, mode deve ser substituído por in; para os de saída, por out. O "data_type" se refere ao tipo de dados que a "port" possui. Em VHDL, há diversos tipos de dados, mas trabalharemos primeiramente com o tipo std_logic; os diversos tipos de dados serão discutidos mais tarde. A figura 7 mostra um exemplo de uma caixa preta e o código VHDL que a descreve. Abaixo se encontram algumas coisas importantes de se notar na figura; a maioria delas se refere a legibilidade e clareza do código. As palavras em negrito são apenas para lembrar as palavras-chave e não possuem função diferente no código por estarem assim escritas. • Cada "port" possui nome único e tem "mode" e "type" associados • O compilador do VHDL permite diversas ports em uma mesma linha, e elas são separadas por vírgulas. type e mode são definidos no final da linha Engenheiro Realce • A listagem de entradas e saídas é feita de forma consecutiva • Há uma tentativa de alinhar as colunas do nome da port, mode e type para melhor legibilidade: vale lembrar que espaços em branco são ignorados pelo compilador • Um comentário que diz coisas quase inteligentes foi adicionado Figura 7: Caixa preta exemplo e seu código A figura 8 apresenta outra entidade em VHDL. Tudo que se foi dito sobre a figura 7 é válido também para esta figura. Figura 8: Outro exemplo de declaração de entidade Pode ser que não esteja claro o que cada circuito acima faz, mas o importante é entender como é feita a declaração da entidade. A maior parte dos circuitos que serão projetados, analisados e testados usando VHDL terão diversas entradas tendo similaridades entre as mesmas. Para não ser necessário escrever cada entrada com um nome diferente, pode-se agrupá-las em "bus". Um bus será um agrupamento de entradas, que diferem entre si apenas por um caractere numérico que caracteriza a posição da respectiva entrada no bus. Cada sinal do bus é chamado de elemento. Pode-se fazer uma analogia: em um ônibus, há diversos passageiros sentados. O motorista não precisa saber o nome de cada passageiro para chamá-lo, basta saber o número do seu assento. O uso de buses sintetiza muito o código VHDL. Eles são usados frequentemente, e são facilmente descritos no código. Alguns exemplos constam na figura 9, onde deve-se notar que o mode permanece o mesmo, enquanto o type mudou: o std_logic agora inclui a palavra vector para indicar que cada identificador de sinal possui, na realidade, mais de um sinal. A forma de referenciar cada elemento do bus será detalhadamais adiante. magic_in_bus : in std_logic_vector(0 to 3); big_magic_in_bus : in std_logic_vector(7 downto 0); tragic_in_bus : in std_logic_vector(16 downto 1); data_bus_in_32 : in std_logic_vector(0 to 31); mux_out_bus_16 : out std_logic_vector(0 to 15); addr_out_bus_16 : out std_logic_vector(15 downto 0); Figura 9: Alguns exemplos de sinais bus Note que há duas maneiras possíveis de se descrever sinais em um bus, que são mostrados nos termos entre parênteses (lista de argumentos) que seguem a declaração de type. Os sinais podem ser descritos em duas ordens: crescente (to) ou decrescente (downto). Não existe um método melhor que outro, a escolha entre eles é baseada na clareza do código. O importante é não esquecer como foi feita essa definição. Vamos analisar a notação para descrever os bus em uma caixa preta. A figura 10 mostra uma caixa preta seguida da sua declaração de entidade. Note que é usado um sinal de uma barra com um número em cima, para indicar que a entrada ou saída é um bus e quantos elementos ele possui. É importante notar que os sinais de entrada sel1 e sel0 poderiam ser descritos em um bus de dois elementos, tendo em vista que ambos possuem mesmo type. Engenheiro Realce Engenheiro Realce Figura 10: uma caixa preta contendo entradas e saídas bus, e sua respectiva declaração de entidade A arquitetura Enquanto a entidade descreve a interface ou a representação externa do circuito, a arquitetura descreve o que o circuito realmente faz. Em outras palavras, a arquitetura descreve a implementação interna da entidade associada. Como é de se imaginar, descrever a entidade é geralmente bem mais fácil que descrever como o circuito deve operar. Isso é cada vez mais verdade quanto mais complexos forem os circuitos que se pretende projetar. A parte mais desafiadora do VHDL é aprender as inumeráveis maneiras de se descrever um circuito. A maior parte dessa apostila é exatamente discutir os diferentes métodos de descrever circuitos lógicos, então não será feita uma discussão prolongada de arquiteturas nesse ponto. Entretanto, algumas noções gerais são dadas à seguir: • Podem existir diversas arquiteturas para descrever uma única entidade. O estilo de código na arquitetura tem efeitos significativos no circuito sintetizado, ou seja, se o circuito for fisicamente produzido, cada arquitetura terá um impacto diferente no resultado final. Uma pode proporcionar maior velocidade, enquanto outra melhora o consumo de energia, por exemplo. • Os modelos básicos para descrição de arquitetura são fluxo de dados, estrutural e comportamental, bem como versões híbridas desses modelos, que serão descritos nas próximas seções desse material. Engenheiro Realce Engenheiro Realce Engenheiro Realce Engenheiro Realce Engenheiro Realce Engenheiro Realce Engenheiro Realce O paradigma de programação em VHDL A última seção foi uma breve introdução das unidades básicas do VHDL: entidade e arquitetura. A entidade está praticamente definida por completo, dada a sua simplicidade quando comparada com a arquitetura. É essencial lembrar que a entidade descreve a interface do circuito com o mundo, enquanto a arquitetura descreve o funcionamento do circuito. Isso tem de estar claro nesse ponto. Antes de entrar em detalhes das especificações da arquitetura, devemos dar um passo atrás e lembrar o que estamos fazendo com VHDL: estamos descrevendo um circuito digital. É importantíssimo pensar nisso. A tendência de alunos com alguma experiência em programação é ver o VHDL apenas como outra linguagem que devem aprender para passar em outra matéria. Isso pode até funcionar para esse propósito, mas isso é uma má abordagem. O VHDL é uma abordagem completamente difenrente à programação, mas como se assemelha em alguns aspectos a outras linguagens, há a tendência de vê-lo como tal. Deve-se ter em mente que, para toda abstração válida na programação em outras linguagens, no VHDL se está implementando algo fisicamente. Essa é a diferença fundamental entre linguagens de programação comuns e as que descrevem hardware. Declarações simultâneas As declarações são o coração da maioria das linguagens de programação. Elas representam quantidades finitas de "ações" a serem feitas. Em linguagens algorítmicas, como C ou Java, elas representam ações a serem feitas no processador, e, assim que terminada a ação, o processador começa a ação seguinte, especificada em algum lugar do código fonte associado. Isso faz sentido, e é de certa forma confortável para humanos que, como o processador, costumam fazer uma ação por vez. Por sua vez, o VHDL não funciona dessa maneira: têm-se a capacidade de executar um número (virtualmente) infinito de ações ao mesmo tempo. Isso é possível quando pensamos que fazemos um projeto de hardware com o VHDL, onde várias coisas acontecem paralelamente, ou seja, simultaneamente. A figura 11 a seguir mostra um exemplo simples de um circuito que executa múltiplas ações simultaneamente. A qualquer momento que uma entrada mudar, há a possibilidade da saída também mudar, o que é verdade para todos circuitos digitais em geral. Figura 11: Circuito que executa ações simultaneamente Aqui está a complicação: como somos humanos, somos capazes apenas de ler uma linha do código por vez de uma maneira sequencial, então como podemos descrever alguma coisa que é inerentemente paralela? Esse problema não aparece quando se discute algo inerentemente sequencial, como um algoritmo em linguagem de programação comum. Engenheiro Realce Engenheiro Realce Engenheiro Realce Engenheiro Realce Engenheiro Realce O paradigma da programação em VHDL se concentra no conceito de paralelismo de expressões e simultaneidade de descrições textuais de circuitos. A alma do VHDL são as declarações simultâneas, que se assemelham a declarações em linguagens algorítmicas comuns mas são significativamente diferentes porque executam mais de uma ação ao mesmo tempo. Para ilustrar, a figura 12 mostra o código que implementa o circuito da figura 11, que mostra quatro declarações de atribuição de sinal simultâneas (concurrent signal assignement statements). O símbolo "<=" é o operador de atribuição de sinal. A verdade é que não podemos escrever todas essas operações de uma só vez, mas devemos interpretá-las como se estivessem ocorrendo simultaneamente. Novamente, esse é o principal ponto a ser entendido até aqui. Se o pensamento algorítmico (sequencial) começar se sobressair, tente contê-lo imediatamente. A próxima seção trará mais detalhes sobre atribuição simultânea de sinais. G <= A AND B; H <= C AND D; I <= E AND F; J <= G OR H OR I; Figura 12: Código VHDL para o circuito da figura 11. A figura 13 mostra um código "C" que é similar ao código da figura 12. Nesse caso, as funções lógicas foram substituídas operadores matemáticos, e os operadores de atribuição de sinal por operadores de atribuição. Nesse código, cada linha é executada por vez, ao contrário do VHDL da figura 12. É importante ressaltar que a figura 13 NÃO é um código VHDL válido. G = A + B; H = C + D; I = E + F; J = G + H + I; Figura 13: código algorítmico SIMILAR ao da figura 12 (não é VHDL). O operador de atribuição de sinal "<=" Todas linguagens algorítmicas tem um tipo de operador de atribuição, por exemplo, Em "C" e Matlab, é o "=", enquanto em Pascal é ":=". Esses operadores indicam uma transferência de informações do lado direito para o lado esquerdo. Em VHDL, usa-se "<=", e é conhecido oficialmente como operador de atribuição de sinal, para deixar evidente o seu verdadeiro propósito. Ele especifica uma relação entre os sinais, ou seja, o sinal à esquerda do operador depende dos sinais à direitado mesmo. Sendo assim, você já deve entender o código da figura 12 e sua relação com a figura 11. A declaração "G = A AND B;" indica que o valor do sinal "G" representa a operação AND, com entradas A e B. Já no caso da programação algorítmica, "G = A + B;" indica que o valor representado pela variável A é adicionado ao valor representado pela variável B e o resultado da adição é atribuído à variável G. A distinção entre as declarações deve estar ficando mais clara agora. Há quatro tipos de declarações simultâneas que são examinadas nesse material. Já foi examinado brevemente a declaração simultânea de atribuição de sinal, e em breve ela será analisada mais a fundo e colocado no contexto de um circuito verdadeiro. Os outro três tipos de declarações simultâneas são declarações de processo, atribuição condicional de sinais e atribuição seletiva de sinais. Os 4 tipos de declarações são ferramentas que podem ser utilizadas para implementar circuitos digitais, e em breve, veremos a versatilidade desses tipos de declaração. Entretanto, deve-se ter em mente que, devido à essa versatilidade, há diversas maneiras de lidar com um mesmo problema. Assim, quando Engenheiro Realce Engenheiro Realce Engenheiro Realce Engenheiro Realce Engenheiro Realce Engenheiro Realce Engenheiro Realce Engenheiro Realce se analisar um exemplo deste material, é importante saber que este corresponde a uma solução de um conjunto muito grande delas, então é uma boa prática tentar resolvê-los de outra maneira, como exercício. Atribuições de sinal simultâneas A forma geral de uma atribuição simultânea é apresentada na figura 14, onde target é um sinal que recebe o valor de expression, que pode ser uma constante, um sinal ou um conjunto de operadores que operam em outros sinais e retornam algum valor. target <= expression; Figura 14: sitaxe da declaração de atribuição simultânea de sinal EXAMPLO 1 Escreva um código em VHDL para implementar uma porta lógica NAND de três entradas. As entradas devem ser nomeadas como A,B e C, e a saída como F. Solução: é boa prática sempre desenhar o diagrama do que se está projetando. Poderíamos ter mostrado diretamente o sinal da porta lógica NAND, mas usaremos uma caixa preta para não perder a generalidade. Assim, a entidade já está praticamente declarada. -- cabeçalho e bibliotecas library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.STD_LOGIC_ARITH.ALL; use IEEE.STD_LOGIC_UNSIGNED.ALL; entity my_nand3 is -- define o nome da entidade port ( A,B,C : in std_logic; F : out std_logic); end my_nand3; architecture ex_nand3 of my_nand3 is begin F <= NOT (A AND B AND C); -- -- Uma outra forma de se fazer: -- F <= A NAND B NAND C; -- end ex_nand3; Figura 15: resolução do exemplo 1 Esse exemplo contém alguns detalhes que devem ser comentados: • Deve-se inserir arquivos de cabeçalho (header files) e bibliotecas (library files) para que o código compile de maneira correta. As linhas que descrevem essa inserção estão descritas no topo do código da figura 15. As linhas aqui utilizadas possuem mais itens do que é necessário para este exemplo, mas alguns exemplos subsequentes precisarão de todas elas. • O exemplo destaca o uso de diversos operadores lógicos. Os operadores disponíveis em VHDL são AND, OR, NAND, NOR, XOR e XNOR. O operador NOT não é tecnicamente lógico mas também está disponível. O exemplo 1 demonstra o uso da declaração de atribuição de sinal simultânea em um programa VHDL, mas como só há uma declaração desse tipo, o conceito de simultaneidade não está evidente. A ideia por trás dessa declaração é que a saída muda sempre que um dos sinais de entrada mudar. Em outras palavras, a saída F é reavaliada sempre que um sinal na entrada muda. O exemplo a seguir ilustra melhor a ideia de simultaneidade. EXEMPLO 2 Escreva um código em VHDL que implemente a função descrita na tabela verdade a seguir: L M N F 0 0 0 0 0 0 1 1 0 1 0 0 0 1 1 0 1 0 0 0 1 0 1 0 1 1 0 1 1 1 1 1 Solução: O primeiro passo no processo é reduzir a função dada. Apesar de não ser obrigatório, pode ajudar a diminuir o tempo usado para escrever o código VHDL. Espera-se que o compilador em reduziria o código à forma mínima em algum ponto, mas isso pode ser um desejo frustrado. O diagrama da caixa preta e o código VHDL associado são mostrados na figura 16. F3L,M,N= L.M.N+ L.M ------------------------------------------------------------------------------------ - -- cabeçalho e bibliotecas devem ser aqui inseridos: não o foram para poupar espaço ------------------------------------------------------------------------------------ - entity my_ckt_f3 is port ( L,M,N : in std_logic; F3 : out std_logic); end my_ckt_f3; architecture f3_1 of my_ckt_f3 is signal A1, A2 : std_logic; -- sinais intermediários (estão dentro da caixa preta) begin A1 <= ((NOT L) AND (NOT M) AND N); A2 <= L AND M; F3 <= A1 OR A2; end f3_1; Figura 16: solução do exemplo 2 Esse exemplo contém novos conceitos e ideias. É importante notar que as informações de cabeçalho e bibliotecas foram suprimidas, e isso vai acontecer em todos os exemplos subsequentes desse material. Então, para implementar os códigos de exemplos, deve-se sempre inserir o cabeçalho e as bibliotecas, como no exemplo 1. O mais importante, entretanto, é notar que esse código demonstra a utilização de declarações de sinal (abaixo da declaração da arquitetura), que são usadas para declarar sinais intermediários. Essa abordagem é análoga a declarar variáveis extras em linguagens de programação algorítmica, onde pode-se utilizá-las para salvar resultados intermediários que, no geral, não são o ponto de interesse de um determinado programa. A declaração desses sinais é feita de maneira análoga à declaração de ports na entidade. A figura 17 ilustra outra arquitetura que implementa a tabela- verdade, onde não é necessário fazer a declaração desses sinais. architecture f3_2 of my_ckt_f3 is begin F3 <= ((NOT L) AND (NOT M) AND N) OR (L AND M); end f3_2; Figura 17: arquitetura alternativa à f3_1 Apesar de as arquiteturas f3_1 e f3_2 das figuras 16 e 17 serem diferentes, elas funcionam da mesma maneira. Isso porque todas as declarações são de atribuições simultâneas de sinal, porque, mesmo f3_1 tendo três declarações desse tipo e f3_2 apenas uma, as declarações em f3_1 são executadas simultaneamente. O exemplo 2 demonstra que pode-se facilmente converter uma função em formato de tabela- verdade para código VHDL. A conversão da função simplificada para declarações de atribuição simultâneas de sinal foi de certa forma bem direta. A facilidade para se implementar ambas as arquiteturas é quase a mesma, mas deve-se notar que o exemplo 2 é bem simples, tinha o objetivo de ilustrar as atribuições simultâneas de sinal. Para circuitos muito complexos, entretanto, essa abordagem fica tediosa, e uma alternativa é mostrada na seção a seguir. Atribuição condicional de sinal As declarações de atribuição simultânea relacionam um alvo com uma expressão. O termo atribuição condicional de sinal é usado para descrever declarações que tenham apenas um alvo mas Engenheiro Realce Engenheiro Realce podem ter diversas expressões associadas, cada qual com uma determinada condição. Cada condição é avaliada sequencialmente até a primeira delas ser verdadeira (TRUE), onde a expressão associada a esta condição é avaliada e atribuída ao alvo. Apenas uma atribuição é usada. A sintaxe de atribuição condicional de sinal é mostrada abaixo. O alvo nesse caso é o identificador de um sinal, e a condição é baseada no estado de outros sinais no circuito. É importante notar quehá apenas um operador de atribuição de sinal ("<=") com a declaração de atribuição condicional. target <= expression when condition else expression when condition else expression; Figura 18: sintaxe da declaração de atribuição condicional de sinal Essa é talvez a forma mais fácil de entender no contexto de um circuito. Por exemplo, pode-se refazer o exemplo 2 usando a atribuição condicional de sinal: EXEMPLO 3 Escreva um código em VHDL que implemente a função descrita na tabela verdade a seguir: L M N F 0 0 0 0 0 0 1 1 0 1 0 0 0 1 1 0 1 0 0 0 1 0 1 0 1 1 0 1 1 1 1 1 Solução: A entidade não muda do exemplo 2, então a resposta será simplificada como apenas a arquitetura. architecture f3_3 of my_ckt_f3 is begin F3 <= ‘1’ when (L = ‘0’ AND M = ‘0’ AND N = ‘1’) else ‘1’ when (L = ‘1’ AND M = ‘1’) else ‘0’; end f3_3; Figura 19: Solução do exemplo 3 Alguns pontos interessantes: • Essa arquitetura parece dar mais trabalho que as anteriores, por ter mais entradas. • De fato, percebe-se que há apenas um alvo e diversas condições e expressões. A última expressão é a exceção às outras, ou seja, ela só é executada se nenhuma outra for TRUE. Há razões mais fortes para se utilizar a atribuição condicional de sinal, e a mais clássica é a implementação de um multiplexador (MUX), descrita no exemplo a seguir. EXEMPLO 4 Escreva um código VHDL que implemente um multiplexador 4:1 usando uma única declaração de atribuição condicional de sinal. As entradas devem ser os dados D3, D2, D1, D0 e um bus de controle de duas entradas SEL. A saída deve ser única, e MX_OUT. Solução: Nesse exemplo deve-se recomeçar a descrição do problema. Isso incluí o diagrama de caixa preta e a entidade associada. A figura 20 mostra a solução completa. ----------------------------------------------------------------- --entidade e arquitetura do multiplexador 4:1 implementado usando --atribuição condicional de sinal --(não esquecer cabeçalho e bibliotecas se for compilar) ----------------------------------------------------------------- entity my_4t1_mux is port ( D3,D2,D1,D0 : in std_logic; SEL : in std_logic_vector(1 downto 0); MX_OUT : out std_logic); end my_4t1_mux; architecture mux4t1 of my_4t1_mux is begin MX_OUT <= D3 when (SEL = “11”) else D2 when (SEL = “10”) else D1 when (SEL = “01”) else D0 when (SEL = “00”) else ‘0’; end mux4t1; Figura 20: solução do exemplo 4: 4:1 MUX usando atribuição condicional de sinal Pontos interessantes: • A solução parece eficiente se comparada à quantidade de lógica que seria necessária caso fossem utilizadas atribuições simultâneas de sinal. Além disso, o código está bem legível. • O operador relacional "=" é usado em conjunto com um bus de sinais. Nesse caso, os valores do bus SEL são acessados usando aspas, e não apóstrofes. • Apenas para ser mais completo, foram incluídas todas as possibilidades do sinal SEL além de um else. Poderíamos ter mudado a linha contendo '0' para D0 e removido a linha associada à condição SEL = "00", sendo assim, um descrição mas elegante da solução. É importante ressaltar que, sempre que se fazemos uma atribuição condicional de sinal, fisicamente temos um multiplexador implementado. Para fins de projeto, é muito importante ter consciência disso. Engenheiro Realce Lembre-se que a atribuição condicional é um tipo de declaração simultânea, onde a declaração é executada sempre que ocorrer uma mudança nos sinais da condição. Esse fato se assemelha à declaração de atribuição simultânea, onde a declaração é executada sempre que um dos sinais à direita do operador <= muda. A atribuição condicional de sinal é um pouco menos intuitiva que a atribuição simultânea, mas pode-se pensar nela de outra forma: ela se assemelha, em função, às construções if-else das linguagens de programação comuns. Essa relação será melhor explorada quando falarmos de declarações sequenciais. Atribuição seletiva de sinal A atribuição seletiva de sinal é a terceira forma de declarações simultâneas que será explorada. Essas declarações podem ter apenas um sinal alvo, e uma expressão determina em que são baseadas as escolhas. Isso ficará mais claro a seguir. A sintaxe desse tipo de declaração é mostrada abaixo. with chooser_expression select target <= {expression when choices, } expression when choices; Figura 21: sintaxe da atribuição seletiva de sinal EXEMPLO 5 Escreva um código em VHDL que implemente a função descrita na tabela verdade a seguir, usando apenas a atribuição seletiva de sinal. L M N F 0 0 0 0 0 0 1 1 0 1 0 0 0 1 1 0 1 0 0 0 1 0 1 0 1 1 0 1 1 1 1 1 Solução: Esse é outra versão do exemplo 2. O diagrama de caixa preta e a declaração de entidade desse exemplo continuam as mesmas da figura 16, e a solução está na figura 22. architecture f3_4 of my_ckt_f3 is begin with ( (L = ’0’ AND M = ’0’ and N = ’1’) or (L = ’1’ AND M = ’1’) ) select F3 <= ‘1’ when ‘1’, ‘0’ when ‘0’, ‘0’ when others; end f3_4; Figura 22: solução ao exemplo 5 EXEMPLO 6 Escreva um código VHDL que implemente um multiplexador 4:1 usando uma única declaração de atribuição seletiva de sinal. As entradas devem ser os dados D3, D2, D1, D0 e um bus de controle de duas entradas SEL. A saída deve ser única, e MX_OUT. Solução: Essa é uma repetição do exemplo 4, exceto que a atribuição deve ser seletiva, não condicional. A declaração de entidade não muda, mas ela é repetida na figura 23. O diagrama de caixa preta é o mesmo da figura 20, e não é repetido. ----------------------------------------------------------------- -- Entidade e arquitetura do MUX 4:1 do exemplo 6 -- Adicionar cabeçalho e bibliotecas caso queira compilar ----------------------------------------------------------------- entity my_4t1_mux is port ( D3,D2,D1,D0 : in std_logic; SEL : in std_logic_vector(1 downto 0); MX_OUT : out std_logic); end my_4t1_mux; architecture mux4t1_2 of my_4t1_mux is begin with SEL select MX_OUT <= D3 when “11”, D2 when “10”, D1 when “01”, D0 when “00”, ‘0’ when others; end mux4t1_2; Figura 23: Solução do exemplo 6 Novamente, há pontos a se destacar no exemplo 6, listados abaixo: • O código possui diversas semelhanças com o da solução do exemplo 4. A aparência geral é a mesma. Ambas as soluções são bem mais eficientes que se fosse usada atribuição simultânea de sinal de maneira direta. • Ao invés de usar else, como na atribuição condicional, usa-se when others, para tratar dos casos não abordados pelas condições listadas. • O circuito do exemplo é um MUX 4:1, onde cada possível condição da chooser_expression (figura 21) - expressão de escolha - tem uma expressão correspondente nas atribuições de sinal. Isso não é necessário, mas é essencial que haja uma linha com when others no final da declaração de atribuição seletiva. EXEMPLO 7 Escreva um código VHDL que implemente o seguinte circuito, que contém um bus de entrada contendo 4 sinais e um de saída contendo 3 sinais. O bus de entrada, D_IN, representa um número binário de 4 bits, e o bus de saída, SZ_OUT, é usado para indicar a magnitude do número de entrada. A relação entre entrada e saída é mostrada na tabela abaixo. Use uma declaração seletiva de sinal na solução. Margem de entrada de D_IN Valor de saída de SZ_OUT 0000 até 0011 100 0100 até 1001 010 1010 até 1111 001 Condição desconhecida 000 Solução: ----------------------------------------------------------------- -- Um circuito decodificador para o exemplo 7, usandoatribuição -- seletiva de sinal. -- -- Adicionar cabeçalho e bibliotecas caso queira compilar ----------------------------------------------------------------- entity my_ckt is port ( D_IN : in std_logic_vector(3 downto 0); SX_OUT : out std_logic_vector(2 downto 0)); end my_ckt; architecture spec_dec of my_ckt is begin with D_IN select SX_OUT <= “100” when “0000” | “0001” | “0010” | “0011”, “010” when “0100” | “0101” | “0110” | “0111” | “1000” | “1001”, “001” when “1010” | “1011” | “1100” | “1101” | “1110” | “1111”, “000” when others; end spec_dec; Figura 24: Solução do exemplo 7 O único comentário dessa solução é que a barra vertical é usada como um caractere de seleção na seção choices (figura 21) da declaração de atribuição seletiva de sinal. Isso aprimora a legibilidade do código. É importante ressaltar novamente que a atribuição seletiva de sinal é outra forma de atribuição simultânea, o que se justifica porque sempre que a chooser_expression mudar, a atribuição seletiva será reavaliada. Um último comentário sobre a solução do exemplo 7: ela é comparável à declaração switch ou case de linguagens algorítmicas de programação como "C" e Java no caso da primeira, e Matlab no caso da segunda. Isso será melhor explorado na seção de declarações sequenciais. A declaração de processo A declaração de processo é o último tipo de atribuição simultânea que será abordada, mas primeiro, é necessário dar alguns passos para trás e explorar outras definições e princípios de VHDL que não foram detalhados até este ponto. Lembre-se que há mais de mil maneiras de aprender alguma coisa, especialmente uma linguagem de programação, onde há diversas soluções para um mesmo problema. Portanto, optou-se aqui por detalhar esse tipo de declaração depois, para ficar mais claro. Engenheiro Realce Arquiteturas comuns em VHDL Como você deve se lembrar, a arquitetura do VHDL descreve a função de alguma entidade. A arquitetura é composta de duas partes: a seção de declaração, onde se nomeia a arquitetura, seguida de uma coleção de declarações simultâneas. Estudamos três tipos de declarações simultâneas até este ponto: atribuição de sinal simultânea, atribuição condicional de sinal e a atribuição seletiva de sinal. Há três diferentes estilos para se escrever arquiteturas no VHDL. Elas são fluxo de dados, estrutural e comportamental. Geralmente, se introduz cada estilo individualmente, usando como exercício elaborar circuitos para cada um deles, o que é uma abordagem boa por ser simples, mas é um pouco inapropriada quando se fala de circuitos mais complexos, onde se utiliza uma mistura dos estilos de programação. É importante ter isso em mente na discussão que segue, onde o foco estará nos estilos de fluxo de dados e comportamental. A modelagem pelo estilo estrutural é essencialmente um método de combinar um conjunto de modelos. Por esse motivo, não tanto um método de modelagem quanto uma forma de interligar modelos previamente elaborados. A arquitetura por fluxo de dados Até este ponto, todos os exemplos deste material foram feitos utilizando o estilo de fluxo de dados. Esse estilo de arquitetura especifica um circuito como uma representação simultânea do fluxo dos dados que atravessam o circuito. Os circuitos são descritos mostrando-se a relação entre entrada e saída, utilizando os componentes existentes na linguagem VHDL (AND,OR,XOR,NOT,etc). As três formas de declarações simultâneas que detalhamos até agora são todas encontradas no estilo de fluxo de dados. Se você examinar alguns exemplos que fizemos até agora, poderá de fato ver como os dados fluem pelo circuito, pelas declarações de atribuição de sinal, que realmente descrevem como os dados dos sinais do lado direito do operador <= fluem para o lado esquerdo do mesmo. O estilo de arquitetura de fluxo de dados tem seus pontos fortes e fracos. Ele possibilita a visualização do fluxo de dados no circuito por simples inspeção do código, além de ser possível fazer uma boa predição de como o circuito lógico seria montado fisicamente. Para circuitos mais complexos, entretanto, é vantajoso utilizar a modelagem pelo estilo comportamental. O estilo comportamental de arquitetura Em comparação com o estilo por fluxo de dados, o estilo comportamental não nos provém de detalhes de como o modelo é implementado em hardware, ou seja, o código VHDL escrito no estilo comportamental não necessariamente reflete como o circuito é implementado quando sintetizado. Ao contrário, ele descreve como as saídas reagem (ou se comportam) às entradas. O estilo comportamental é essencialmente a utilização da abordagem da caixa-preta para modelar os circuitos, onde efetivamente não se sabe o que acontece dentro da caixa, apenas se sabe a saída para cada entrada. A ferramenta de síntese do VHDL é o que decide como o circuito será descrito fisicamente. Todo esse estilo está centrado na declaração de processo, quarto tipo de declaração simultânea sobre a qual será falado. Ele é significativamente diferente dos outros três tipos estudados até aqui. A maior diferença reside na abordagem de simultaneidade desse tipo de declaração, que é a grande dificuldade no aprendizado da declaração de processo. Engenheiro Realce Engenheiro Realce Engenheiro Realce Engenheiro Realce A declaração de processo Para compreender a declaração de processo, vamos primeiramente examinar as suas semelhanças com a declaração de atribuição simultânea de sinal, para depois detalharmos as diferenças entre esses dois tipos de declaração. A figura 25 ilustra a sintaxe da declaração de processo. O mais importante de se notar é que o corpo da declaração de processo consiste de declarações sequenciais, como as utilizadas nas linguagens de programação usuais (C, Java, Pascal, etc). Essa é a principal diferença entre as declarações simultâneas de atribuição de sinal e as de processo, mas vamos focar agora nas semelhanças. label: process(sensitivity_list) begin {sequential_statements} end process label; Figura 25: sintaxe da declaração de processo A seguir, a figura 26 mostra a declaração de entidade para uma função XOR, enquanto a figura 27 mostra dois estilos de arquiteturas possíveis, um usando fluxo de dados e outro comportamental. A maior diferença reside na presença da declaração de processo no estilo comportamental. Lembre-se que a declaração de atribuição simultânea de sinal na descrição por fluxo de dados opera da seguinte forma: a qualquer momento que houver uma mudança nos sinais listados à direita do operador <=, o sinal à esquerda será reavaliado. No caso da descrição comportamental da arquitetura, sempre que um sinal presente na sensitivity_list de uma declaração de processo mudar, todas as declarações sequenciais do processo serão reavaliadas. Ou seja, a avaliação do processo é controlada pelos sinais presentes na "lista de sensibilidade" do processo, enquanto para as atribuições de sinal, para qualquer mudança, é necessária reavaliação. Essas abordagens são essencialmente as mesmas, apenas com uma mudança significativa na sintaxe. Neste ponto, pode ficar um tanto estranho. Mesmo que ambas as arquiteturas da figura 27 tenham exatamente a mesma atribuição de sinal (F <= A XOR B;), a execução dessa atribuição no modelo comportamental é controlada pelos sinais que aparecem na lista de sensibilidade do processo, enquanto para o fluxo de dados, a atribuição é executada sempre que houver mudança nos sinais A ou B. Aqui nota- se uma diferença na funcionalidade de cada estilo. entity my_xor_fun is port ( A,B : in std_logic; F : out std_logic); endmy_xor_fun; Figura 26: declaração de entidade para um circuito que implementa a função XOR architecture my_xor_dataflow of my_xor_fun is begin F <= A XOR B; end my_xor_dataflow; architecture my_xor_behavioral of my_xor_fun is begin xor_proc: process(A,B) begin F <= A XOR B; end process xor_proc; end my_xor_behavioral; Figura 27: Descrição da entidade my_xor_fun usando as descrições por fluxo de dados e comportamental. A outra grande diferença entre as arquiteturas por fluxo de dados e comportamental reside no corpo da declaração de processo, que contém apenas declarações sequenciais. A seguir, mostramos alguns tipos dessas declarações. Declarações sequenciais O termo "declaração sequencial" vem do fato de as declarações dentro do corpo de um processo são executadas sequencialmente, ou seja, uma de cada vez. A execução das declarações sequenciais começa quando ocorre uma mudança em algum sinal contido na lista de sensibilidade do processo, e termina quando todas as declarações de dentro do processo forem executadas. Há três tipos de declarações simultâneas com as quais trabalharemos. Com a primeira (atribuição de sinal), já estamos bem familiarizados, então não a discutiremos com muitos detalhes. As outras duas são if e case, provavelmente já discutidas em outra(s) disciplina(s) de programação algorítmica. Em VHDL, a estrutura e função das declarações if e case são exatamente as mesmas. A declaração de atribuição de sinal Nesse ponto, ficará mais claro porque sempre temos o cuidado de falar atribuição simultânea de sinal quando se trabalha com a descrição por fluxo de dados. Sintaticamente, a atribuição de sinal em um processo e a atribuição simultânea de sinal são equivalentes. Entretanto, em um processo, todas as declarações são sequenciais, por isso há essa diferença na nomenclatura da declaração de atribuição de sinal. Por exemplo, na arquitetura por fluxo de dados da figura 27, a atribuição de sinal é do tipo simultânea, enquanto na arquitetura comportamental, a mesma atribuição de sinal é sequencial. As diferenças funcionais entre declarações simultâneas e sequenciais já foram discutidas na seção de declarações de processos. Declaração IF A declaração if é usada para criar um ramo no fluxo de execução de declarações sequenciais. Dependendo das condições listadas no corpo da declaração if, podem ser realizadas as instruções de um ou de outro (ou até mesmo de nenhum) ramo do código. A forma geral de uma declaração if é mostrada na figura 28. Engenheiro Realce Engenheiro Realce Engenheiro Realce Engenheiro Realce Engenheiro Realce Engenheiro Realce if (condition) then { sequence of statements } elsif (condition) then { sequence of statements } else { sequence of statements } end if; Figura 28: Sintaxe da declaração if A ideia da declaração if deve ser familiar para você em duas olhadas. Primeiramente, sua forma e função são parecidas com a maioria das linguagens de programação algorítmicas; a sintaxe, entretanto, é um pouco diferente. Além disso, o if em VHDL é a declaração sequencial equivalente da declaração de atribuição condicional de sinal. Essas duas declarações tem essencialmente a mesma função, mas a declaração if é a declaração sequencial encontrada dentro de um processo, enquanto a declaração de atribuição condicional de sinal é uma forma de atribuição simultânea. Pontos importantes da declaração if: • Os parênteses usados ao redor da expressão de condição condition são opcionais, mas é bom incluí-los para ter uma melhor legibilidade do código. • Cada declaração if ou elseif possui um then associado (traduzindo: "se ... então; "caso contrário, se ... então"). O último else não possui then associado. • Como na figura 28, o else é uma expressão que abrange todas as exceções. Caso nenhuma das condições prévias for avaliada como verdadeira, a sequência de declarações associada ao último else é executada. Dessa forma, ao menos uma sequência de instruções presentes no corpo do if será executada. • O último else, entretanto, é opcional. A não inclusão do mesmo pode implicar na não execução de nenhuma sequência de declarações do if. EXAMPLO 8 Escreva um código em VHDL que implemente a seguinte função lógica usando uma declaração IF FoutA,B,C= A.B.C+ B.C Solução: Embora não seja dito diretamente na descrição do problema, o código deve ser feito utilizando a arquitetura comportamental, uma vez que deve ser usada uma declaração if. O código de solução é mostrado na figura 29, onde não é mostrado o diagrama de caixa-preta devido à simplicidade do problema. entity my_ex_7 is port ( A,B,C : in std_logic; F_OUT : out std_logic); end my_ex_7; architecture dumb_example of my_ex_7 is begin proc1: process(A,B,C) begin if (A = ‘1’ and B = ‘0’ and C = ‘0’) then F_OUT <= ‘1’; elsif (B = ‘1’ and C = ‘1’) then F_OUT <= ‘1’; else F_OUT <= ‘0’; end if; end process proc1; end dumb_example; Figura 29: Solução do exemplo 8 Essa provavelmente não é a melhor maneira de implementar a função lógica, mas serviu para ilustrar a declaração if. Outra possível arquitetura que soluciona o problema é mostrada na figura 30, enquanto o exemplo 9 apresenta um problema em que o uso da declaração if é mais adequada. architecture bad_example of my_ex_7 is begin proc1: process(A,B,C) begin if (A = ‘0’ and B = ‘0’ and C = ‘0’) or (B = ‘1’ and C = ‘1’) then F_OUT <= ‘1’; else F_OUT <= ‘0’; end if; end process proc1; end bad_example; Figura 30: solução alternativa ao problema do exemplo 8 EXAMPLO 9 Escreva um código VHDL que implemente o MUX 8:1 mostrado abaixo. Utilize uma declaração if. Solução: A figura 31 mostra uma possível solução para o problema. entity mux_8t1 is port ( Data_in : in std_logic_vector (7 downto 0); SEL : in std_logic_vector (2 downto 0); F_CTRL : out std_logic); end mux_8t1; architecture my_8t1_mux of mux_8t1 is begin my_proc: process (Data_in,SEL) begin if (SEL = “111”) then F_CTRL <= Data_in(7); elsif (SEL = “110”) then F_CTRL <= Data_in(6); elsif (SEL = “101”) then F_CTRL <= Data_in(5); elsif (SEL = “100”) then F_CTRL <= Data_in(4); elsif (SEL = “011”) then F_CTRL <= Data_in(3); elsif (SEL = “010”) then F_CTRL <= Data_in(2); elsif (SEL = “001”) then F_CTRL <= Data_in(1); elsif (SEL = “000”) then F_CTRL <= Data_in(0); else F_CTRL <= ‘0’; end if; end process my_proc; end my_8t1_mux; Figura 31: Solução ao problema do exemplo 9 EXAMPLO 10 Escreva um código VHDL que implemente o MUX 8:1 mostrado abaixo, utilizando quantas declarações if forem necessárias. No diagrama de caixa preta abaixo, a entrada CE é um "chip enable", ou seja, quando essa entrada for "1", a saída será como a do MUX do exemplo 9, e quando for "0", a saído do MUX é "0". Solução: A solução para o exemplo 10 é similar à do exemplo 9. Note que nessa solução as declarações if podem ser aninhadas (colocadas umas dentro das outras) para melhorar o código. A figura 32 exibe a solução do exemplo 10. Figura 32: Solução ao problema do exemplo 10 Declarações CASE A declaração case é de certa forma similar à declaração if no sentido que uma sequência de declarações é executada quando sua expressão associada for verdadeira. A figura 33 abaixo ilustra a sintaxe da declaração case. case (expression) is when choices => {sequential statements} when choices => {sequential statements}when others => {sequential statements} end case; Figura 33: Sintaxe da declaração case Como no caso da declaração if, a case deve estar familiar em poucas olhadas. Primeiramente, pode ser considerada uma forma diferente e talvez mais compacta do if, não sendo, entretanto, tão funcional quanto. Além disso, o case também é utilizado em outras linguagens de programação, com muitas similaridades em formato e função. Finalmente, o case em VHDL é a declaração sequencial equivalente à da atribuição seletiva de sinal, que é um tipo de atribuição simultânea. A linha " when others " não é necessária, mas é uma boa prática incluí-la para trabalhar com situações inesperadas. A seguir, um exemplo de como se usar a declaração case. EXEMPLO 11 Escreva um código em VHDL que implemente a seguinte função usando uma declaração case Fout(A,B,C) = A.B.C + B.C Solução: Novamente, este exemplo cai na categoria de não ser a melhor forma de implementar o circuito usando o VHDL. Entretanto, ele ilustra o uso da declaração case, outra ferramenta importante em VHDL. A primeira parte da solução requere que listemos a função como uma soma de mintermos. Se este assunto ainda não foi abordado em sala, não se preocupe: apenas tenha em mente que a seguinte simplificação ajuda a resolver este problema, e, em breve, este assunto será detalhado em sala. De forma simplificada, multiplica-se o produto que não possui termo com A por 1, no caso, A+A. Assim, teremos a função lógica como uma soma de produtos, sendo que cada produto possui todas as variáveis inclusas uma única vez: Fout(A,B,C) = A.B.C + A.B.C+A.B.C. A segunda parte da solução é mostrada na figura 34, onde é interessante notar que o agrupamento dos três sinais de entrada nos possibilitou utilizar uma declaração case como solução. Essa abordagem demandou a definição de um sinal intermediário "ABC". Vale ressaltar que essa provavelmente não é a forma mais eficiente de solucionar este problema. entity my_example is port ( A,B,C : in std_logic; F_OUT : out std_logic); end my_example; architecture my_soln_exam of my_example is signal ABC: std_logic_vector(2 downto 0); begin ABC <= (A,B,C); -- Agrupa os sinais para usar o case my_proc: process (ABC) begin case (ABC) is when “100” => F_OUT <= ‘1’; when “011” => F_OUT <= ‘1’; when “111” => F_OUT <= ‘1’; when others => F_OUT <= ‘0’; end case; end process my_proc; end my_soln_exam; Figura 34: Solução para o exemplo 11 A seguir, refazemos o exemplo 10, mas usando o case. Note a mudança quanto à legibilidade. EXEMPLO 12 Escreva um código com VHDL que implemente o MUX 8:1 mostrado abaixo, utilizando uma declaração case. No diagrama de caixa preta abaixo, a entrada CE é um "chip enable", ou seja, quando ela for "1", o chip está ligado e a saída é a de um MUX normal; quando for "0", o chip está desativado e a saída é "0". Solução: A solução é mostrada abaixo, na figura 35. A declaração de entidade foi repetida para sua conveniência. A solução insere o case dentro de uma declaração if. Caso ainda não esteja claro, o número de possíveis soluções para um problema aumenta com a complexidade do mesmo. entity mux_8t1_ce is port ( Data_in : in std_logic_vector (7 downto 0); SEL : in std_logic_vector (2 downto 0); CE : in std_logic; F_CTRL : out std_logic); end mux_8t1_ce; architecture my_case_ex of mux_8t1_ce is begin my_proc: process (SEL,Data_in,CE) begin if (CE = ‘1’) then case (SEL) is when “000” => F_CTRL <= Data_in(0); when “001” => F_CTRL <= Data_in(1); when “010” => F_CTRL <= Data_in(2); when “011” => F_CTRL <= Data_in(3); when “100” => F_CTRL <= Data_in(4); when “101” => F_CTRL <= Data_in(5); when “110” => F_CTRL <= Data_in(6); when “111” => F_CTRL <= Data_in(7); when others => F_CTRL <= ‘0’; end case; else F_OUT <= ‘0’; end if; end process my_proc; end my_case_ex; Loop FOR Como em outras linguagens de programação, o loop for é usado quando o programador sabe o número de iterações que o loop irá realizar. Geralmente, declara-se o intervalo sobre o qual serão realizadas as iterações. Esse intervalo pode ser descrito de duas formas: a) o intervalo pode ser especificado na declaração do loop for ou b) o loop pode usar um intervalo definido anteriormente. Algumas observações devem ser feitas a respeito da variável de indexação para evitar futuros erros ao se compilar o código: • A varável de indexação não precisa ser declarada. • A variável de indexação não pode ser usada em atribuições. Porém, ela pode ser usada para cálculos dentro do loop. • A variável de indexação só é incrementada no loop em passos de um. • Como mostrado na figura anterior, o comando downto pode ser usado para declarar o intervalo da iteração. Loops WHILE Loops while não possuem uma variável de indexação e são, portanto, considerados mais simples de se trabalhar do que os loops for. A principal diferença entre os loops for e while é o fato de a estrutura do loop while não conter um critério de parada já embutido, como ocorre com o loop for. Ainda assim, o código associado ao loop while deve conter algum critério para que o loop termine. Na figura abaixo, há alguns exemplos: Figura 35: Loops for equivalentes que em a) especifica o intervalo e em b) usa um intervalo especificado anteriormente. Figura 36: Dois exemplos para o cálculo da sequência Fibonacci usando while. Declarações NEXT e EXIT A declaração next permite que o loop ignora as instruções restantes da linha de comando e inicie a próxima iteração. A declaração exit implica no término imediato da iteração: Figura 37: Exemplos da declaração next em loops for e while. Figura 38: Exemplos da declaração exit em loops for e while. Operadores Esta seção irá apresentar listagem dos principais operadores usados em VHDL. Serão apresentados alguns operadores lógicos, relacionais, de troca, de adição, de incremento, de multiplicação e outros que não se encaixam nos anteriores. Essa ordem na qual foram listados, é a ordem de prioridade a partir da qual os operadores serão analisados. Essa prioridade só existe entre os tipos de operadores. Em uma mesma classe de operadores, não existe diferença de prioridade; essa, se existir, deve ser indicada pelo uso de parênteses. Abaixo segue uma tabela com os operadores mais comuns usados em VHDL: Tipos de operadores Lógicos and or nand nor xor xnor not Relacionais = /= < <= > >= Troca s11 sr1 s1a sra ro1 ror Adição + - Sinal + - Multiplicação * / abs rem Outros ** abs & Operadores lógicos São auto-explicativos: utilizados para realizar as operações lógicas entre os sinais. Operadores relacionais Os operadores relacionais irão relacionar variáveis de diversas formas. Alguns deles já foram utilizados em exemplos anteriores e uma lista com a função de cada um segue abaixo: Operador Nome Explicação = Equivalência O valor x é equivalente ao valor y? /= Não – equivalência O valor x é não-equivalente ao valor y? < Menor que O valor x é menor que o valor y? <= Menor ou igual a O valor x é menor ou igual ao valor y? > Maior O valor x é maior que o valor y? >= Maior ou igual a O valor x é maior ou igual ao valor y? Operadores de troca Há três tipos de operadores de troca: troca simples, troca aritmética e rotação. A função desses operadores é basicamente acrescentar zeros à palavra binária em alguma ordem, seja da direita pra esquerda, ou da direita pra esquerda. As poucas diferenças entre eles podem ser vistas abaixo: • Operadores de troca simples: acrescentam zeros à palavrabinária em uma determinada ordem, dependendo do comando utilizado. O operador ssl (shift left) acrescenta zeros da direita pra esquerda e o operador ssr (shift right), da esquerda para direita. Exemplo: tomando a palavra “110111” iremos realizar as duas operações em cima dela: result <= “110111” ssl 2 “011100” Nesse caso, acrescentam- se dois zeros no final da palavra binária, fazendo com que os outros bits se desloquem para a esquerda. result <= “110111” ssr 3 “000110” Nesse caso, acrescentam- se três zeros no início da palavra binária, fazendo com que os outros bits se desloquem para a direita. • Operadores de troca aritmética: fazem a mesma operação dos operadores de troca simples com a diferença de que os bits mais significativos (bits de sinal) não são alterados. O comando sla (shift left arithmetic) é equivalente ao ssl e o comando slr (shift left arithmetic) é equivalente ao ssr. Ou seja, : result <= “110011” sla 2 “101100” Nesse caso, acrescentam- se dois zeros no final da palavra binária, fazendo com que os outros bits se desloquem para a esquerda, sem que o bit de sinal seja alterado. result <= “110011” slr 3 “100010” Nesse caso, acrescentam- se três zeros no início da palavra binária, fazendo com que os outros bits se desloquem para a direita, sem que o bit de sinal seja alterado. • Operadores de rotação: transportam uma quantidade de bits definida pelo usuário de uma das extremidades de uma palavra para sua outra extremidade. O comando rol (rotate left) transporta os bits da extremidade esquerda para a extremidade direita e o comando ror (rotate right) faz o contrário, transporta os bits da extremidade direita para a extremidade esquerda. Exemplos: result <= “101000” rol 2 “010010” Os dígitos “10” em negrito foram levados da extremidade esquerda para a direita pelo comando rol. result <= “101001” ror 2 “011010” Os dígitos “01” em negrito foram levados da extremidade direita para a esquerda pelo comando ror. Operadores de troca Os outros operadores são geralmente utilizados para manipulação numérica, sendo alguns de utilidade bem óbvia. A lista abaixo segue com alguns detalhes desses operadores sendo que os operadores mod, rem e & serão um pouco mais detalhados. Operador Nome Comentário Adição + Adição Calcula a operação entre dois números. - Subtração Calcula a operação entre dois números. Sinal + Identidade Especifica o sinal de um número. - Negação Especifica o sinal de um número. Multiplicação * Multiplicação Calcula a operação entre dois números. / Divisão Calcula a operação entre dois números. mod Módulo rem Resto Outros ** Exponenciação Calcula a operação entre dois números. abs Valor absoluto Calcula o valor absoluto de um número. & Concatenação • Operador de concatenação (&): muito usado em circuitos digitais pois é possível atribuir ao valor de uma variável, o valor de outras variáveis concatenadas. Por exemplo: signal A_val, B_val : std_logic_vector(3 downto 0); signal C_val, E_val : std_logic_vector(6 downto 0); signal D_val, F_val : std_logic_vector(8 downto 0); C_val <= A_val & “000”; E_val <= “111” & B_val; D_val <= “00001” & A_val; F_val <= A_val & B_val & “0”; Pelo código acima, concluimos o seguinte: ✗ “C_val” é um vetor de 7 posições. As quatro primeiras posições serão preenchidas pelas componentes do vetor “A_val” e as outras três por zeros (“000”); ✗ “E_val” é um vetor de 7 posições. As três primeiras posições serão preenchidas por uns (“111”) e as outras quatro pelas componentes do vetor “B_val”; ✗ “D_val” é um vetor de 9 posições. As cinco primeiras posições serão preenchidas pelo conjunto de bits “0001” e as outras quatro pelas componentes do vetor “B_val”; ✗ “F_val” é um vetor de 9 posições. As quatro primeiras posições serão preenchidas pelas componentes do vetor “A_val”, as quatro seguintes pelas componentes do vetor “B_val”, e a última posição é preenchida por um zero. • Operador de módulo (mod) e de resto (rem): esses dois operadores possuem definições muito específicas na linguagem. Suas definições e alguns exemplos são mostrados abaixo: Operador Nome Definições Comentários rem Resto (remainder) 1. (X rem Y) tem o mesmo sinal de X; 2. abs(X rem Y) < abs (X); 3. X = (X/Y)*X + (X rem Y). mod Módulo 1. (X mod Y) tem o mesmo sinal de Y; 2. abs (X mod Y) < abs (Y); 3. X = Y*N + (X mod Y) N é um valor inteiro rem mod 8 rem 5 = 3 -8 rem 5 = -3 8 rem -5 = 3 -8 rem -5 = -3 8 mod 5 = 3 -8 mod 5 = 3 8 mod -5 = -3 -8 mod -5 = -3 Objetos de dados Esta seção tem como objetivo explicar um pouco da teoria por trás da linguagem VHDL apresentando os objetos da linguagem. Objeto é um item da linguagem que possui um nome (identificador associado) e um tipo específico. Existem quatro tipos de objetos e diversos tipos de dados em VHDL. Alguns serão discutidos nesta seção. Tipos de objetos de dados - Declaração de objetos de dados Como mencionado anteriormente, existem quatro tipos de objetos de dados: sinais (signals), variáveis (variables), constantes (constants) e arquivos (files). Serão apresentadas algumas informações sobre os três primeiros tipos de objetos, afim de melhorar o entendimento dos códigos implementados em laboratório. Os objetos do tipo files não serão detalhados. Todos os três tipo de objetos que serão discutidos apresentam uma similaridade muito grande no modo como são declaradas. Abaixo apresentamos a forma como são declaradas sendo que as palavras reservadas do VHDL estão em negrito: Objeto de dados Declaração signal signal signal_name : signal_type := initial_value; variable variable variable_name : variable_type := initial_value; constant constant constant_name : constant_type := initial_value; Em exemplos anteriores, essa forma de declaração já havia sido apresentada. É possível notar que todos os objetos acima listados podem começar com um valor inicial, e não somente a constante. Abaixo, alguns exemplos de declaração com alguns tipos que serão vistos mais adiante. Objeto de dados Declaração signal signal sig_var1 : std_logic := '0'; signal tmp_bus : std_logic_vector(3 downto 0) := “0011”; variable variable my_var1,my_var2 : std_logic; variable index_a : integer_range(0 to 255) := 0; constant constant sel_var : std_logic_vector(2 downto 0) := “001”; constant max_cnt : integer := 12; - Variáveis e o operador de atribuição Variáveis e sinais possuem características muito similares, mas variáveis não são tão funcionais uma vez que só podem ser declaradas dentro de funções, processos e procedimentos. Um dos erros mais comuns é usar uma variável fora de um processo. O operador de atribuição de sinal “<=” é usado para transferir o valor de um sinal para outro quando se está trabalhando com objetos de dados do tipo signal. Quando se trata de variáveis, o operador de atribuição é “:=”. - Sinais x Variáveis Sinais e variáveis podem ser um pouco confusos porque são muito parecidos. Sinais, de forma geral, podem ser comparados com os “fios”ou algum outro tipo de conexão física no design de um circuito. Sinais também podem ser usados para ligar módulos dentro de uma interface VHDL, inclusive com os valores de entrada e saída. Uma diferença importante entre sinais e variáveis é que um valor pode ser previamente atribuído a um sinal; o que não ocorre com a variável. Variáveis deveriam ser usadas principalmente como contadores em iterações ou como valores temporários em um algoritmo que realize certos cálculos. Em outras situações elas deveriam ser evitadas pois elas não possuem a mesma capacidade de ligar módulos em uma interface, já que não possuem uma memória. Tipos de dados - Tipos de dados mais comumente usados Existem inúmerostipos de dados em VHDL. Na tabela abaixo encontram-se os mais comuns, sendo que alguns já foram mencionados anteriormente ou já foram usados em alguns exemplos neste tutorial. Os tipos integer e std_logic serão um pouco mais detalhados em seguida. Tipo Exemplo std_logic signal my_sig : std_logic; std_logic_vector signal busA : std_logic_vector(3 downto 0); Boolean Variable my_test : boolean := false; integer signal iter_cnt : integer := 0; - Tipos integer O uso dos tipos integer é geralmente em códigos de descrição de circuitos digitais mais complexos. A faixa de valores que esse tipo pode ter vai de [-231 a +231]. Outros tipos similares ao tipo integer são os tipos natural e positive, sendo que estes apresentam uma faixa de valores diferente. Esses valores podem ser variados pelo usuário, fazendo-se uso dos comandos type, range and to (ou downto). Apesar de não serem muito úteis nesse curso, alguns exemplos são mostrados abaixo: - Tipos std_logic O tipo bit é um dos tipos mais comuns em VHDL, mas muito limitado. Ele pode conter apenas os valores de '0' e '1'. Por isso, o tipo std_logic é mais usado do que o tipo bit, devido à sua versatilidade e à maior possibilidade de valores. O tipo std_logic é definido como um tipo enumerado e dois dos possíveis valores são, obviamente, '0' ou '1'. A definição completa é mostrada abaixo. Essa definição, porém, lista Figura 39: Exemplos de variação de algumas variáveis do tipo integer std_ulogic, ao invés de std_logic. Isso se deve pois o tipo std_logic é uma versão “resolvida” do tipo std_ulogic. A definição de “resolução” vai além do esse tutorial propõe. Os valores mais comuns, dos listados acima, são '0', '1', 'Z' e '-'. O valor de alta impedância 'Z' é particularmente interessante. O 'Z' é geralmente utilizado quando se lida com estruturas “bus”. Isso permite que um sinal ou conjunto de sinais passe a ter a possibilidade de ser conduzido por múltiplas fontes, sem a necessidade de gerar funções de “resolução”. Quando um sinal é "dirigido" ao seu estado de alta impedância, o sinal não é dirigido a partir dessa fonte e é efetivamente removido do circuito. E, finalmente, uma vez que os caracteres utilizados no tipo std_ulogic fazem parte da definição, devem ser usados como listados. Uso de letras minúsculas irá gerar um erro. Figura 40: Definição do tipo std_logic. Uso de VHDL para circuitos sequenciais Esta seção irá mostrar como descrever alguns circuitos sequenciais usando a linguagem. Será dada uma atenção maior ao modelo comportamental da linguagem, aplicado a alguns flip-flops D. Será dada uma atenção maior aos conceitos básicos desses flip-flops, o que irá ajudar na modelagem das máquinas de estado. Elementos simples de armazenamento usando VHDL O flip-flop consiste de um dispositivo com uma lógica enable, um sinal com um certo clock. O primeiro flip-flop com o qual iremos trabalhar é o flip-flop tipo D mostrado abaixo: O código que descreve o comportamento do flip-flop acima pode ser dado por: Algumas observações a respeito do código acima: • Como nós usamos um modelo comportamental, a arquitetura é composta principalmente de uma instrução process. As instruções dentro de process são executadas sequencialmente. Sempre que Figura 41: Flip-flop D. Figura 42: Definição do flip-flop D. uma mudança é detectada em qualquer um dos sinais na lista de sensitividade de process, essa instrução é executada. Neste caso, as instruções dentro de process são executadas cada vez que há mudança no nível lógico de D ou do Clock. • A contrução rising_edge() é usada na instrução if para indicar que as mudanças na saída só ocorrem na subida do Clock. rising_edge é uma função já definida em uma das bibliotecas já incluídas na linguagem. • Para melhor entendimento, o processo foi nomeado: process dff. A maior utilidade de um flip-flop é a sua função de “memória”. Pois, esse dispositivo mantém o bit anterior na saída caso não haja alteração na entrada. Isso está implícito no código quando não coloca- se nenhuma condição ao if. Ou seja, a saída Q só será alterada caso a condição do if seja satisfeita. O próximo exemplo é um outro flip-flop, mas agora com um enable., ou seja, uma entrada que liga/desliga o dispositivo: O código que descreve o comportamento do flip-flop acima pode ser dado por: Figura 43: Flip-flop com Set. Algumas observações a respeito do código acima: • Pelo código acima, podemos perceber que o dispositivo só funciona caso a entrada S seja '1'. Caso contrário, a saída será sempre '0'. O exemplo agora é um outro flip-flop tipo D, mas com um botão de Reset (R). O esquema do dispositivo pode ser visto abaixo: O código que descreve o comportamento do flip-flop acima pode ser dado por: Figura 44: Definição do flip-flop D com Set. Figura 45: Flip-flop D com Reset. Algumas observações a respeito do código acima: • Pelo código acima, podemos perceber que o Reset, diferentemente do Set, não depende do Clock e tem prioridade sobre ele. O flip-flop só irá funcionar se o R for '1'. Figura 46: Definição do flip-flop D com Reset. Design de máquinas de estado com VHDL Máquinas de estado (ME's) são geralmente usadas como controladores em circuitos digitais. Para projetar ME's em hardware, o primeiro passo (e facilitador do processo) é aprender a modela-las usando VHDL. Essa modelagem é uma continuidade das técnicas usadas na seção anterior, sobre circuitos sequenciais. O diagrama de blocos da figura 47 traz um modelo padrão de uma máquina de estado de Moore. O bloco Next State Decoder é um bloco de lógica combinatória que usa as entradas externas atuais e o estado atual da ME para decidir o próximo estado da máquina. Em outras palavras, as entradas desse bloco são decodificadas e produzem uma saída correspondente ao próximo estado da ME. O próximo estado se torna o estado atual quando o clock na entrada do bloco State Registers torna-se ativo. O bloco State Registers contém elementos de armazenamento que armazenam o estado atual da máquina. As entradas do bloco Output Decoder são usadas para gerar as saídas externas desejadas. Esse bloco é constituido de circuitos combinatórios, que irão gerar as saídas desejadas. Como as saídas externas dependem apenas do estado atual da máquina, ela é classificada como máquina de Moore. A versatilidade da linguagem irá nos permitir descrever máquinas de estado de forma mais simples do que quando o mesmo é feito em papel. Há muitas formas de descrever uma máquina de estado em VHDL. A abordagem utilizada nesta apostila é parcialmente descrita pelo diagrama de blocos da figura 48. Figura 47: Diagrama de blocos de uma máquina de estado de Moore. Figura 48: Modelo para implementação em VHDL das máquinas de estado. A abordagem que usaremos divide a máquina de estado em dois processos em VHDL. O primeiro processo, o processo síncrono (synchronous process), engloba tudo que se refere a clock e outros controles associados a elementos de armazenamento. O outro processo, o processo combinatório (combinatorial process), engloba todos os elementos dos blocos Next State Decoder e Output Decoder, mostrados no diagrama da figura 40. Ou seja, esse último processo contém todos os circuitos combinatórios da máquina. As entradas denominadas parallel inputs são entradas que agem em paralelo aos elementos armazenadores. Essas entradas incluem enables, resets, sets, clear, etc.. As entradas denominadas state transition inputs incluem entradas externas que controlam as transições de estado. EXEMPLO 13: Escrever o código VHDL que descreve a máquina de estado abaixo. Solução: Este problema representa uma implementação básica de uma máquina de estado.
Compartilhar