As instruções do microprocessador

Mesmo o mais simples dos microprocessadores pode executar uma grande variedade de instruções. As instruções são implementadas como padrões binários; cada uma delas significa algo diferente quando são carregadas pelo registrador de instruções. Como pessoas não são tão boas em lembrar padrões binários, um conjunto de pequenas palavras foi definido para representar os diferentes padrões binários. Esta coleção de palavras é conhecida como a linguagem assembly do processador. Um assembler (montador) pode traduzir as palavras para o seu padrão binário e a informação de saída do assembler é alocada na memória para ser executada pelo microprocessador.

Aqui está uma série de instruções assembly que um projetista poderia criar para este microprocessador simples:

  • LOADB mem - carrega o registrador B do endereçamento de memória
  • CONB con - carrega um valor constante no registrador B
  • SAVEB mem - armazena o registrador B no endereçamento de memória
  • SAVEC mem - armazena o registrador C no endereçamento de memória
  • ADD - soma A com B e armazena o resultado em C
  • SUB - subtrai A de B e armazena o resultado em C
  • MUL - multiplica A por B e armazena o resulado em C
  • DIV - divide A por B e armazena o resultado em C
  • COM - compara A com B e armazena o resultado no registrador teste
  • JUMP addr - desvia para um endereçamento
  • JEQ addr - desvia, se igual, para o endereçamento
  • JNEQ addr - desvia, se não igual, para o endereçamento
  • JG addr - desvia, se maior que, para o endereçamento
  • JGE addr - desvia, se maior que ou igual, para o endereçamento
  • JL addr - desvia, se menor que, para o endereçamento
  • JLE addr - desvia, se menor que ou igual, para o endereçamento
  • STOP - pára a execução
  • LOADA mem - carrega o registrador A do endereçamento de memória
Se você leu o artigo Como funciona a programação em C você sabe que este pequeno código em C vai calcular o fatorial de 5 (onde o fatorial de 5 = 5! = 5 * 4 * 3 * 2 * 1 = 120):
       a=1;
    f=1;
    while (a <= 5)
    {
    f = f * a;
    a = a + 1;
    }

Ao fim da execução do programa, a variável f conterá o fatorial de 5.

Linguagem Assembly
Um compilador C traduz o código em C para a linguagem assembly. Se considerarmos que a RAM começa no endereço 128 deste processador e a ROM (que contém o programa em linguagem assembly) começa no endereçamento 0, então a linguagem do nosso simples microprocessador seria assim:

       // Suponha que a está no endereçamento 128
    // Suponha que f está no endereçamento 129
    0 CONB 1 // a=1;
    1 SAVEB 128
    2 CONB 1 // f=1;
    3 SAVEB 129
    4 LOADA 128 // if a > 5 desvia para 17
    5 CONB 5
    6 COM
    7 JG 17
    8 LOADA 129 // f=f*a;
    9 LOADB 128
    10 MUL
    11 SAVEC 129
    12 LOADA 128 // a=a+1;
    13 CONB 1
    14 ADD
    15 SAVEC 128
    16 JUMP 4 // volta para o if
    17 STOP

Memória ROM
Agora vem a pergunta: "Como essas instruções vão ser exibidas na ROM?" Cada uma dessas instruções de linguagem assembly tem de ser representadas por um número binário. Para simplificar as coisas, vamos supor que cada instrução de linguagem assembly equivale a um único número:

  • LOADA - 1
  • LOADB - 2
  • CONB - 3
  • SAVEB - 4
  • SAVEC mem - 5
  • ADD - 6
  • SUB - 7
  • MUL - 8
  • DIV - 9
  • COM - 10
  • JUMP addr - 11
  • JEQ addr - 12
  • JNEQ addr - 13
  • JG addr - 14
  • JGE addr - 15
  • JL addr - 16
  • JLE addr - 17
  • STOP - 18
Esses números são conhecidos como opcodes (códigos de operação). Na ROM, nosso pequeno programa estaria assim:
       // Suponha que a está no endereçamento 128
    // Suponha que f está no endereçamento 129
    Addr opcode/value
    0 3 // CONB 1
    1 1
    2 4 // SAVEB 128
    3 128
    4 3 // CONB 1
    5 1
    6 4 // SAVEB 129
    7 129
    8 1 // LOADA 128
    9 128
    10 3 // CONB 5
    11 5
    12 10 // COM
    13 14 // JG 17
    14 31
    15 1 // LOADA 129
    16 129
    17 2 // LOADB 128
    18 128
    19 8 // MUL
    20 5 // SAVEC 129
    21 129
    22 1 // LOADA 128
    23 128
    24 3 // CONB 1
    25 1
    26 6 // ADD
    27 5 // SAVEC 128
    28 128
    29 11 // JUMP 4
    30 8
    31 18 // STOP

Como você pode ver, sete linhas de código em C se transformaram em 18 linhas de linguagem assembly e isso se transforma em 32 bytes na ROM.

Decodificação
O decodificador de instrução precisa transformar cada um dos opcodes em um conjunto de sinais que guiam os diferentes componentes dentro do microprocessador. Vamos pegar a instrução ADD como exemplo e ver o que ela precisa fazer.

  1. Durante o primeiro ciclo do clock, nós precisamos carregar a instrução; depois, o decodificador de instrução precisa:
    • ativar o buffer tri-state para o contador de programa;
    • ativar a linha RD;
    • ativar a entrada de dados no buffer tri-state;
    • armazenar a instrução no registrador de instruções;
  2. Durante o segundo ciclo do clock, a instrução ADD é decodificada. Não é necessário muito trabalho:
    • configure a operação do ULA para adição;
    • trave a saída do ULA no registrador C;
  3. Durante o terceiro ciclo de clock, o contador de programa é incrementado (em teoria, esse processo poderia estar acontecendo ao mesmo tempo que o segundo ciclo).
Cada instrução pode ser separada em um conjunto de operações em seqüência, como essas. Elas manipulam os componentes do microprocessador na ordem adequada. Algumas instruções, como a ADD, podem levar dois ou três ciclos de clock, outras podem durar cinco ou seis ciclos de clock.