Este artigo apresenta o que são exatamente os circuitos integrados de lógica programável e como eles funcionam, com uma abordagem técnica e prática desses componentes.

Nota: Artigo publicado na Revista Saber Eletrônica 457 de 2011.

Todo projetista está acostumado a utilizar desenhos esquemáticos para representar circuitos eletrônicos. Naturalmente este é um modo fácil de também representar circuitos lógicos. Em poucos instantes, analisando um esquema é possível entender o funcionamento do circuito e, desde que o esquema seja bem-feito, qualquer outro projetista poderá trabalhar sobre o mesmo e não apenas o projetista original.

Contudo, quando o projeto é complexo, o número de folhas de esquemas aumenta, o tamanho das páginas aumenta e o funcionamento já não fica tão claro para qualquer um. Alterações de projeto exigem redesenhar uma ou mais páginas e se a alteração for significativa pode exigir a subdivisão de um circuito em páginas adicionais.

Esquemas são uma boa forma de representar circuitos mas, em muitos casos, um outro meio pode ser mais eficaz e poupar algum tempo no processo de desenvolvimento. Este outro meio é o uso de uma linguagem textual para descrição do circuito, HDL (Hardware Description Language), onde o comportamento do circuito é o que mais importa e não exatamente onde cada sinal é conectado.

Uma descrição comportamental facilita a vida do projetista pois ele não precisa se preocupar com detalhes puramente matemáticos como os mapas de Karnaugh para redução de lógica ou mesmo a construção de decodificadores para selecionar cada passo de uma máquina de estados. Por exemplo, um contador síncrono pode ser descrito como:

 

if rising_edge(clock) then saida<=saida+1; end if;.

 

Note-se que o uso de lógica síncrona é natural em HDL (de fato, para fazer um contador assíncrono tipo ripple-carry dá um bocado de trabalho. Leia mais no quadro Lógica Síncrona).

Se alguém pensar em fazer o mesmo usando esquemas, precisa lembrar-se que cada estágio do contador possui um porta AND com tantas entradas quanto o número de estágios anteriores. E quantos estágios são necessários? Se for um contador de 8 bits serão 8 estágios. Mas, e se o projeto exigir 2 bits a mais por que ocorreu uma alteração nas especificações? No esquema será preciso desenhar mais dois estágios (se couberem na folha) ou criar uma nova folha para desenhar o contador completo e depois desenhar as referências de interconexão das páginas, etc.

Como isso é feito em HDL? Como pode ser visto no exemplo citado, a largura em bits do contador não entra na equação. Ela é definida em uma parte anterior onde o sinal é descrito como:

signal saida : std_logic_vec-tor (7 downto 0);.

Esta linha diz que o sinal saida tem 8 bits de largura. Se o projetista quiser mudar para 10 bits basta alterar esta linha para

signal saida : std_logic_vector (9 downto 0);.

 

Simples, não?

Linguagens HDLs são aplicáveis no projeto de circuitos integrados (ASICs —Application Specific Integrated Circuits) e no uso em circuitos de lógica programável (FPGAs e CPLDs). Naturalmente, existem muitas linguagens de descrição de hardware além do VHDL, como VERILOG, ABEL e AHDL. Como VHDL tem sido mais divulgada e aplicada em empresas e universidades, esta foi escolhida para esta série de artigos que são, de fato, um minicurso na revista Saber Eletrônica.

A primeira versão disponibilizada da linguagem VHDL (VHSIC Hardware Description Language) surgiu em 1987 e foi originalmente encomendada pelas Forças Armadas americanas. Sistemas complexos e produzidos por múltiplas empresas exigiam uma linguagem padronizada para modelagem e simulação. Por isso muitas das instruções não são sintetizáveis, ou seja, não podem ser convertidas em um equivalente em circuitos eletrônicos e servem, por exemplo, para permitir a simulação de circuitos. Em 1993 surgiu uma nova revisão que é empregada até hoje, o VHDL-93.

 

 

Fundamentos

Antes de tudo é importante saber como um texto que descreve o comportamento de um circuito pode ser convertido no circuito eletrônico propriamente dito. Existem duas etapas principais neste processo: síntese e implementação.

A síntese é a tradução da linguagem HDL em um outro texto descritivo que emprega referências de interconexão entre elementos básicos de lógica digital como portas lógicas, registradores e pinos de entrada e saída.

A implementação emprega o resultado da síntese e outras referências específicas do dispositivo a ser utilizado (conforme um determinado modelo de FPGA ou de CPLD) para calcular como este dispositivo deverá ser programado para obter-se o circuito esperado.

A síntese é equivalente à compilação em software: traduz-se de uma linguagem como C para código assembly. A implementação é equivalente a traduzir a descrição assembly em código objeto para um processador.

Apesar do uso da linguagem VHDL estar relacionado, de forma direta, apenas com a síntese, durante o desenvolvimento de um projeto deve-se levar em conta os efeitos na implementação conforme o dispositivo utilizado. Uma função lógica com muitas entradas poderá ser implementada mais eficientemente em um CPLD do que em um FPGA. Contudo, se esta mesma função for descrita de maneira a ser um agrupamento de pequenas funções o resultado poderá ser mais adequado para um FPGA do que para um CPLD. Isso ocorre porque os CPLDs tem poucos geradores de função (até poucas centenas), porém cada um com muitas entradas (alguns com mais de 40), ao passo que o FPGA tem muitos geradores de funções (até muitos milhares) com poucas entradas (tipicamente entre 4 e 6).

Outro aspecto é que a descrição em VHDL pode ser ou muito específica ou, por outro lado, mais genérica. Ela é específica quando indica claramente como o circuito deverá ser montado e utilizando recursos particulares do dispositivo. Por exemplo, indicando ouso de um componente chamado CB8CE, que é natural dos componentes Xilinx, o projetista estaria empregando um contador binário de 8 bits. Isso também é chamado de instanciação (ou ocorrência forçada). Já uma descrição genérica deixaria a ferramenta de síntese calcular qual circuito representaria uma função declarada. Neste caso a declaração valor <= valor + 1; produz o mesmo resultado final, ou seja, um contador binário. Este cálculo feito durante a síntese é chamado de inferência (ou dedução).

Naturalmente a inferência é mais simples de usar e de se entender quando o projeto de outra pessoa é lido. Pode existir, contudo, uma grande diferença no resultado final: o componente CB8CE emprega recursos otimizados dos dispositivos Xilinx e garantidamente oferecerá o melhor desempenho em espaço ocupado e velocidade, enquanto a inferência poderá usar recursos genéricos dos dispositivos e ser menos rápida ou ocupar mais espaço. Todavia, ao empregar o CB8CE o projetista está determinando que o contador terá 8 bits de largura. O que ocorre se ele precisar de 11 bits? Seguindo o raciocínio do uso de componentes ele deveria incluir mais um destes componentes e possivelmente ocupar mais espaço com os 5 bits restantes e desnecessários. Usando a inferência somente o número de bits usado será implementado.

As ferramentas de síntese são capazes de um bom nível de otimização que resulta no emprego dos recursos específicos dos fabricantes de lógica programável. Por isso, preferencialmente, o projetista deve descrever o hardware usando inferência, tornando o projeto mais claro e mais fácil de ser desenvolvido. Mais à frente veremos este assunto novamente.

Importante: os sinais e comandos (palavras reservadas) em VHDL podem ser escritos tanto em minúsculas como em maiúsculas ao longo de todo o texto.

 

 

VHDL

VHDL é uma linguagem típica: simples em sua funcionalidade básica e complexa quando o projetista aproveita todos os seus recursos.

Cada projeto em VHDL é sempre distribuído em um ou mais arquivos de texto, geralmente coma extensão .vhd. Cada arquivo contém sempre a descrição funcional completa de um elemento do projeto ou mesmo do projeto inteiro. De fato, o arquivo VHDL de um elemento é um projeto em si sendo usado como um componente. O arquivo principal, topo do projeto, que contém referências de interconexão com os outros arquivos pode ser imaginado como a descrição de um diagrama de blocos. Geralmente, funções complexas e já verificadas são mantidas como um projeto individual (análogo a uma sub-rotina bem depurada quando programamos um computador). Assim o projetista não precisa reescrever ou copiar trechos extensos em VHDL para um único arquivo, mantendo a legibilidade e repetibilidade dos resultados. A estrutura de um arquivo VHDL contém sempre a declaração das bibliotecas utilizadas e duas seções básicas e obrigatórias: ENTITY e ARCHITECTURE, como pode ser visto no exemplo do quadro 1. No texto deste quadro todas as palavras e caracteres que são reservadas do VHDL ou tipicamente obrigatórias em qualquer projeto (como as bibliotecas) estão marcados em negrito. As poucas partes que não estão em negrito são aquelas dependentes e produzidas pelo próprio projetista.

 


 

 

 

A seção ENTITY (entidade) serve como um cabeçalho onde são indicados todos os sinais de entrada e de saída do projeto ou elemento a ser descrito. No exemplo, a palavra main é o nome da entidade e normalmente também será o nome do arquivo VHDL. Os sinais no "Port" da entidade possuem um descritor indicando o sentido do sinal: In, Out e Inout e o tipo do sinal. Os tipos mais usados são:

Bit, Std_logic, Bit_vector e Std_logic_vector.

 

Na prática utiliza-se preferencialmente std_logic ou std_logic vector. A razão disto será vista mais adiante.

A seção ARCHITECTURE (arquitetura) é onde o projetista descreve o funcionamento da entidade. A primeira linha da arquitetura deve indicar duas coisas: o tipo de arquitetura (no exemplo: RTL) e o nome da entidade à qual a arquitetura está relacionada (no exemplo: main). O tipo da arquitetura pode, de fato, ser qualquer nome escolhido pelo projetista. Programas que geram a estrutura de um arquivo VHDL automaticamente (como o ISE da Xilinx) costumam colocar o tipo como BEHAVIO-RAL. O VHDL não examina esta informação. Você pode usar quaisquer palavras tais como: COMPORTAMENTO, DESCRICAO, MINHA_ARQUITETURA, ABC123, etc. Em suma, o tipo da arquitetura serve mais para a pessoa que vai ler o projeto ter uma ideia da finalidade e a palavra usada não é importante para o VHDL.

A arquitetura tem um início (begin) e um fim (end). Entre o begin e a primeira linha (architecture) são declarados os sinais internos que serão usados neste projeto ou elemento de projeto. Os sinais devem ser entendidos como fios que vão interligar os componentes e portas lógicas do circuito. No exemplo do quadro 1 o único sinal utilizado que já não foi declarado no "port" da entidade é um barramento de 8 bits que é a saída de um contador de 8 bits chamado DIV. Entre o begin e o end deve ser incluído um texto descrevendo a lógica do circuito. No caso do exemplo esta descrição possui um PROCESS (processo) e uma linha de lógica combinacional muito simples que diz que o sinal LED deve ser fornecido a partir do bit 7 do contador DIV. Equações lógicas podem ser facilmente implementadas e todos os operadores (funções) comumente usados estão à disposição. Uma equação deste tipo seria, por exemplo:

 

clk_x <= clock and (not div(0)) and div(3);.

 

Neste caso um sinal de saída chamado "clk_x" seria o resultado de uma porta AND de três entradas com os sinais clock, bit O de DIV invertido e o bit 3 de DIV. Já um decodificador de endereço poderia ser descrito como chipselect <= '0' when (addr = "0011000000000001" and wr = 'O' and ale = '1') else '1'•,. Note-se que o sinal "chipselect" vai ficar em nível lógico zero quando o barramento de endereço for 0x3001 e ao mesmo tempo tendo os sinais "wr" em nível zero e "ale" em um. Caso contrário, o sinal ficará em 1. Esta operação lógica utilizou um recurso do VHDL que é um teste condicional. Para fazer isso em esquemático levaria um pouco mais de tempo...

 

Tipos de sinais

Existem diversos tipos pré-definidos para serem atribuídos aos sinais. Eles são divididos em dois grupos: ESCALARES e COMPOSTOS. O quadro 2 mostra os tipos escalares.

 


 

 

O tipo BIT somente pode ter dois estados: 1 e O. Por esta razão ele não poderia ser associado a um sinal que possa ficar em alta impedância (tri-state). O tipo BOOLEAN serve para verificação em testes condicionais. O tipo INTEGER é útil para equações matemáticas e contadores, mas não é prático quando se precisa extrair um ou mais bits para serem usados em outras equações. Do ponto de vista do hardware um sinal INTEGER é, de fato, um barramento de n bits, sendo o número de bits correspondente ao necessário para expressar toda a gama de valores que o sinal pode ter (indicado no range).

O REAL é um tipo apenas aplicável em simulações e não poderá ser sintetizado, isto é, a ferramenta de síntese não saberá como traduzir um sinal REAL em hardware. Note-se que operações aritméticas com números reais somente podem ser feitas através da implementação de um hardware correspondente, para o processamento em ponto flutuante. Tal implementação fica por conta do projetista.

O tipo STD_LOGIC é o mais usado. Ele permite que o sinal possa assumir até 9 estados diferentes e isso permite fazer verificações funcionais mais completas além de tornar viável a implementação de sinais em tri-state (Z). O significado de cada um dos estados possíveis é: U: Não inicializado; X: Forçado "Unknown"; 0 : Forçado Zero; 1 : Forçado Um; Z : Alta impedância; W : "Unknown" fraco; L : Zero fraco; H : Um fraco; - : Don't care. A maioria dos estados é somente útil em projetos mais complexos. Os estados '0' ‘1’, 'Z' e são mais usados.

O tipo MY TYPE (apenas no exemplo pois poderia ter qualquer nome) é também chamado de tipo enumerado, ou seja, onde cada estado possível recebe um nome escolhido pelo projetista. Durante o processo de síntese os sinais de tipo enumerado são montados como barramentos com tantos bits quantos necessários para, através de números binários ou one-hot (barramento de n bits onde apenas um bit de cada vez pode ficar em nível 1), representar todos os estados declarados. Observar que, na representação binária poderão ser possíveis mais estados do que os declarados (com 3 bits existem 8 combinações e quando apenas 5 estados são declarados ainda sobram outros três). Durante a síntese estes estados adicionais são tratados internamente como nomes ou códigos atribuídos aleatoriamente. O projetista deve saber disso pois o hardware final, não sabendo como atuar durante a presença destes estados, pode funcionar incorretamente.

Os tipos enumerados são muito usados para definir os possíveis estados de uma máquina de estados finitos (FSM ou Finite State Machine). Obviamente o nome de cada estado ajuda a entender a sua finalidade e é mais amigável que séries de valores binários.

 


| Clique na imagem para ampliar |

 

Os tipos compostos aparecem no quadro 3. Os VECTORs são pré-definidos e não exigem uma declaração de tipo (type). Um std_logic vector ou bit vector representa um barramento de tantos bits quanto os declarados na expressão X downto Y, onde X é o número do bit mais a esquerda do barramento e Y o mais à direita. Embora Y possa ter qualquer valor (sempre menor que X) recomenda-se usar 0 (zero) pois algumas ferramentas de síntese causam erros quando Y é maior que zero. A declaração de largura pode ser tanto DOWNTO como TO. O VHDL não impõe restrições e a ordem (do bit mais significativo para o bit menos significativo) sempre é feita da esquerda para a direita.

Recomenda-se usar consistentemente apenas uma orientação, pois do contrário uma conexão entre dois sinais pode significar a inversão de um barramento (veja o quadro 4). Normalmente, usa-se a ordem DOWN-TO por ser a mesma da representação em esquemas e a mais natural aos projetistas de circuitos lógicos.

 


 

 

A inicialização, feita na declaração dos sinais (como indicado no exemplo do quadro 3a — :="0100"), é útil para a simulação e não é sintetizável. Quando um sinal não é inicializado, os simuladores não conseguem calcular os resultados das lógicas dependentes deste sinal. No caso do ModelSIM, um ou mais sinais e outras lógicas dependentes deste de um sinal não inicializado aparecem na janela de análise como uma linha vermelha.

Um tipo muito interessante e que desperta a imaginação dos projetistas é o RECORD. Com ele, monta-se um barramento complexo onde cada parte pode ser tratada individualmente. Depois este barramento pode ser carregado, digamos, em um registrador de deslocamento (shift register) e a saída resultante usada numa interface serial de comunicação. Outro exemplo é o uso do record para descrever microinstruções de um processador. Para acessar as partes individuais de um record basta usar:

 

Exemplo:


 

 

Finalmente, os agregados são na verdade a forma de associar partes individuais de outros sinais e constantes de forma a compor um sinal diferente. O uso da palavra reservada "others" indica que todas as demais posições não explicitamente indicadas devem assumir o estado apontado. Os agregados somente podem ser compostos de elementos associados de forma posicional ou nomeada.

Os dois modos não podem ser usados juntos na mesma atribuição. Vale dizer neste momento que a forma posicional serve para indicar a posição exata de um bit de um barramento. Como no exemplo em 3c, segunda linha, onde o sinal c vai ligado no bit 7 de data, o sinal aux(1) vai no bit 6 e assim por diante.

Já a forma nomeada indica em que bit do barramento o sinal em questão será ligado. Veja a terceira linha do exemplo onde o sinal data(2) é ligado ao bit 14 de word. Lembre-se disso, pois estas formas posicional e nomeada são empregadas em outras referências no VHDL. Adicionalmente ao exemplo uma expressão data <= (a,b,c,d,aux); não funcionaria porque a ferramenta de síntese percebe que os tipos de data e aux são diferentes.

Também a expressão zz <= aux; não funcionaria pelo mesmo motivo, contudo isso pode ser resolvido, neste caso, empregando-se uma função de conversão de tipos: zz <= to_stdlogicvector (aux);

Ainda sobre sinais, na representação de valores, conforme o tipo usa-se:

 


 

 

 

 

NO YOUTUBE


NOSSO PODCAST