Tutorial -‐ Xilinx ISE

Transcrição

Tutorial -‐ Xilinx ISE
Tutorial -­‐ Xilinx ISE
Universidade Federal do Rio de Janeiro
Escola Politécnica
Departamento de Eletrônica e Computação
Autores: Artur Lemos Ioav Lichtenstein
Thiago Lobo
Orientador:
Mário Vaz
Índice:
1. Introdução 2. Instalação 3. Criando um novo projeto 4. VHDL Básico 5. Simulando um projeto 6. Criando um DCM 7. Implementando um projeto na FPGA 8. Exemplos de Código 3
4
5
8
15
23
26
27
2
1. Introdução
O ISE (Integrated Software Environment) é um software criado pela Xilinx para síntese e análise de modelos HDL. O usuário poderá escrever, compilar, realizar análises temporais (utilizando diferentes estímulos como entrada) e/
ou configurar FPGA’s com o modelo criado.
Este tutorial visa fornecer ao usuário uma visão geral do software, mostrando, de forma simples, desde a ativação do programa até a compilação, visualização de diagramas RTL e configuração da FPGA Spartan 3AN com códigos VHDL.
Desenvolveremos o tutorial baseando-­‐nos num módulo divisor de freqüência, e forneceremos outros exemplos de código na última sessão.
3
2. Instalação
Para ativar o ISE, deve-­‐se baixar os arquivos no link https://www.del.ufrj.br/pastas-­‐das-­‐disciplinas/eel480 -­‐
sistemas-­‐digitais, garantir que estão na pasta ~/Downloads e executar a seguinte instrução no Terminal:
cd ; tar xvf ∼/Downloads/Xilinx.tar ; ./ise.sh
Feito isso, o ISE deverá estar ativado e para executá-­‐lo, deve-­‐se utilizar o seguinte comando, também no Terminal:
ise &
O que nos leva à tela principal do programa:
4
3. Criando um novo projeto
Na tela principal do ISE, devemos começar pelo menu “File > New Project” e a janela “New Project Wizard” será aberta.
Campos:
“Name” -­‐ nome do projeto
“Location” -­‐ localização onde o projeto será salvo*
“Description” -­‐ descrição do projeto
* -­‐ utilizaremos a pasta /tmp, pois eventualmente, o ISE pode gerar arquivos de projeto muito grandes, os quais excederiam o limite por usuário da rede. Para garantir que os arquivos sejam guardados, devemos utilizar o comando Archive, como mostraremos posteriormente.
5
Feito isso, clicar em “Next”. Agora, devemos escolher outros parâmetros de projeto:
“Evaluation Development Board” -­‐ Spartan 3AN Starter Kit
“Synthesis Tool” -­‐ XST
“Prefered Language” -­‐ VHDL
“VHDL Source Analysis Standard” -­‐ VHDL-­‐93
Após clicar, mais uma vez, em “Next”, uma janela que resume os parâmetros do projeto será exibida. Confira os dados e caso estejam corretos, clique em “Finish”.
Agora, temos um projeto (.xise) pronto, basta adicionar códigos-­‐fonte VHDL e poderemos trabalhar com os modelos. Para importar um código VHDL já escrito, devemos acessar o menu “Project > Add Copy of Source”, navegar até o arquivo e selecioná-­‐lo. Isso copiará o arquivo para o workspace do projeto e permitirá sua edição. Sempre que a edição dos arquivos estiver concluída, devemos utilizar o menu “Project > Archive”, para salvar o projeto na pasta do 6
usuário em formato .zip. Para utilizar esse projeto posteriormente, basta descompactar o .zip gerado dentro da pasta /tmp e abri-­‐lo na primeira tela do ISE (“Open Project”).
7
4. VHDL Básico
VHDL é uma linguagem de descrição de hardware que foi inicialmente utilizada para documentar componentes digitais e acabou sendo aproveitada para simulações/
implementações em FPGA desses mesmos componentes.
Um arquivo VHDL deve conter, no mínimo, uma entity e uma architecture. A entity consiste na descrição da interface do módulo como uma caixa preta: especificando apenas as entradas e saídas.
A architecture descreve o funcionamento do módulo, propriamente dito. Costumamos separar architectures em duas classes: comportamentais e estruturais. As comportamentais servem para definir como um módulo funcionará e as estruturais servem para definir como um módulo de alta hierarquia interliga diferentes módulos “menores” (o que não deixa de ser seu funcionamento).
A utilização de módulos já prontos dentro de uma architecture se assemelha à instanciação de um objeto em uma linguagem Orientada a Objetos: especifica-­‐se o tipo do objeto (sua Entity), escolhe-­‐se um nome e mapeia-­‐se suas entradas e saídas (semelhante ao construtor de classes em Java).
Dentro da architecture utilizamos sinais (signals) para intercomunicar módulos. É aplicável um paralelo com as variáveis de um programa em Java.
Comentários são feitos utilizando-­‐se um “-­‐-­‐”, por exemplo:
-­‐-­‐ Isso é um comentário e não será utilizado na síntese
8
Segue um exemplo básico: uma porta lógica AND:
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
-­‐-­‐ importando std_logic da biblioteca IEEE
library IEEE;
use IEEE.std_logic_1164.all;
-­‐-­‐ essa é a entidade (entity)
entity ANDGATE is
port ( I1 : in std_logic;
I2 : in std_logic;
O : out std_logic);
end entity ANDGATE;
-­‐-­‐ essa é a arquitetura comportamental (architecture)
architecture behavioral of ANDGATE is
begin
O <= I1 and I2;
end architecture behavioral;
Como podemos ver, a leitura do bloco “entity” permite imaginarmos o seguinte:
I1
I1
I2
ANDGATE
O
Depois, em architecture, o funcionamento do bloco é descrito: a saída O recebe a operação bit-­‐a-­‐bit AND entre as entradas I1 e I2. A operação AND foi incluída quando importamos a biblioteca IEEE.
9
Agora, podemos analisar o divisor de freqüência clk_div:
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use ieee.numeric_std.ALL;
entity clk_div is
generic (n : integer := 4);
port (clk_in : in std_logic; div : out std_logic;
div2 : out std_logic );
end clk_div;
architecture divide of clk_div is
signal cnt, cnt1 : integer := 0;
signal div_temp, div_temp2 : std_logic := '0';
begin
div <= div_temp;
div2 <= div_temp2;
process (clk_in) begin
if (clk_in'event and clk_in = '1') then
if cnt >= n then
div_temp <= not(div_temp);
cnt <= 1;
else div_temp <= div_temp;
cnt <= cnt + 1;
end if;
end if;
if (clk_in'event and clk_in = '0') then
if cnt1 >= n then
cnt1 <= 1; div_temp2 <= '1';
else div_temp2 <= '0';
cnt1 <= cnt1 + 1;
end if;
end if;
end process;
end divide;
Como podemos ver, o módulo clk_div é descrito por uma entrada (clk_in) e duas saídas (div1 e div2). O generic n : integer := 4 é equivalente a um #define em C. n será 10
substituído em todos os lugares que aparece no código por 4. Trata-­‐se de uma constante. Dentro da architecture, vemos um process, que é como uma função em C. Todo processo leva uma lista de sensibilidade, que no caso é (clk_in). Isso indica que sempre que clk_in for atualizado, o process será executado. Há, também, 4 signals: 2 contadores e 2 temporários que são mapeados às saídas do bloco, como indicam as linhas: 57. div <= div_temp;
58. div2 <= div_temp2;
Sabendo-­‐se disso, podemos realizar o resto da análise como em qualquer linguagem de programação. Perceberemos que div é simplesmente clk_in dividido por n e que div2 é um clock cujo período é o mesmo de div, porém seu duty-­‐cycle (tempo em nível alto) é de 25%, ao contrário dos 50% em clk_in e div.
11
Agora, podemos analisar o outro módulo, base_tempo, que utiliza o módulo clk_div para dividir a frequência do sinal de entrada clk. Vale ressaltar o fato de que um DCM (Digital Clock Manager foi necessário, voltaremos a falar sobre ele na sessão 5):
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use ieee.numeric_std.ALL;
library UNISIM;
use UNISIM.Vcomponents.ALL;
entity base_tempo is
generic (n: integer := 2);
port (clk : in std_logic;
clk_out : out std_logic;
clk_out_2 : out std_logic;
locked : out std_logic);
attribute LOC : string ;
attribute LOC of clk : signal is "E12";
attribute LOC of locked : signal is "W21";
attribute LOC of clk_out : signal is "Y22";
attribute LOC of clk_out_2 : signal is "Y18";
end entity;
architecture estrutural of base_tempo is
component clk_div
port (clk_in : in std_logic; div : out std_logic;
div2 : out std_logic);
end component;
COMPONENT dcm1
PORT(CLKIN_IN : IN std_logic;
CLKFX_OUT : OUT std_logic;
CLKIN_IBUFG_OUT : OUT std_logic;
CLK0_OUT : OUT std_logic;
LOCKED_OUT : OUT std_logic);
END COMPONENT;
signal clk_int : std_logic;
12
100.begin
101.clk_divider : clk_div port map 102. (clk_in => clk_int, 103. div => clk_out, 104. div2 => clk_out_2);
105. 106.Inst_dcm1 : dcm1 PORT MAP (
107. CLKIN_IN => clk,
108. CLKFX_OUT => clk_int,
109. CLKIN_IBUFG_OUT => open,
110. CLK0_OUT => open,
111. LOCKED_OUT => locked);
112.end estrutural;
Começaremos a análise pela entity: como esperado, há uma entrada clk e duas saídas clk_out e clk_out_2. Há uma saída extra chamada locked, que simplesmente diz se o DCM está ou não sincronizado.
As linhas 113-­‐117 associam sinais da entity a componentes da FPGA na qual o código será carregado. Exceto por “E12”, todos os sinais são mapeados a LEDS ou contatos, que podem ser identificados pelos mesmos nomes na placa física:
113.attribute LOC : string ;
114.attribute LOC of clk : signal is "E12";
115.attribute LOC of locked : signal is "W21";
116.attribute LOC of clk_out : signal is "Y22";
117.attribute LOC of clk_out_2 : signal is "Y18";
A achitecture, como de se esperar, é estrutural, pois base_tempo trata-­‐se de um módulo que unifica “pequenos” módulos (clk_div e dcm1). Para instanciarmos módulos, devemos antes fazer uma declaração do tipo “COMPONENT nome ... END COMPONENT;”. Isso foi feito para o clk_div e o dcm1.
Feito isso, podemos finalmente mapear as portas dos componentes (tal como inicializar um objeto em Java através de seu construtor) a sinais ou entradas/saídas da entidade.
13
No caso, utilizamos um sinal interno clk_int, que serve como saída do dcm1. Não podemos utilizar diretamente o clock da placa (50mHz), pois há problemas como capacitâncias parasitas que prejudicariam o funcionamento do circuito. De fato, resolvemos esse problema através do dcm1, que coloca uma frequência menor em clk_int para que essa seja utilizada como entrada para o clk_div. As saídas do clk_div são intuitivamente mapeadas às saídas clk_out e clk_out_2 da base_tempo.
Acreditamos que a esse ponto, o leitor já adquiriu certa noção de código VHDL e de seu funcionamento, sendo capaz de continuar por si só utilizando referências online para funções mais complexas.
14
5. Simulando um projeto
Completados os códigos VHDL, podemos realizar simulações e visualizar seu diagrama RTL através do ISE. Vale lembrar que podemos ver erros de sintaxe em tempo real na aba “Errors”, basta um “CTRL + s” para salvar o código e a janela será atualizada com os erros encontrados:
Para sintetizar os arquivos, devemos selecionar a “Implementation View” em “Design > Hierarchy”. Feito isso, devemos clicar uma vez sobre o nome do componente que será sintetizado:
15
Feito isso, basta um duplo-­‐clique em “Synthesize -­‐ XST”. Espere alguns instantes e verifique o log de erros para saber se a sintetização foi feita com sucesso.
Para visualizar o esquemático RTL, basta expandir “Synthesize -­‐ XST”, um dupo-­‐clique em “View RTL Schematic” e selecionar “Start with a schematic of the top-­‐
level block” no diálogo que será aberto.
Com o “Top-­‐level schematic” aberto, podemos dar duplos-­‐cliques de forma a expandir as “caixas-­‐pretas” que compoem o circuito.
Podemos, finalmente, simular o código. Selecione a “Simulation View” em “Design > Hierarchy” e clique uma vez sobre o nome do componente que será simulado. Feito isso, expanda “iSIM simulator” e dê duplos-­‐cliques, na sequência, em: “Behavioral Check Sintax” (um último teste de sintaxe) e 16
“Simulate Behavioral Model” (a simulação propriamente dita). Caso tudo corra bem, a janela do simulador (iSim) será aberta:
17
Agora, podemos forçar um valor no clock de forma a observar a saída do circuito (que se trata de um clk_div). Devemos clicar com o botão direito na variável desejada e selecionar “Force clock”. Isso abrirá uma janela com diversas caixas a serem preenchidas:
“Value Radix” -­‐ Tipo de clock: binário, ASCII, Decimal etc
“Leading Edge Value” -­‐ Valor inicial de clock
“Trailing Edge Value” -­‐ Valor final de clock
“Starting at Time Offset” -­‐ Quando o clock começa
“Cancel After Time Offset” -­‐ Quando o clock termina
“Duty Cycle (%)” -­‐ Razão entre Leading Edge e Trailing Edge
“Period” -­‐ Auto-­‐explicativo
Preenchendo da seguinte forma, obtemos um período de 50mHz.
18
Agora, devemos programar por quanto tempo a simulação será executada. Basta preencher a caixa de texto na barra de ícones e então clicar na setá para a esquerda (apagando a última simulação). Feito isso, devemos clicar no botão de play com uma ampulheta e na terceira lupa (com um X) para ajustar a escala dos gráficos ao tamanho da janela.
Esse é o resultado final. Como podemos ver, o clock foi devidamente dividido.
19
Outra forma de impor valores de clock e de certas entradas é utilizar um TestBench. Para tanto, devemos selecionar a “Implementation View” em “Design > Hierarchy”, clicar com o botão direito sobre o módulo principal, ir em “New > Source > TestBench”. Devemos dar um nome ao TestBench (normalmente adicionando-­‐se o sufixo _tb ao nome do componente original) e ao confirmar seremos levado à janela de seu código fonte (o código exemplo foi usado em um debouncer, que será descrito na última sessão):
118.LIBRARY ieee;
119.USE ieee.std_logic_1164.ALL;
120. 121.ENTITY debouncer2_tb IS
122.END debouncer2_tb;
123. 124.ARCHITECTURE behavior OF debouncer2_tb IS 125. 126. COMPONENT debouncer2
127. PORT(
128. Clk : IN std_logic;
129. Key : IN std_logic;
130. pulse : OUT std_logic
131. );
132. END COMPONENT;
133. 134. 135. -­‐-­‐Inputs
136. signal Clk : std_logic := '0';
137. signal Key : std_logic := '0';
138. 139. -­‐-­‐Outputs
140. signal pulse : std_logic;
141. 142. -­‐-­‐ Clock period definitions
143. constant Clk_period : time := 10 ms;
144. 145.BEGIN
146. 147. -­‐-­‐ Instantiate the Unit Under Test (UUT)
148. uut: debouncer2 PORT MAP (
149. Clk => Clk,
20
150. Key => Key,
151. pulse => pulse
152. );
153. 154. -­‐-­‐ Clock process definitions
155. Clk_process :process
156. begin
157. Clk <= '0';
158. wait for Clk_period/2;
159. Clk <= '1';
160. wait for Clk_period/2;
161. end process;
162. 163. 164. -­‐-­‐ Stimulus process
165. stim_proc: process
166. begin 167. Key <= '0';
168. wait for Clk_period;
169. Key <= '1';
170. wait for Clk_period * 6;
171. Key <= '0';
172. wait for Clk_period * 4;
173. Key <= '1';
174. wait for Clk_period * 4;
175. Key <= '0';
176. wait;
177. end process;
178. 179.END;
Como pode-­‐se ver, o módulo do exemplo possui duas entradas clk e key e uma saída pulse. Podemos inicializar as variáveis como nas linhas 136 e 137 e também definir um período para o clock.
Feito isso, podemos implementar o Clk_process, que define como o clock mudará no tempo. No caso, como podemos ver, ele é forçado a 0, meio período é esperado, ele é forçado a 1, meio período é esperado e o processo se repete, gerando uma onda quadrada.
O mesmo pode ser feito para os estímulos (as entradas) do componente no processo stim_proc. Podemos usar a 21
palavra reservada wait para definir os atrasos entre as mudanças de valores (que também podem ser definidas). Com o TestBench feito, basta simulá-­‐lo no lugar do componente original e teremos o processo automatizado, sem necessidade de forçar clocks ou coisas do tipo.
22
6. Criando um DCM
A criação de um DCM consiste nos seguintes passos: -­‐ Selecionar o “Implementation View” em “Design > Hierarchy”
-­‐ Clicar com o botão direito no projeto e selecionar New Source
-­‐ Busque por DCM na barra de pesquisa e selecione Single DCM_SP conforme a seguinte imagem:
-­‐ Dê um nome diferente de dcm, tal como dcm1 ou dcm2, e clique em Finish
-­‐ Feito isso, você será levado à janela Xilinx Clocking Window
-­‐ Nessa janela é possível selecionar os mais variados parâmetros para o clock que o DCM transformará. 23
Selecione conforme as exigências de seu projeto (como multiplicar/dividir o clock)
-­‐ Clique em Next, selecione “Use Global Buffers for all Selected Clock Outputs”
-­‐ Clique em Next, selecione “Show all Modifiable Outputs” e clique em Finish
24
-­‐ O DCM está criado, agora basta utilizar o código para o componente de DCM como mostrado na sessão 4.
25
7. Implementando um projeto na FPGA
Caso os sinais da entity do componente já estejam mapeados para partes da FPGA, como mostrado na sessão 4, você está pronto para implementar o código na placa. É importante, obviamente, que a placa esteja ligada ao PC pela interface USB e esteja devidamente alimentada.
Em primeiro lugar, devemos escolher a “Implementation View” em “Design > Hierarchy”, feito isso, clique uma vez sobre o módulo principal do seu projeto e clique duas vezes sobre “Configure Target Device”. A janela do iMPACT será aberta.
Clique duas vezes em “Boundary Scan” e clique com o botão direito no espaço em branco e selecione “Initialize Chain”. Na primeira miniatura da FPGA, carregue o arquivo .bit que correponde ao seu projeto. Deixei a segunda em “bypass”. Clique com o botão direito sobre a primeira miniatura e clique em “Program FPGA Only”.
O programa deve estar carregado na placa. Observe as partes para as quais os sinais foram mapeados.
26
8. Exemplos de código
Debouncer:
180.LIBRARY ieee;
181.USE ieee.STD_LOGIC_1164.all;
182.USE ieee.STD_LOGIC_UNSIGNED.all;
183. 184.ENTITY debouncer2 IS
185. PORT (Clk : IN STD_LOGIC;
186. Key : IN STD_LOGIC;
187. pulse : OUT STD_LOGIC);
188.END debouncer2;
189. 190.ARCHITECTURE shift_register OF debouncer2 IS
191.SIGNAL SHIFT_KEY : STD_LOGIC_VECTOR(3 DOWNTO 0) := "1111";
192. 193.BEGIN
194.PROCESS
195. BEGIN
196. 197. WAIT UNTIL Clk'EVENT AND Clk = '1';
198. 199. SHIFT_KEY(2 DOWNTO 0) <= SHIFT_KEY(3 DOWNTO 1);
200. SHIFT_KEY(3) <= NOT Key;
201. 202. IF SHIFT_KEY(3 DOWNTO 0)="0000" THEN PULSE <= '1';
203. ELSE PULSE <= '0';
204. END IF;
205. 206.END PROCESS;
207.END shift_register;
Primeiramente, explicaremos a motivação do circuito: remover trepidações transientes provenientes de chaves mecânicas.
O módulo possui duas entradas Clk e Key e uma saída Pulse. Key é o sinal proveniente da chave mecânica, pulse é a saída que corresponde ao sinal da chave “corrigido”. O circuito funciona inicializando o registrador para “1111” e esperando que ele se torne “0000”, para fazer pulse = 1. 27
A forma de atualizar o valor do registrador é ir colocando not key no lado direito do vetor de bits do registrador. Se por 4 clocks seguidos, o valor de key amostrado estiver em 1, o registrador estará no estado “0000” e pulse valerá 1, caso contrário pulse valerá 0. Isso faz sentido pois evita que a saída seja atualizada durante o transiente da vibração da chave, fazendo com que ela só seja ativada algum tempo depois (o suficiente para que a trepidação termine). Logo, haverá um atraso muito pequeno (porém detectável em osciloscópio) na resposta da chave.
A seguinte imagem, adquirida em um osciloscópio ligado à FPGA com o programa do Debouncer carregado demonstra tal atraso e comprova o funcionamento do circuito:
28
A curva mais baixa é key e a mais alta é not pulse, vemos, portanto, que pulse só vai a 1 algum tempo depois de key ter ido a 1. Esse é o tempo do transiente de vibração.
Referências:
An ISE Tutorial -­‐ Luiz Rennó
Projeto Debouncer -­‐ Camila Simões da Costa Cunha Vasconcellos, Henrique Duarte Faria, Patrick Svaiter, Paulo Roberto Yamasaki Catunda
Relatório de Sistemas Digitais: Projeto Base de Tempo -­‐ Michael D. Barreto e Silva, Luiz Carlos, Gabriela Dantas
29

Documentos relacionados

Anotações relatório 1 O código cedido pelo professor

Anotações relatório 1 O código cedido pelo professor (fica a seu critério), pois caso o arquivo seja salvo na área tmp, ele será apagado depois de algum tempo. Para se executar o arquivo em outra ocasião, dê unzip no arquivo para a área tmp para evi...

Leia mais

Segundo Trabalho de Sistemas Digitais

Segundo Trabalho de Sistemas Digitais A Figura 4 mostra a terceira parte do código que será a lógica de funcionamento do divisor de frequências. Quatro sinais internos são usados como parte da lógica. Dois serão contadores e serão os ...

Leia mais