Nesse tutorial a gente vai falar sobre shift registers, o que são? Para que serve? E ainda montar um projeto com um resultado muito interessante, o resultado vocês podem ver no vídeo abaixo.
- O que é um shift registers? Shift registers, como o próprio nome propõe, é um circuito digital que possui dados que são armazenados e shifted, ou seja, "substituidos" como se saltassem a cada clock, juntos. Eu recomendo ao leitor dar uma olhada na página do wikipedia sobre shift registers e conferir todas as imagens e entedê-las. Esse tipo de circuito é bem útil e usado para os vários propósitos, e em projetos maiores ele pode ser conveniente. - O nosso projeto Como de costume, abra o Quartus (aqui estamos utilizando a v. 13.1) e crie um novo projeto.
Figura 1: Criando um novo projeto.
Qualquer maior dificuldades nesse passo, consulte o tutorial 01. Em File > New > VHDL File, crie um arquivo VHDL em branco.
Figura 2: Criando um novo arquivo VHDL.
Agora é só descrever o nosso hardware.
-- declara ao compilador a biblioteca usada
LIBRARY ieee;
USE ieee.std_logic_1164.all;
USE ieee.numeric_std.all;
-- entidade shift_register
ENTITY shift_register IS
-- listagem de entradas e saidas
PORT(
LEDG:OUT STD_LOGIC_VECTOR (9 downto 0);
SW:INSTD_LOGIC_VECTOR (9 downto 0);
CLOCK_50:INSTD_LOGIC
);
END shift_register;
-- descrever o comportamento do circuito
ARCHITECTURE behavioral OF shift_register IS
-- contador para o tempo (aprx. um quarto de segundo)
SIGNAL temporal: INTEGER RANGE 0 TO 12500000 := 0;
-- inicialmente nenhum LED é ligado.
SIGNAL vetor_temp:STD_LOGIC_VECTOR (9 downto 0) := (OTHERS => '0');
-- transfere valor da chave compassadamente para o vetor_temp(0)
vetor_temp(0) <= SW(9-i);
-- reseta o indice quando chegar no limite
IF (i = 9) THEN
i <= 0;
ELSE
i <= i + 1;
END IF;
ELSE
-- senao, continua contando (tempo correndo...)
temporal <= temporal + 1;
END IF;
END IF;
END PROCESS;
END behavioral;
Antes de compilar, importamos (ou declaramos) os pinos da placa. Para isso, Assignments -> Import Assignments.
Figura 3: Declarando os pinos da placa.
E finalmente, Processing> Start Compilation.
Nota: qualquer dificuldade em algum desses passos, leia os tutoriais anteriores.
- Existem outras maneiras de fazê-lo? Claro que sim. Assim como existem várias maneiras de descrever um objeto, existem várias maneiras de descrever um shift register em VHDL. Talvez uma possível solução seria utilizando o for loop e executar a mudança dos valores dentro do loop... seria um ótimo exercício tentar uma outra aproximação para arranjar o shift register. É isso pessoal, e não esqueçam de ler a página do wikipédia :-) .
Olá pessoal, Mais um novo projeto com VHDL, nesse post iremos criar um simples e pequeno relógio digital utilizando VHDL. Primeiramente, abra o Quartus II (a versão utilizada aqui é a 13.1)... em File > New > New Quartus II Project crie um novo projeto (caso tenha alguma dúvida de como fazer, leia o primeiro tutorial)
Figura 1: Criando um novo projeto para o relógio digital.
Logo após, crie dois novos documentos VHDLFile > New > VHDL File.
Dai você pergunta, por que dois? Bom, nesse tutorial a gente vai criar uma função que irá converter números inteiros em um STD_LOGIC_VECTOR com valores correspondente a sua imagem no display de 7 segmentos, e para isso, precisaremos de um novo arquivo VHDL.
Antes de começar a programar, vamos entender como funciona o display de 7 segmentos... primeiramente, vale lembrar que cada display é composto por 7 LEDs (light emitting diode). No nosso caso, o display é conectado em ânodo comum, ou seja, para ligar o LED devemos atribuir um sinal de tensão baixo (zero) e para desligar devemos atribuir um sinal alto (um). Caso o display seja ligado em cátodo comum, basta inverter a lógica.
Figura 2: Esquemático do display de 7 segmentos.
Por exemplo, para "escrever" o número 5, escreveriamos um vetor com o seguinte valor:
| g | f | e | d | c | b | a |
| 0 |0 | 1 | 0 | 0 | 1 | 0 |
assim, "b" e "e" estariam apagados e o restante ligado.
Ou o número 9 (apenas "e" desligado):
| g | f | e | d | c | b | a |
| 0 |0 | 1 | 0 | 0 | 0 | 0 |
Para facilitar e diminuir o nosso código criaremos uma função que irá converter os números em seus respectivos códigos para o display. Chamaremos essa função de "int2seg". int2seg deve ser criada em um arquivo contendo uma estrutura determinada, necessitaremos criar um PACKAGE. Com os PACKAGES você pode compartilhar soluções padrões para certos problemas e compartilhar entre diferentes estruturas. Para criar um PACKAGE precisamos:
- inserir as bibliotecas;
- declarar o package e o seu nome;
- criar o package body;
- criar suas funções e/ou procedures;
- inserir seu package em seu código principal.
a estrutura de um package
PACKAGE nome_do_package IS
-- DECLARA AS FUNÇÕES, CONSTANTES, SINAIS, PROCEDURES
END nome_do_package;
PACKAGE BODY nome_do_package IS
-- CRIA SUAS DECLARAÇÕES
END nome_do_package ;
Para criar a função criamos um package com nome PACOTE e dentro dele declaramos nossa função.
FUNCTION int2seg(A: INTEGER) RETURN STD_LOGIC_VECTOR;
Essa função tem como argumento um número inteiro (armazenado como A) e retorna um STD_LOGIC_VECTOR (o código correspondendo para o display de 7 segmentos)
No package body a mesma declaração é utilizada, uma variável chamada result é criada, ela armazenará o valor correto do display e será retornada com o comando RETURN
FUNCTION int2seg(A: INTEGER) RETURN STD_LOGIC_VECTOR IS VARIABLE result: STD_LOGIC_VECTOR(6 downto 0); BEGIN CASE A IS WHEN 0 => result := "1000000"; . . . END CASE; RETURN result; END int2seg; O comando USE insere o package no nosso arquivo com a nossa função. USE work.PACOTE.all; -- INSERE O PACKAGE
Agora que já podemos escrever no display, precisamos fazer "o tempo correr". Isso é feito por meio do clock. A placa DE0 possui um clock interno de 50Mhz, isso significa temos um período de 20ns de duração. Ou seja, se pudermos contar de 0 até 49999999 contaremos 50000000, se cada contagem demora 20ns, contaremos um segundo. Teoricamente isso é correto, porém na prática, existe um pequeno erro (que não interfere no resultado). A contagem será feita dentro do bloco PROCESS. Com process somos capazes de utilizar operações sequenciais como if-else, case... dentro do bloco process tudo é executado sequencialmente. Process possui uma "lista de sensibilidade" que irá disparar os eventos dentro dele. No nosso relógio, o process é executado observando o CLOCK_50, que é o clock do FPGA. PROCESS(CLOCK_50)
LIBRARY ieee; USE ieee.std_logic_1164.all; USE ieee.numeric_std.all; USE work.PACOTE.all; ENTITY relogio_digital IS GENERIC (SEG: INTEGER := 49999999); -- DEFINE O NUMERO DE CLOCKS PARA CONTAR UM SEGUNDO PORT( -- AQUI DEFIMOS AS ENTRADAS E SAIDAS HEX0_D :OUT STD_LOGIC_VECTOR (6 downto 0); HEX1_D :OUT STD_LOGIC_VECTOR (6 downto 0); HEX2_D :OUT STD_LOGIC_VECTOR (6 downto 0); HEX3_D :OUT STD_LOGIC_VECTOR (6 downto 0); CLOCK_50 :IN STD_LOGIC ); END relogio_digital; ARCHITECTURE behavioural OF relogio_digital IS SIGNAL TEMPORAL: INTEGER RANGE 0 TO SEG; SIGNAL SS0: INTEGER RANGE 0 TO 59 := 0; -- SEGUNDOS SIGNAL HH0: INTEGER RANGE 0 TO 9 := 0; --PRIMEIRO CARACTERE DA HORA SIGNAL HH1: INTEGER RANGE 0 TO 2 := 0; --SEGUNDO CARACTERE DA HORA SIGNAL MM0: INTEGER RANGE 0 TO 9 := 0; --PRIMEIRO CARACTERE DOS MINUTOS SIGNAL MM1: INTEGER RANGE 0 TO 5 := 0; --SEGUNDO CARACTERE DOS MINUTOS BEGIN -- ATRIBUI OS VALORES DOS SINAIS PARA O DISPLAY DE 7 SEGMENTOS HEX0_D <= int2seg(MM0); HEX1_D <= int2seg(MM1); HEX2_D <= int2seg(HH0); HEX3_D <= int2seg(HH1); PROCESS(CLOCK_50) BEGIN IF rising_edge(CLOCK_50) THEN -- NA BORDA DE SUBIDA -- SE O CONTADOR NAO CHEGAR NO FIM DE 1 SEG IF(TEMPORAL /= SEG) THEN TEMPORAL <= TEMPORAL + 1; -- INCREMENTA -- SENAO, RESETA O CONTADOR E ATRIBUI UM SEGUNDO ELSE TEMPORAL <= 0; SS0 <= SS0 + 1; IF(SS0 = 59) THEN -- 60 segundos eh 1 minuto SS0 <= 0; MM0 <= MM0 + 1; IF(MM0 = 9) THEN -- CASO PASSE 10 minutos MM0 <= 0; -- RESETA O DISPLAY 0 MM1 <= MM1 + 1; -- INCREMENTA O DISPLAY 1 -- 60 MINUTOS EH 1 HORA E ASSIM POR DIANTE... IF(MM1 = 5) THEN MM1 <= 0; HH0 <= HH0 + 1; IF(HH0 = 9) THEN HH0 <= 0; HH1 <= HH1 + 1; ELSIF (HH0 = 3 AND HH1 = 2) THEN
HH1 <= 0;
HH0 <= 0;
MM1 <= 0;
MM0 <= 0; END IF; END IF; END IF; END IF; END IF; END IF; END PROCESS; END behavioural; ________________________________________________________ relogio_digital_pk.vhdl LIBRARY ieee; USE ieee.std_logic_1164.all; USE ieee.numeric_std.all; PACKAGE PACOTE IS FUNCTION int2seg(A: INTEGER) RETURN STD_LOGIC_VECTOR; END PACOTE; PACKAGE BODY PACOTE IS FUNCTION int2seg(A: INTEGER) RETURN STD_LOGIC_VECTOR IS VARIABLE result: STD_LOGIC_VECTOR(6 downto 0); BEGIN CASE A IS WHEN 0 => result := "1000000"; WHEN 1 => result := "1111001"; WHEN 2 => result := "0100100"; WHEN 3 => result := "0110000"; WHEN 4 => result := "0011001"; WHEN 5 => result := "0010010"; WHEN 6 => result := "0000010"; WHEN 7 => result := "1011000"; WHEN 8 => result := "0000000"; WHEN 9 => result := "0010000"; WHEN OTHERS => result := (OTHERS=>'0'); END CASE; RETURN result; END int2seg; END PACOTE; ________________________________________________________
Salve os arquivos e compile Processing > Start Compilation.
Figura 3: Salvando os arquivos...
Se nenhum erro for reportado, é hora de declarar a pinagem. Isso pode ser feito da maneira antiga (ensinada no tutorial 1) ou importando a pinagem da placa DE0 utilizando os arquivos dentro do DVD fornecido pela terasic junto com sua placa.
Para importar os pinos: Assignments > Import Assignments e escolha o arquivo DE0_Default.qsf (esse arquivo deve ser facilmente encontrado pela internet, caso você não possua o DVD).
Figura 4: Importando todos os pinos da placa DE0.
Compile novamente.
- Salvando seu programa na memória EEPROM.
Para que seu programa possa rodar mesmo depois da placa ser desligada precisamos primeiro declarar o tipo de EEPROM. A DE0 possui um chip Altera EPCS4. Em Assignments > Device clique em Device and Pin Options. Na nova tela marque o checkbox "use configuration device" e escolha o chip EPCS4.
Figura 5: Assignments > Device.
Figura 6: Selecionando a EEPROM.
Compile seu programa novamente.
Na sua placa selecione o modo PROG.
Figura 7: Modo PROG.
Logo após, em Tools > Programmer, selecione a opção Mode: Active Serial Programming e procure pelo arquivo com a extensão .pof.
Figura 8: Programando a placa.
Clique em open e start. Ao completar o processo, troque o modo da placa para RUN e voilà.
Figura 9: Resultado final.
- Incrementos para o relógio...
É fato que o relógio começa no 00:00 e, a não ser que você ligue ele na meia-noite, ele vai funcionar mais como um "cronômetro". Então seria um bom desafio fazer com que o usuário pudesse corrigir o valor do relógio a partir dos botões e chaves da placa. Com esse mesmo código acima eu fiz um relógio capaz de editar a hora, confira no vídeo abaixo.
Nesse
primeiro tutorial de VHDL para FPGA estaremos ligando os LEDs (light emitting diode) a partir das
nossas chaves embutidas na placa. O conjunto de ferramentas utilizadas nesse
projeto:
-Altera DE0
Board;
-Quartus II
(versao 13.1);
-Windows 8;
Acredito
que você não enfretará nenhum problema caso esteja utilizando outra placa. O
procedimento é basicamente o mesmo, embora as especificações do seu projeto
(modelo do chip, modelo da EEPROM,
localização dos pinos) serão diferentes.
Para
iniciar, se este é o seu primeiro contato com a placa, você deve instalar os drivers USB-BLASTER para que a placa
seja reconhecida pelo sistema operacional, para isso, no gerenciador de
dispositivos do seu PC (pressione a tecla do windows + pause/break simultaneamente e clique em gerenciador de
dispositivos) procure pelo USB-BLASTER e
instale os drivers (são encontrados na pasta do Quartus II X:\Altera\13\quartus\drivers\usb-blaster).
Figura 1: O driver foi instalado com sucesso.
Nota: Qualquer maior dificuldade, o manual do usuário deve
ser consultado.
Ok, agora
que sua placa já é reconhecida é hora de começar o projeto. Abra o Quartus II…
Figura
2: Tela inicial do Quartus II.
Clique em New Project Wizard…
Nesse
momento você irá passar por uma série de janelas as quais definirão as características
da sua placa e do seu projeto. Leia a primeira tela e clique em next.
Figura 3: new project wizard.
Na segunda janela, escolha a pasta aonde você irá salvar seu projeto e nos outros dois campos escreva o nome do seu projeto, no
caso “chaves”.
Dica: Não escolha nomes e diretórios com espaços vazio (“ “) nos
seus projetos. Às vezes acontecem erros de compilação por causa disso... recomendo sempre utilizar caminhos curtos e sem espaços.
Figura 4: definindo a
pasta e o nome do seu projeto.
Clique em next,
nessa janela oprograma pergunta se
você já tem algum arquivo pronto e quer adicionar ao projeto, no nosso caso não
temos nenhum, então pulamos essa janela, next.
Figura 5: adicionar
arquivos ao projeto.
Nesse momento definimos a família do nosso FPGA. Para a DE0 Board escolhemos a
família Cyclone III e em devices selecionamos
EP3C16F484C6.
Nota: Caso esteja
utilizando uma placa diferente as especificações nessa etapa serão diferentes.
Observe qual a família e o chip da sua placa. Por exemplo, se estiver
utilizando a placa DE2 você deve escolher a família Cyclone II e EP2C35F672C6.
Figura 6:
Especificação da família.
Clicando em next,
o programa pergunta se estaremos utilizando alguma ferramenta extra para
utilização no projeto, como ferramentas CAD (EDA e CAD são basicamente
sinônimos).
Figura 7: Escolha de
ferramentas EDA.
A última tela exibe um relatório com todas as configurações
que você escolher, revise e clique em finish.
Depois de instalar os drivers e configurar o seu projeto, eh hora de programar. Clique em File >
New escolha a opção VHDL. Um editor de texto vai aparecer dentro da janela
do Quartus II. Agora clique em Project
> Add current file to project, com isso seu arquivo VHDL é adicionado ao
seu projeto.
LIBRARY ieee;
USE ieee.std_logic_1164.all;
ENTITY chaves IS
PORT (
LEDS
:OUT STD_LOGIC_VECTOR (9 downto 0);
SWITCHES
:IN STD_LOGIC_VECTOR (9 downto 0)
);
END chaves;
ARCHITECTURE arc_chaves OF chaves IS
BEGIN
LEDS
<= SWITCHES;
END arc_chaves;
Pronto, agora que o código ficou pronto, só nos resta
entendê-lo.
LIBRARY ieee;
USE ieee.std_logic_1164.all;
Esse trecho do código chama o pacote ieee que é padrão para o VHDL. Dentro dele estao contidos vários outros arquivos que
definem estruturas no nosso código, no exemplo de hoje chamamos apenas a
std_logic_1164 que define sinais do tipo STD_ULOGIC, STD_ULOGIC_VECTOR e o resolved ulogic: STD_LOGIC e
STD_LOGIC_VECTOR.
Figura 8: Código VHDL.
ENTITY chaves IS
PORT (
LEDS
:OUT STD_LOGIC_VECTOR (9 downto 0);
SWITCHES
:IN STD_LOGIC_VECTOR (9 downto 0)
);
END chaves;
Nesse momento, criamos a entidade chaves que contem dois STD_LOGIC_VECTOR que chamamos de LEDS e
SWITCHES, o primeiro sendo uma saida e o segundo uma entrada. E/S são definidas
dentro de PORT, observe que a última linha antes de ‘);’ não contem ‘;’
ARCHITECTURE __chaves OF chaves IS
BEGIN
LEDS
<= SWITCHES;
END __chaves;
Finalizando, criamos um architecture. Ele é usado para descrever o comportamento, fluxo de dados e estrutura de uma entity. No exemplo, ele é utilizado para associar os valores de 'SWITCHES' nos 'LEDS'. Note que o sinal de atribuição não é o sinal de igualdade, e sim a seta '<='.
Para compilar
nosso código clique no botão play
roxo ou vá em Processing > Start
compilation. Aguarde seu programa ser compilado, ao fim, o quartus irá exibir um relatório que pode ser acessado através do atalho ctrl + r.
Figura 9: Relatório de compilaçao.
Se seu
programa não foi compilado com sucesso, revise as configurações do seu projeto
(Assignment > Devices) e seu
código. Agora, com tudo funcionando, vamos associar os pinos do seu FPGA com as entradas e saidas do nosso
programa. Para isso, clique em Assignment
> Pin Planner, uma nova tela abrirá. Nessa tela, com auxilio do seu
manual de usuário você irá associar os pinos da sua placa com as variáveis de
entrada e saida criadas. Por exemplo, para a variável SWITCHES[0], em location digite J6 e o pino PIN_J6 será marcado.
Nota: PIN_J6 é associado ao
SWITCH_0 da placa DE0. Consulte o manual de usuário caso possua outra placa.
Figura 10: Tabela de pinos do switch da placa DE0.
Figura 11: Pin Planner.
Após associar
todos os pinos, compile seu programa novamente.
Para testar
seu programa na placa, ligue ela apertando no botao vermelho e com a chave no
modo RUN. Clique em Programmer > Tools em Hardware Setup selecione USB-BLASTER e
em mode selecione JTAG. Selecione o seu arquivo chaves.sof e clique em Start. Imediatamente o programa é
transferido para sua placa e já pode ser testado.
Figura
12: gravacao da placa utilizando JTAG.
Agora é só usar as chaves para ligar os leds e voalà.
Figura 13: Resultado final.
Em breve estarei postando e ensinando a vocês como gravar o seu programa utilizando o active serial programming para que seu programa seja aberto mesmo quando a placa é desligada.