Você está na página 1de 9

UFRJ – COPPE – PESC Arquitetura de Computadores I – Prof. Felipe França

Alexandre F. S. de Mattos –

109109763

Lúcio Martins de Paiva – Primeiro período de 2009

109109739

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.

e alteração destes processadores um trabalho complexo. Figura 1 - Decodificador de instruções (esquerda) e matriz
e alteração destes processadores um trabalho complexo. Figura 1 - Decodificador de instruções (esquerda) e matriz

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.

consequentemente, altera o estado da máquina como um todo. Figura 2 - A máquina DLX do

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]:

próprio termo da forma que é usado hoje, que curiosamente ele fez questão de relatar diversas

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:

são calculados através da seguinte expressão: Onde: Sendo X n um número da sequência de valores
são calculados através da seguinte expressão: Onde: Sendo X n um número da sequência de valores
são calculados através da seguinte expressão: Onde: Sendo X n um número da sequência de valores
são calculados através da seguinte expressão: Onde: Sendo X n um número da sequência de valores

Sendo X n um número da sequência de valores pseudo-aleatórios gerados, X 0 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 | 0004: 34421000 | 0008: 44030064 |

| ADDI R0, 0x0064, R1 | ADDI R0, 0x5EED, R2 | 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 | 0004: 44025EED | 0008: 44030020 |

| ADDI R0, 0x0064, R1 | ADDI R0, 0x5EED, R2 | 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 multiplica do 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
Figura 3 – Saídas do algoritmo executado no Escape DLX (esquerda) e na máquina virtual

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:

uAR

Label

ALU

S1

S2

Dest

ExtIR

Const

JCond

Adr

 

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)

120000

Tempo de Execução (Ciclos) 120000   Tempo de Execução (ciclos)    
 

Tempo de Execução (ciclos)

 
Tempo de Execução (Ciclos) 120000   Tempo de Execução (ciclos)    
 

2000000

100000

80000

Sem RND

1500000

Sem RND

60000

Usando RND

1000000

Usando RND

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”, 3 a 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.