Vous êtes sur la page 1sur 9

UFRJ – COPPE – PESC

Arquitetura de Computadores I – Prof. Felipe França


Alexandre F. S. de Mattos – 109109763
Lúcio Martins de Paiva – 109109739
Primeiro período de 2009

Trabalho de Microprogramação

1. Conceitos de Microprogramação

Os primeiros projetos de processadores inicialmente tinham seu conjunto de instruções


implementado através de lógica combinatória, construído em circuito digital, da maneira que
habitualmente chama-se hard wired. Uma vez projetado e construído o processador, é virtualmente
impossível alterar sua arquitetura ou seu conjunto de instruções. Adicionar uma nova instrução, por
exemplo, poderia levar os projetistas a ter que remodelar a arquitetura completamente. Se, por um
lado, esse método é computacionalmente eficiente (as máquinas teoricamente são capazes de
possuir maior desempenho de execução), por outro, torna a construção, depuração e alteração destes
processadores um trabalho complexo.

Figura 1 - Decodificador de instruções (esquerda) e matriz de controle (direita) hard-wired.

Em vista desse problema, surgiu a necessidade de uma arquitetura com uma unidade de
controle (UC) maleável. Esta unidade, que manipula o estado do processador através de sinais de
controle, poderia ser implementada de maneira a facilitar o trabalho dos projetistas e a evolução da
arquitetura do processador.

No processador microprogramado, a UC possui uma memória interna, de forma a armazenar


o microprograma. O microprograma é executado da mesma forma que um programa assembly,
sequencialmente, e possui inclusive um apontador de microinstruções. Cada instrução de assembly
é implementada de forma a executar um certo trecho do microprograma, como uma se uma rotina
fosse acionada.

O microprograma, por sua vez, é composto de uma série de palavras na memória da UC


chamadas microinstruções. Cada microinstrução é composta basicamente de um vetor de bits, que
são usados para acionar ou inibir os diversos pontos de controle da máquina (isso não é totalmente
verdade; alguns bits têm outra utilidade - ver parágrafo seguinte). A cada pulso de clock gerado,
atualiza-se o estado da unidade de controle, o que faz com que se atualize a posição do apontador de
microinstruções. Ao fazer isso, a UC consegue então manipular os pontos de controle e,
consequentemente, altera o estado da máquina como um todo.

Figura 2 - A máquina DLX do Escape, com a unidade de controle destacada,


mostrando a memória de microprograma e a jump table.

Outra coisa a se notar é que o apontador de microinstruções pode sofrer desvios, assim como
o PC num código assembly. Os desvios podem inclusive ser condicionais, levando em conta o valor
atual de registradores da máquina comparados entre si, comparados com imediatos passados no
código assembly (e obtidos através do registrador de instrução) ou comparados com constantes
embutidas nas próprias palavras de microinstrução (uma outra utilidade dos bits, como foi
adiantado no parágrafo acima).

Finalmente, há também que se falar a respeito de como a UC sabe qual trecho do


microprograma deve ser executado para cada instrução assembly. Isso é feito utilizando uma
unidade de memória extra com uma tabela de conversão de opcode em nível de assembly para
endereço da memória de microprogra da UC (a jump table). Quando uma nova instrução de
assembly é recuperada num fetch, ela é armazenada no registrador de instrução e os bits
correspondentes ao opcode são usados para buscar na jump table o endereço na memória da UC
para onde o apontador de microinstruções deve ser desviado.

Como uma observação histórica, o surgimento da idéia de uma UC microprogramada se deu


através de Maurice Wilkes. No paper de 1951 intitulado “The best way to design an automatic
calculating machine”, ele descreve de maneira pioneira a idéia de microprograma, bem como o
próprio termo da forma que é usado hoje, que curiosamente ele fez questão de relatar diversas
vezes, como em [4] e [5]:
O próprio Wilkes foi o primeiro a construir um computador microprogramado: o Electronic
Delay Storage Automatic Calculator 2 (EDSAC2) [6], que foi o sucessor do EDSAC, também
construído pelo Wilkes, e que foi o primeiro computador a ter uma memória de programa.[7]

2. Introdução ao Problema

Para demonstrar o uso de uma memória microprogramada da forma como foi enunciada na
seção 1, este trabalho consiste em utilizar o Escape DLX (ver seção 3) para programar e avaliar dois
programas distintos que implementam um algoritmo de geração de números pseudo-aleatórios.

Ambos os programas consistem em implementações distintas de um mesmo algoritmo: o


gerador congruente linear, que será explicado em maiores detalhes na seção 4.

O primeiro programa é uma implementação em linguagem assembly, utilizando o conjunto


de instruções fornecido por default no Escape DLX.

O segundo programa é uma tentativa de otimização do primeiro, obtida através de uma


implementação em microcódigo. A idéia é que a codificação em microprograma seja mais eficiente
que uma implementação utilizando um conjunto de instruções genérico, e este trabalho se propõe a
implementar e comparar os resultados da execução dos dois códigos.

Em caráter de observação, existem outras maneiras de se gerar uma sequência de números


pseudo-aleatórios. A que escolhemos geralmente é a adotada em implementações deste tipo, mas
também outros métodos podem ser utilizados. Para uma visão mais ampla sobre o assunto,
recomendamos uma visita à [1].

3. Simulador

Neste trabalho, foi utilizado o Escape DLX [3], um simulador baseado na arquitetura RISC
microprogramada proposta por by John L. Hennessy and David A. Patterson. Este simulador é
altamente customizável, sendo possível alterar diversos aspectos de sua arquitetura. Dentre eles,
estão o tamanho da memória, formato do código da instrução, tamanho do microcódigo e etc.
Além disso, o simulador ainda conta com uma arquitetura de pipeline (que não será abordada aqui).

O propósito deste trabalho é implementar uma nova instrução neste simulador. Para isto,
utilizamos o projeto default do simulador como base.

A primeira modificação foi a inclusão de um novo opcode, através da aba “Instruction


Encoding” da janela “Configuration”. Ele ganhou o mnemônico RND (detalhado na seção 4), que é
uma instrução do tipo R e recebe operandos na forma “R1, R2, R3”.

Depois disso, alteramos a jump table. Isto foi necessário para que, quando a UC decodificar
a instrução RND, saiba para qual endereço do microcódigo deve desviar. Por isso, inserimos o
opcode RND e o rótulo RND (que recebeu o mesmo nome por questões de legibilidade) na Jump
Table 1.

O simulador também dá a possibilidade de se adicionar registradores temporários . Neste


trabalho foram adicionados 6 registradores temporários, a saber: CA (Constante A), CC (Constante
C), AT (Valor atual), P (Ponteiro), T (Temporário) e CO (Contador).
Também tivemos que mudar o tamanho do campo de constante da microinstrução. Isto foi
necessário pois o algoritmo utiliza constantes de 32 bits. Para não alterar o tamanho do campo de 12
bits (valor default) para 32 bits (que numa possível implementação em hardware traria uma maior
complexidade), resolvemos aumentar o campo para somente 16 bits e, na execução do microcódigo,
concatenar 2 valores de 16 bits utilizando a ALU, para formar a constante de 32 bits.

Por fim, escrevemos o microprograma e o inserimos no rótulo RND do microcódigo (a partir


da primeira posição não utilizada pelo conjunto de instruções default). Com isso, a nova instrução
está implementada e pronta para ser utilizada.

4. Apresentação do algoritmo

Como já foi dito anteriormente, o método do gerador congruente linear [2] é o que usaremos
na implementação deste trabalho.

Os números são calculados através da seguinte expressão:

Onde:

Sendo Xn um número da sequência de valores pseudo-aleatórios gerados, X0 o seed ou valor


inicial e a, c e m constantes. Utilizaremos os seguintes valores para as contantes, sugeridos segundo
[2] como:

m = 2^32
a = 1664525
c = 1013904223

O código Java da Listagem 1 é uma implementação inicial do gerador Congruente Linear.


Ele foi usado como base para a elaboração do código assembly e, posteriormente, do
microprograma.
public class GeradorCongruenteLinear {

public static void main(String[] args) {

int n = 100
long r[] = new long[n+1];
r[0] = 0x5EED;
long m = 4294967296L;
long a = 1664525;
long c = 1013904223;

for (int i = 0; i < n; i++) {


r[i+1] = (a * r[i]) % m;
}

for (int i = 1; i < r.length; i++) {


if ( ((i-1) % 4) == 0 ) System.out.println();
System.out.print(String.format("%08X", r[i]) + " ");
}
}
}

Listagem 1 – Gerador Congruente Linear em Java.

O programa foi codificado em assembly do DLX da seguinte maneira:

0000: 44010003 | | ADDI R0, 0x0064, R1


0004: 34421000 | | ADDI R0, 0x5EED, R2
0008: 44030064 | | ADDI R0, 0x0064, R3
000C: 4404004C | | ADDI R0, 0x004C, R4
0010: 0C8C0000 | | LDW R12, 0x0000(R4)
0014: 0C850004 | | LDW R5, 0x0004(R4)
0018: 1C403000 | | ADD R2, R0, R6
001C: 34E73800 | | XOR R7, R7, R7
0020: 1C204000 | | ADD R1, R0, R8
0024: 84080020 | prox | BRLE R8, halt
0028: 24CC4800 | | MUL R6, R12, R9
002C: 1D255000 | | ADD R9, R5, R10
0030: 1CE35800 | | ADD R7, R3, R11
0034: 196A0000 | | STW R10, 0x0000(R11)
0038: 44E70004 | | ADDI R7, 0x0004, R7
003C: 1D403000 | | ADD R10, R0, R6
0040: 49080001 | | SUBI R8, 0x0001, R8
0044: 7000FFDC | | BRZ R0, prox
0048: 7000FFFC | halt | BRZ R0, halt
004C: 0019660D | |
0050: 3C6EF35F | |

Listagem 2 – Gerador Congruente Linear em assembly do Escape DLX.


Onde:

- R1 guarda o número de pseudo-aleatórios que devem ser gerados


- R2 guarda o valor do seed
- R3 guarda a posição de memória onde será armazenado o primeiro número pseudo-
aleatório gerado (R3+4 conterá o segundo, e assim sucessivamente)
As constantes estão a e c estão nas posições de memória 004Ch e 00050h respectivamente.

Observe que não é necessaria a operação de Mod m enunciada na fórmula, visto que m=2^32
e a palavra de dados do Escape DLX é de 32 bits; consequentemente, a máquina truncará os bits
excedentes e preservará os 32 bits menos significativos.

Dado o código assembly implementado acima, podemos então iniciar a codificação do


segundo programa, que fará uso da nova instrução RND já mencionada anteriormente, que possui a
seguinte assinatura:

RND Ri, Rj, Rk

- Ri guarda o número de pseudo-aleatórios que devem ser gerados


- Rj guarda o valor do seed
- Rk guarda a posição de memória onde será armazenado o primeiro número pseudo-
aleatório gerado (Rk+4 conterá o segundo, e assim sucessivamente)

Segue abaixo a codificação do programa 2, que utiliza a nova instrução RND e gera 100
números a partir da posição de memória 0x20. O seed utilizado tem o valor 0x5EED.

0000: 44010064 | | ADDI R0, 0x0064, R1


0004: 44025EED | | ADDI R0, 0x5EED, R2
0008: 44030020 | | ADDI R0, 0x0020, R3
000C: 88221800 | | RND R1, R2, R3
0010: 7000FFFC | halt | BRZ R0, halt

Listagem 3 – Chamada para a nova instruçao do Gerador Congruente Linear.

A figura 3 (esquerda) é o dump da memória após a execução do código acima. Ele pode ser
comparado com a saída do mesmo algoritmo programado em alto nível (mesma figura, direita) pelo
código em Java da Listagem 1.

O microprograma recupera as constantes a e c e as acumula nos registradores temporários


CA E CC, respectivamente. Isto é feito concatenando constantes de 16 bits contidas nas
microinstruções, usando a operação S2S1 da ALU. Isto poderia ser feito utilizando um único campo
de constante de 32 bits, que simplificaria o microcódigo. No entanto, isto tornaria a implementação
da máquina mais complexa e custosa, como já foi observado na seção 3.

Continuando, o valor de seed é movido para o registrador AT para ser multiplicado por a e
somado a c. O registrador CO é utilizado como contador do loop (recebe N passado como
parâmetro da instrução) e P (ponteiro) auxilia no cálculo do endereço de memória onde será
armazenado o número gerado.

A última microinstrução desvia incodicionalmente para o label RNDprx e loop continua


sendo executado até que CO seja igual a 0.
Figura 3 – Saídas do algoritmo executado no Escape DLX (esquerda) e na máquina virtual Java
(direita).
Segue o microcódigo da instrução:
M
e
uAR Label ALU S1 S2 Dest ExtIR Const JCond Adr m MAdr MDest Regs

0034 RND S1 Const CA 25

0035 S1 Const T 26125

0036 S2S1 T CA CA

0037 S1 Const CC 15470

0038 S1 Const T 62303

0039 S2S1 T CC CC RAF2

003A S1 A AT

003B S1 Const P 0 RAF1

003C S1 A CO

003D RNDprx SUB CO Const 0 LE Fetch

003E MUL CA AT AT

003F ADD CC AT MDR RAF3

0040 ADD A P MAR

0041 Espera S1 MDR AT Mbusy Espera WW MAR

0042 SUB CO Const CO 1

0043 ADD P Const P 4

0044 True RNDprx

Listagem 4 – Microcódigo do Gerador Congruente Linear (instrução RND).


5. Resultados

Para executar os testes, optamos por calcular analiticamente o tempo de execução dos
programas para poder utilizar a opção de execução de múltiplos ciclos do simulador. Para isso,
tivemos que contar o tempo de execução de cada instrução e a partir disto calcular o valor de
execução total do programa.

Considerando Tm como o tempo de acesso à memória em ciclos, a Tabela 1 descreve os


tempos de execução.

Operação Tempo de Execução (ciclos)


Fetch Tm + 1
ADDI 2
RND 10 + N * (Tm + 7)
XOR 2
LDW 3 + Tm
ADD 2
BRE Not-Taken 1
BRE Taken 2
MUL 2
STW 3 + Tm
SUBI 2
BRZ Taken 2
Tabela 1: Tempos de execução.

Tempo total de execuxão do programa com a instrução RND :

= 4 Fetch + 3 ADDI + 1 RND

= 4 Tm + 20 + (Tm + 7) * N

Tempo total de execuxão do programa sem a instrução RND :

= 10 Fetch + 3ADDI + 2 ADD + 2 XOR + 2 LDW + 1 BRE Taken + ( 9 Fetch + 1


BRE Not-Taken + 1 MUL + 3 ADD + 1 ADDI + 1 STW + 1 SUBI + 1 BRZ ) * N

= 12 Tm + 32 + (10 Tm + 27) * N

Com isso, pudemos considerar 2 cenários para o tempo de acesso à memória: 9 ciclos
(testado no simulador) e 200 ciclos (hipotético).
Tm = 9 ciclos Tm = 200 ciclos
140000 2500000
Tempo de Execução (Ciclos)

Tempo de Exec uç ão (cic los)


120000
2000000
100000
80000 1500000
Sem RND Sem RND
60000 Usando RND Usando RND
1000000
40000
20000 500000

0 0
0 500 1000 1500 0 500 1000 1500
Números Gerados Números Gerados

Figura 4: Comparação entre cenários.

Como podemos observar, o uso da instrução RND implementada em microcódigo fez com
que o tempo de execução diminuísse consideravelmente. Isso se deveu principalmente à economia
com o tempo de fetch de cada instrução (pois a instrução RND faz o papel de diversas instruções no
programa de código em assembly, reduzindo consideravelmente o número de instruções a serem
buscadas na memória), como também ao fato de as constantes utilizadas pelo algoritmo não
gerarem fetchs adicionais, por já estarem embutidas na memória da UC.

6. Referências

[1] - http://www.random.org, acessado em 07/Maio/2009.


[2] - “Numerical Recipes: the art of scientific computing”, 3a ed., p.341 – Press, William H. et al,
2007.
[3] - Escape DLX: http://trappist.elis.ugent.be/escape, acessado em 07/Maio/2009.
[4] - Wilkes, M. V. “The Growth of Interest in Microprogramming: A Literature Survey”, “ACM
Computing Surveys (CSUR)” Volume 1, Issue 3, 1969, p.139
[5] - Wilkes, M. V. “Microprogramming”, “Papers and discussions presented at the December 3-5,
1958, eastern joint computer conference: Modern computers: objectives, designs, applications”,
1958, p.18
[6] - Wilkes, M. V. “IEEE Annals of the History of Computing”, Volume 14 , Issue 4 (October
1992), p.49-56 - http://portal.acm.org/citation.cfm?id=612476
[7] - Wilkes, M. V. “Papers and discussions presented at the Dec. 10-12, 1951, joint AIEE-IRE
computer conference: Review of electronic digital computers”, 1951, p.79-83 -
http://portal.acm.org/citation.cfm?id=1434770.1434783

Figura 1 retirada de Eckert, Richard R. “Micro-Programmed Versus Hardwired Control


Units: How Computers Really Work”, 1988, p.13-22. Disponível no endereço
http://www.cs.binghamton.edu/~reckert/hardwire3new.html, acessado em 07/Maio/2009.

Vous aimerez peut-être aussi