Questão: Como alterar a sequência de execução de um programa?
Para atender aos desvios de sequenciamento do programa, é necessário que a instrução contenha a informação sobre o endereço de destino. A organização dos campos da instrução, que chamaremos de JMP (jump), pode ser a mostrada abaixo, onde M é o número de bits de endereço da memória de programa.
opCode | Endereço de Destino |
---|---|
X bits | M bits |
Não é necessário adicionar nenhum bit ao formato da instrução, uma vez que podemos aproveitar espaço do campo do endereço de memória das instruções de acesso à memória (LDA, STA, etc…). Só daremos mais uma interpretação para esse campo, como mostrado no desenho abaixo.
Para adicionar essa instrução ao nosso computador, devemos interligar o campo da instrução, referente ao Endereço de Destino, ao PC. Isso pode ser feito através de um MUX, como visto abaixo.
Responder o quiz de participação, no blackboard, em:
Conteúdos > Participação > Aula_5_Quiz-P1
Precisamos adicionar a instrução de Jump e para tanto, precisaremos modificar o decodificador de instruções, adicionando um novo ponto de controle. A tabela abaixo mostra a adição dessa instrução.
Instrução | Mnemônico | Código Binário | JMP | Sel MUX | Habilita A | Operação | habLeituraMEM | habEscritaMEM |
---|---|---|---|---|---|---|---|---|
Sem Operação | NOP | 0000 | X | 0 | XX | 0 | 0 | |
Carrega valor da memória para A | LDA | 0001 | 0 | 1 | 10 | 1 | 0 | |
Soma A e B e armazena em A | SOMA | 0010 | 0 | 1 | 01 | 1 | 0 | |
Subtrai B de A e armazena em A | SUB | 0011 | 0 | 1 | 00 | 1 | 0 | |
Carrega valor imediato para A | LDI | 0100 | 1 | 1 | 10 | 0 | 0 | |
Salva valor de A para a memória | STA | 0101 | 0 | 0 | XX | 0 | 1 | |
Desvio de execução | JMP | 0110 |
As saídas que estão indicadas como X ou XX devem ser implementadas como 0 ou 00. Isso facilita o processo de debug do projeto.
Vamos implementar, e simular, esse circuito em VHDL, adicionando o MUX e alterando o decodificador. Para tanto, utilizaremos o programa abaixo. Note que os valores precisam ser convertidos para binário.
Linha | Instrução | Observação | ||
---|---|---|---|---|
0 | JMP @4 | Deve desviar para a posição 4 | ||
1 | JMP @5 | Deve desviar para a posição 5 | ||
2 | NOP | |||
3 | NOP | |||
4 | JMP @1 | Deve desviar para a posição 1 | ||
5 | NOP | |||
6 | JMP @6 | Fim. Deve ficar neste laço |
O caractere arroba (@) indica uma posição de memória, tanto para RAM quanto para ROM.
O resultado da compilação será o circuito, em RTL, mostrado no diagrama abaixo.
Para fazer a simulação não precisaremos de nenhum sinal de entrada, além do clock. Vale lembrar que o clock é aplicado através do sinal KEY(0).
A verificação do funcionamento do circuito pode ser feita através do monitoramento do Program Counter e sua comparação com a execução do programa mostrado no fim do ítem Procedimento desta atividade. O resultado esperado está mostrado abaixo.
Podemos ver que, de acordo com o esperado, o Program Counter assume a seguinte sequência de valores: 0, 4, 1, 5, 6, 6, 6, 6, …
Como a última instrução é o _JMP @6_, que está localizada na posição 6 da memória de instruções, ocorrerá um laço infinito que sempre executará essa instrução.
O desvio condicional permite a criação de estruturas de programação do If … Then … Else e laços de execução (For … Next e Do … While).
Esse desvio só é executado se uma determinada condição for atendida. Por exemplo, podemos fazer o desvio sempre que uma comparação entre dois números resultar igual (verdadeira).
Nota-se que precisaremos de duas instruções: uma de comparação e outra para o desvio se a comparação for verdadeira. Um exemplo de código seria:
CEQ M[enderecoVariavel] # Compara se o valor do acumulador é igual ao valor contido no endereço de memória. Caso sim ativa o flag IGUAL.
...
... # Várias instruções que não executam comparações.
...
JEQ EnderecoDestino # Verifica o flag IGUAL e, caso verdadeiro, faz o desvio.
Onde: CEQ = compara se igual; IGUAL = flag (flip-flop) que recebe o resultado da comparação; JEQ = desvia se o flag de igual for verdadeiro (Jump Equal).
Pelo código, vemos que o resultado da comparação, armazenado no respectivo flag, deve ficar armazenado até a próxima instrução de comparação.
O destino do desvio condicional é retirado do mesmo campo imediato que armazena o destino do desvio incodicional (Jump). Portanto, o formato da instrução não precisa ser modificado.
O bloco “Lógica de Desvio” foi desenhado fora da UC para evidenciar a sua existência.
Utilizaremos uma porta NOR de 8 entradas, conectadas aos 8 bits da saída da ULA, para detectar que o resultado da operação da ULA é Zero, conforme mostrado abaixo.
Assim, quando a saída do detector tiver valor lógico ALTO, significa que o resultado presente na saída da ULA é zero.
Exemplo do código em VHDL:
Zero <= not (Saida(7) or Saida(6) or Saida(5) or Saida(4) or Saida(3) or Saida(2) or Saida(1) or Saida(0));
Essa instrução necessitará de:
Modificações na ULA, para poder fazer a comparação;
Inclusão de um flip-flop para armazenar o resultado da comparação;
Inclusão de mais um ponto de controle, para ativar o flip-flop do flag de igual;
Inclusão de um módulo com a lógica de desvio:
As duas tabelas abaixo mostram os sinais envolvidos nessa modificação. Elas devem ser completadas de acordo com o comportamento desejado para o JEQ.
Instrução | Mnemônico | Código Binário | JMP | JEQ | Sel MUX | Hab_A | Operação | habFlag= | RD | WR |
---|---|---|---|---|---|---|---|---|---|---|
Sem Operação | NOP | 0000 | 0 | 0 | X | 0 | XX | 0 | 0 | |
Carrega valor da memória para A | LDA | 0001 | 0 | 0 | 0 | 1 | 10 | 1 | 0 | |
Soma A e B e armazena em A | SOMA | 0010 | 0 | 0 | 0 | 1 | 01 | 1 | 0 | |
Subtrai B de A e armazena em A | SUB | 0011 | 0 | 0 | 0 | 1 | 00 | 1 | 0 | |
Carrega valor imediato para A | LDI | 0100 | 0 | 0 | 1 | 1 | 10 | 0 | 0 | |
Salva valor de A para a memória | STA | 0101 | 0 | 0 | 0 | 0 | XX | 0 | 1 | |
Desvio de execução | JMP | 0110 | 1 | 0 | X | 0 | XX | 0 | 0 | |
Desvio condicional de execução | JEQ | 0111 | 0 | 1 | X | 0 | XX | 0 | 0 | |
Comparação | CEQ | 1000 | 0 | 0 |
Observação: Hab_A : Habilita A; habFlag= : habFlagIgual; RD : habLeituraMEM; WR : habEscritaMEM;
As saídas que estão indicadas como X ou XX devem ser implementadas como 0 ou 00. Isso facilita o processo de debug do projeto.
JMP | JEQ | Flag de Igual | Seleção do Mux | Ação Resultante | |
---|---|---|---|---|---|
0 | 0 | X | 0 | Prox. Instrução | |
1 | 0 | X | 1 | Desvio de JMP | |
0 | 1 | 0 | 0 | Prox. Instr. JEQ | |
0 | 1 | 1 | 1 | Desvio de JEQ |
Vamos implementar, e simular, esse circuito em VHDL, fazendo as alterações necessárias na ULA e na Unidade de Controle. Para tanto, utilizaremos o programa abaixo. Note que os valores precisam ser convertidos para binário.
Linha | Instrução | Observação | ||
---|---|---|---|---|
0 | JMP @4 | Deve desviar para a posição 4 | ||
1 | JEQ @9 | Deve desviar para a posição 9 | ||
2 | NOP | |||
3 | NOP | |||
4 | LDI $5 | Carrega acumulador com valor 5 | ||
5 | STA @256 | Armazena 5 na posição 256 da memória | ||
6 | CEQ @256 | A comparação deve fazer o flagIgual ser 1 | ||
7 | JMP @1 | Vai testar o flagIgual depois do jump | ||
8 | NOP | |||
9 | LDI $4 | Carrega acumulador com valor 4 | ||
10 | CEQ @256 | Compara com valor 5, deve fazer o flagIgual ser 0 | ||
11 | JEQ @3 | Não deve ocorrer o desvio | ||
12 | JMP @12 | Fim. Deve ficar neste laço |
O caractere arroba (@) indica um endereço da memória (RAM ou ROM) enquanto que o caractere cifrão ($) indica um valor constante (imediato).
O resultado da compilação será o circuito, em RTL, mostrado no diagrama abaixo.
Para fazer a simulação não precisaremos de nenhum sinal de entrada, além do clock. Vale lembrar que o clock é aplicado através do sinal KEY(0).
A verificação do funcionamento do circuito pode ser feita através do monitoramento do Program Counter e sua comparação com a execução do programa mostrado no fim do ítem Procedimento desta atividade. O resultado esperado está mostrado abaixo.
Podemos ver que, de acordo com o esperado, o Program Counter assume a seguinte sequência de valores: 0, 4, 5, 6, 7, 1, 9, 10, 11, 12, 12, 12 …
Como a última instrução é o _JMP @12_, que está localizada na posição 12 da memória de instruções, ocorrerá um laço infinito que sempre executará essa instrução.
Uma outra instrução de desvio é o JSR (jump sub routine), que desvia a execução para um trecho de código que, após executado retorna para a posição seguinte à chamada da sub-rotina. Ela funciona juntamente com a instrução de retorno (RET), que indica o fim da rotina que foi chamada.
Isso pode ser implementado aumentando-se o MUX_JMP/PC+1 para quatro entradas e armazenando o valor de PC+1 do momento da chamada da sub-rotina. Esse valor será utilizado pela instrução RET, para retornar ao processamento no ponto deixado pela instrução JSR.
Essa implementação permite a chamada de somente uma sub-rotina, não permitindo sub-rotinas aninhadas. A implementação de sub-rotinas aninhadas necessita que seja utilizada uma estrutura de pilha controlada pelo hardware.
Para simplificar o diagrama, os sinais de clock, vindos do detector de borda, foram suprimidos no desenho. Mas, na implementação, o sinal de clock deve ser distribuito para todos os registradores.
O bloco “Lógica de Desvio” foi desenhado fora da UC para evidenciar a sua existência.
Responder o quiz de participação, no blackboard, em:
Conteúdos > Participação > Aula_5_Quiz-P2
Abaixo, temos os pontos de controle necessários para implementar essa nova instrução. Vamos fazer as alterações necessárias, implementar e simular o circuito.
Instrução | Mnemônico | Código Binário | Hab Escrita Retorno | JMP | RET | JSR | JEQ | Sel MUX | Hab_A | Operação | habFlag= | RD | WR |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Sem Operação | NOP | 0000 | X | 0 | XX | 0 | 0 | ||||||
Carrega valor da memória para A | LDA | 0001 | 0 | 1 | 10 | 1 | 0 | ||||||
Soma A e B e armazena em A | SOMA | 0010 | 0 | 1 | 01 | 1 | 0 | ||||||
Subtrai B de A e armazena em A | SUB | 0011 | 0 | 1 | 00 | 1 | 0 | ||||||
Carrega valor imediato para A | LDI | 0100 | 1 | 1 | 10 | 0 | 0 | ||||||
Salva valor de A para a memória | STA | 0101 | 0 | 0 | XX | 0 | 1 | ||||||
Desvio de execução | JMP | 0110 | X | 0 | XX | 0 | 0 | ||||||
Desvio condicional de execução | JEQ | 0111 | X | 0 | XX | 0 | 0 | ||||||
Comparação | CEQ | 1000 | |||||||||||
Chamada de Sub Rotina | JSR | 1001 | |||||||||||
Retorno de Sub Rotina | RET | 1010 |
Observação: Hab_A : Habilita A; habFlag= : habFlagIgual; RD : habLeituraMEM; WR : habEscritaMEM;
As saídas que estão indicadas como X ou XX devem ser implementadas como 0 ou 00. Isso facilita o processo de debug do projeto.
JMP | RET | JSR | JEQ | Flag de Igual | Seleção do Mux | Ação Resultante | |
---|---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | X | 00 | Prox. Instrução | |
1 | 0 | 0 | 0 | X | 01 | Desvio de JMP | |
0 | 0 | 0 | 1 | 0 | 00 | Prox. Instr. JEQ | |
0 | 0 | 0 | 1 | 1 | 01 | Desvio de JEQ | |
0 | 0 | 1 | 0 | X | 01 | Desvio de JSR | |
0 | 1 | 0 | 0 | X | 10 | Desvio de RET |
Para padronizar os testes do decodificador, iremos assumir a seguinte distribuição de bits na palavra de controle:
Bit 11 | Bit 10 | Bit 9 | Bit 8 | Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 |
---|---|---|---|---|---|---|---|---|---|---|---|
habEscritaRetorno | JMP | RET | JSR | JEQ | SelMUX | Habilita A | Operação Bit 1 | Operação Bit 0 | HabFlagIgual | HabLeituraMEM | HabEscritaMEM |
Dado o aumento do número de pontos de controle, fica mais simples e menos propenso a erros fazer o seguinte:
Utilizar o recurso de Alias do VHDL;
Atribuir a ativação por ponto de controle e não mais a palavra de controle inteira de uma única vez.
Abaixo, temos um exemplo de como utilizar essa técnica.
-- Antes do BEGIN, depois da definição dos respectivos SIGNALs
alias SelMUX: std_logic is controlWord(6);
alias operacaoULA: std_logic_vector(1 downto 0) is controlWord(4 downto 3);
-- Depois do BEGIN, para fazer a atribuição individual.
SelMUX <= '1' when (opcode = ???) or (opcode = ???) else '0';
operacaoULA <= "00" when (opcode = ???) else
"01" when (opcode = ???) else
"10" when (opcode = ???) or (opcode = ???) else
"11";
TESTE
Para testar, utilizaremos o programa abaixo. Note que os valores precisam ser convertidos para binário.
Linha | Instrução | Observação | ||
---|---|---|---|---|
0 | JSR @14 | Deve desviar para a posição 14 | ||
1 | JMP @5 | Deve desviar para a posição 5 | ||
2 | JEQ @9 | Deve desviar para a posição 9 | ||
3 | NOP | |||
4 | NOP | |||
5 | LDI $5 | Carrega acumulador com valor 5 | ||
6 | STA @256 | Armazena 5 na posição 256 da memória | ||
7 | CEQ @256 | A comparação deve fazer o flagIgual ser 1 | ||
8 | JMP @2 | Vai testar o flagIgual depois do jump | ||
9 | NOP | |||
10 | LDI $4 | Carrega acumulador com valor 4 | ||
11 | CEQ @256 | Compara com valor 5, deve fazer o flagIgual ser 0 | ||
12 | JEQ @3 | Não deve ocorrer o desvio | ||
13 | JMP @13 | Fim. Deve ficar neste laço | ||
14 | NOP | |||
15 | RET | Retorna para a posição 1 |
O caractere arroba (@) indica um endereço de memória (RAM ou ROM) enquanto que o caractere cifrão ($) indica um valor constante (imediato).
Esta atividade deverá ser entregue no Black Board, na semana seguinte a esta aula.
O resultado da compilação será o circuito, em RTL, mostrado no diagrama abaixo.
Para fazer a simulação não precisaremos de nenhum sinal de entrada, além do clock. Vale lembrar que o clock é aplicado através do sinal KEY(0).
A verificação do funcionamento do circuito pode ser feita através do monitoramento do Program Counter e sua comparação com a execução do programa mostrado no fim do ítem Procedimento desta atividade. O resultado esperado está mostrado abaixo.
Podemos ver que, de acordo com o esperado, o Program Counter assume a seguinte sequência de valores: 0, 14, 15, 1, 5, 6, 7, 8, 2, 9, 10, 11, 12, 13, 13 …
Como a última instrução é o _JMP @13_, que está localizada na posição 13 da memória de instruções, ocorrerá um laço infinito que sempre executará essa instrução.
Somente a última Atividade deverá ser entregue através do Blackboard!