Até agora, fizemos o esboço geral do nosso projeto, um contador que deverá:
Ter um botão de entrada para incrementar a contagem;
Ter um botão de entrada para reiniciar a contagem;
Utilizar as chaves para configurar o limite de contagem;
Mostrar o valor da contagem nos displays de sete segmentos;
Armazenar todas as variáveis do programa na memória RAM.
Além disso, a distribuição dos periféricos no espaço de endereçamento foi feita da seguinte forma:
RAM: da posição 0 até a posição 63;
RAM (expansão): reservado da posição 64 até a posição 127;
E/S para escrita: da posição 256 até a posição 319;
E/S para leitura: da posição 320 até a posição 383;
E o endereçamento da memória RAM e dos LEDs está mostrado nos desenhos abaixo.
Falta fazer o endereçamentos dos displays de sete segmentos, das chaves e dos botões. Vamos começar pelo display, que está no mesmo bloco de E/S para escrita, juntamente com os LEDs. Portanto, teremos que ajustar o circuito para acomodar esses dois periféricos.
Os LEDs estão alocados na faixa de endereços para E/S de escrita (entre 256 e 319), nos seguintes endereços:
Endereço 256: 8 LEDs (LEDR0 até 7)
Endereço 257: LEDR8;
Endereço 258: LEDR9.
Vamos dividir essa faixa de 64 posições em duas metades:
A primeira, de 256 até 287, conterá somente os LEDs;
A segunda, de 288 até 319, conterá somente os Displays.
A linha de endereço que faz essa divisão é o sinal A5. Portanto, quando A5 tiver o valor ‘0’, estaremos na primeira metade da faixa. De forma complementar, quando A5 tiver o valor ‘1’, estaremos na segunda metade da faixa.
Para manter o edereçamento definido anteriormente para os LEDs, devemos adicionar às portas AND a linha de endereço A5 invertida, conforme a listagem abaixo.
Para o ativar o conjunto de 8 LEDs, no endereço 256, teremos uma porta AND com quatro entradas:
A saída do decodificador de blocos S4 (endereços entre 256 e 319);
O sinal de habilita escrita (WR);
A saída do decodificador de endereços S0;
A linha de endereços A5 invertida.
Para o ativar o LEDR8, no endereço 257, teremos uma porta AND com quatro entradas:
A saída do decodificador de blocos S4 (endereços entre 256 e 319);
O sinal de habilita escrita (WR);
A saída do decodificador de endereços S1;
A linha de endereços A5 invertida.
Para o ativar o LEDR9, no endereço 258, teremos uma porta AND com quatro entradas:
A saída do decodificador de blocos S4 (endereços entre 256 e 319);
O sinal de habilita escrita (WR);
A saída do decodificador de endereços S2;
A linha de endereços A5 invertida.
O circuito para os displays será similar, porém com a utilização da linha de endereço A5 sem inversão. Isso fará com que os displays sejam endereçados como mostrado a seguir.
Display | Endereço em Decimal | ||
---|---|---|---|
HEX0 | 288 | ||
HEX1 | 289 | ||
HEX2 | 290 | ||
HEX3 | 291 | ||
HEX4 | 292 | ||
HEX5 | 293 |
O circuito, com todas as conexões desenhadas, ficaria como o mostrado abaixo.
Vamos implementá-lo em VHDL e testar o seu funcionamento utilizando um programa que escreva nesses endereços. Uma forma simples seria a utilização de uma sequência de incrementos e armazenamentos, como a mostrada abaixo.
Linha | Instrução | Observação |
---|---|---|
0 | LDI $1 | Carrega o acumulador com o valor 1 |
1 | STA @0 | Armazena o valor do acumulador na posição zero da memória (MEM[0]) |
2 | SOMA @0 | Soma o valor atual do acumulador com o conteúdo de MEM[0] |
3 | STA @288 | Armazena o valor do acumulador em HEX0 |
4 | SOMA @0 | Incrementa o valor do acumulador em uma unidade |
5 | STA @289 | Armazena o valor do acumulador em HEX1 |
6 | SOMA @0 | Incrementa o valor do acumulador em uma unidade |
7 | STA @290 | Armazena o valor do acumulador em HEX2 |
8 | SOMA @0 | Incrementa o valor do acumulador em uma unidade |
9 | STA @291 | Armazena o valor do acumulador em HEX3 |
10 | SOMA @0 | Incrementa o valor do acumulador em uma unidade |
11 | STA @292 | Armazena o valor do acumulador em HEX4 |
12 | SOMA @0 | Incrementa o valor do acumulador em uma unidade |
13 | STA @293 | Armazena o valor do acumulador em HEX5 |
14 | JMP @2 | Desvia e continua incrementando e escrevendo nos displays |
O caractere arroba (@) indica um endereço de memória (RAM ou ROM) enquanto que o caractere cifrão ($) indica um valor constante (imediato).
Programa, igual ao acima, para ser copiado e utilizado na ROM, com os devidos ajustes nos endereçamentos.
Para as chaves e botões, vamos utilizar um circuito similar. Porém, endereçado no Bloco 5 (endereço 320 até 383), ou seja, conforme o descrito abaixo:
Endereço 320: as chaves SW0 até SW7 (A5 = 0);
Endereço 321: a chave SW8 (A5 = 0);
Endereço 322: a chave SW9 (A5 = 0);
Endereço 352: o botão KEY0 (A5 = 1);
Endereço 353: o botão KEY1 (A5 = 1);
Endereço 354: o botão KEY2 (A5 = 1);
Endereço 355: o botão KEY3 (A5 = 1);
Endereço 356: o botão FPGA_RESET (A5 = 1).
Como vamos ler esses periféricos, devemos interligá-los ao barramento de leitura de dados e utilizar o sinal de Leitura (RD) na lógica das portas AND.
Para facilitar a leitura do diagrama, vamos eliminar as linhas e indicar as conexões através do nome do sinal sendo conectado. Isso faz com que o diagrama fique mais limpo e continue informando sobre as conexões feitas. Não podemos esquecer de que todos os pontos com o nome de um dado sinal devem ser interligados na hora de fazer o projeto em VHDL.
Vamos implementá-lo em VHDL e testar o seu funcionamento utilizando um programa que leia as chaves e botões e escreva os valores nos displays ou nos LEDs. Como o processador estará rodando a 50MHz, a sensação será de que as chaves e botões estão ligados diretamente aos displays e LEDs.
Como usamos KEY0 como botão de entrada, o clock deve vir de outro botão ou do Clock_50.
Linha | Instrução | Observação | ||
---|---|---|---|---|
0 | LDA @320 | Carrega o acumulador com a leitura das chaves SW0 até SW7 | ||
1 | STA @288 | Armazena o valor do acumulador no display HEX0 | ||
2 | LDA @321 | Carrega o acumulador com a leitura da chave SW8 | ||
3 | STA @289 | Armazena o valor do acumulador no display HEX1 | ||
4 | LDA @322 | Carrega o acumulador com a leitura da chave SW9 | ||
5 | STA @290 | Armazena o valor do acumulador no display HEX2 | ||
6 | LDA @352 | Carrega o acumulador com a leitura do botão KEY0 | ||
7 | STA @291 | Armazena o valor do acumulador no display HEX3 | ||
8 | LDA @353 | Carrega o acumulador com a leitura do botão KEY1 | ||
9 | STA @292 | Armazena o valor do acumulador no display HEX4 | ||
10 | LDA @354 | Carrega o acumulador com a leitura do botão KEY2 | ||
11 | STA @293 | Armazena o valor do acumulador no display HEX5 | ||
12 | LDA @355 | Carrega o acumulador com a leitura do botão KEY3 | ||
13 | STA @257 | Armazena o valor do bit0 do acumulador no LDR8 | ||
14 | LDA @356 | Carrega o acumulador com a leitura do botão FPGA_RESET | ||
15 | STA @258 | Armazena o valor do bit0 do acumulador no LDR9 | ||
16 | JMP @0 | Desvia e continua atualizando os valores das entradas nas saídas |
O caractere arroba (@) indica um endereço de memória (RAM ou ROM) enquanto que o caractere cifrão ($) indica um valor constante (imediato).
Programa, igual ao acima, para ser copiado e utilizado na ROM, com os devidos ajustes nos endereçamentos.
O botão KEY0 será usado para incrementar o contador. Dessa forma, cada vez que ele for pressionado devemos incrementar uma única vez o valor da contagem.
Para evitar que ocorram múltiplos incrementos, devemos garantir que os ruídos de chaveamento de KEY0 sejam eliminados. Isso é feito através do circuito de debounce implementado no edge detector, como mostrado no circuito abaixo.
Como o edge detector transforma o sinal do botão pressionado em apenas um pulso de clock, devemos utilizar um circuito de memorização da ativação desse botão (flag de botão apertado). Esse circuito de memorização deve ser limpo após qualquer leitura do botão que retorne a informação de botão pressionado.
Esse tipo de implementação está mostrado no circuito abaixo, onde qualquer acesso ao endereço de limpaLeitura (endereço 511 ou 0x1FF) fará com que o valor armazenado no flip-flop seja limpo (a saída Q assumirá o valor 0).
Precisaremos fazer a decodificação do endereço 511 para implementar a limpeza do flag. Como temos que decodificar um único endereço e não pode ter espelhamento em outro endereço (como ocorre com as outras decodificações que fizemos), devemos utilizar todos os oito bits de endereço nessa decodificação.
Como utilizamos o sinal escrita (WR) na decodificação do endereço 511/510, o acesso de escrita executará a limpeza do valor armazenado no flip-flop, ou seja, uma instrução STA 511.
Para testar o novo circuito, vamos fazer um programa parecido com o listado abaixo.
Como usamos KEY0 como botão de entrada, o clock deve vir de outro botão ou do Clock_50.
Linha | Instrução | Observação | ||
---|---|---|---|---|
0 | LDI $0 | Carrega o acumulador com o valor 0 | ||
1 | STA @0 | Armazena o valor do acumulador em MEM[0] (constante 0) | ||
2 | STA @2 | Armazena o valor do acumulador em MEM[2] (contador) | ||
3 | LDI $1 | Carrega o acumulador com o valor 1 | ||
4 | STA @1 | Armazena o valor do acumulador em MEM[1] (constante 1) | ||
5 | NOP | |||
6 | LDA @352 | Carrega o acumulador com a leitura do botão KEY0 | ||
7 | STA @288 | Armazena o valor lido em HEX0 (para verificar erros de leitura) | ||
8 | CEQ @0 | Compara com o valor de MEM[0] (constante 0) | ||
9 | JEQ @11 | Desvia se igual a 0 (botão não foi pressionado) | ||
10 | JSR @32 | O botão foi pressionado, chama a sub-rotina de incremento | ||
11 | NOP | Retorno da sub-rotina de incremento | ||
12 | JMP @5 | Fecha o laço principal, faz uma nova leitura de KEY0 | ||
… | ||||
… | ||||
32 | STA @511 | Limpa a leitura do botão | ||
33 | LDA @2 | Carrega o valor de MEM[2] (contador) | ||
34 | SOMA @1 | Soma com a constante em MEM[1] | ||
35 | STA @2 | Salva o incremento em MEM[2] (contador) | ||
36 | STA @258 | Armazena o valor do bit0 do acumulador no LDR9 | ||
37 | STA @293 | Armazena o valor do acumulador no HEX5 | ||
38 | RET | Retorna da sub-rotina |
O caractere arroba (@) indica um endereço de memória (RAM ou ROM) enquanto que o caractere cifrão ($) indica um valor constante (imediato).
Neste programa, as instruções NOP são para facilitar a localização dos destinos de desvios. Em um programa normal elas não deveriam ser utilizadas para essa finalidade.
Programa, igual ao acima, para ser copiado e utilizado na ROM, com os devidos ajustes nos endereçamentos.
LDI $0
STA @0
STA @2
LDI $1
STA @1
NOP
LDA @352
STA @288
CEQ @0
JEQ @11
JSR @32
NOP
JMP @5
STA @511
LDA @2
SOMA @1
STA @2
STA @258
STA @293
RET
No caso de KEY0 e KEY1, a sua leitura não possui os 7 bits mais significativos. Isso pode gerar erros na leitura devido a esses bits não estarem conectados e poderem ser lidos tanto como valor alto como valor baixo.
Para resolver esse inconveniente, podemos utilizar uma porta tristate de oito bits com os sete bits mais significativos conectados ao nível lógico baixo.
No caso da leitura de KEY0, o circuito ficaria como o mostrado abaixo.
Outra solução, mais elegante, é a manutenção do circuito com um único bit e a implementação da instrução AND no processador. Assim, após cada leitura de KEY0 (ou KEY1) devemos fazer uma operação AND com a máscara adequada (b0000_0001) para limpar os bits 7 até 1 e deixar somente o valor do bit zero.
Linha | Instrução | Observação |
---|---|---|
0 | STA @511 | Limpa a leitura do botão |
1 | LDI $1 | Carrega o acumulador com o valor 1 |
2 | STA @1 | Armazena o valor do acumulador em MEM[1] (constante 1) |
3 | NOP | |
4 | LDA @352 | Carrega o acumulador com a leitura do botão KEY0 |
5 | STA @288 | Armazena o valor lido em HEX0 (para verificar erros de leitura) |
6 | AND @1 | Utiliza a máscara b0000_0001 para limpar todos os bits menos o bit 0 |
7 | STA @289 | Armazena o valor mascarado em HEX1 |
8 | NOP | |
9 | JMP @3 | Fecha o laço principal, faz uma nova leitura de KEY0 |
Como usamos KEY0 como botão de entrada, o clock deve vir de outro botão ou do Clock_50.
Programa, igual ao acima, para ser copiado e utilizado na ROM, com os devidos ajustes nos endereçamentos.
Esta Atividade deverá ser entregue através do Blackboard!