Linguagem de Montagem

Propaganda
Organização de Computadores
Antônio Borges / Gabriel P. Silva
5. A Linguagem de Montagem do DLX
“Um mercador dispunha de oito pérolas iguais, sendo que sete tinham o
mesmo peso; a oitava, entretanto, era um pouco mais leve que as outras.
Como poderia o mercador descobrir a pérola mais leve e indicá-la, usando a
balança apenas duas vezes?”
Malba Tahan
O Homem que Calculava
5.1. Introdução
Programa em Linguagem de Alto Nível
Compilador
Programa em Linguagem de Montagem
Montador
Objeto: Programa em
Linguagem de Máquina
Objeto: Rotinas da Biblioteca
(em linguagem de máquina)
Ligador
Programa Executável
Carregador
Memória
Figura 8 – Diagrama de Compilação
Organização de Computadores
Antônio Borges / Gabriel P. Silva
Antes de estudarmos o conjunto de instruções do DLX com mais profundidade vamos apresentar
todo o processo de conversão da linguagem de alto nível, utilizada pelos programadores, até a
execução do programa equivalente em linguagem de máquina, ou seja, aquela que é entendida
pelo computador.
Um programa compilador utiliza vários passos para converter um arquivo escrito em linguagem de
alto nível para linguagem de máquina. O processo se dá em várias etapas para que esta tradução
possa ser mais eficiente e que o compilador possa ser portado de um tipo de computador para
outro, com mais facilidade.
O fluxo de tradução de um programa em linguagem de alto nível até um programa executável é
mostrado na figura acima. Nela, o compilador, que é específico para cada linguagem de alto nível, traduz o programa em linguagem de alto nível para a linguagem de montagem, que é específica para cada tipo de processador.
Em seguida, o programa é traduzido da linguagem de montagem para a linguagem de máquina pelo montador. Normalmente para cada para de arquitetura/sistema operacional, existe apenas um programa montador. O arquivo resultante é chamado de programa objeto.
Contudo, esse programa ainda não está pronto para ser executado. Necessita antes ser acrescido
das rotinas existentes nas bibliotecas da linguagem, como por exemplo, printf. O código dessas
rotinas foi previamente traduzido para linguagem de máquina, sendo guardado em arquivos
chamados de bibliotecas. Esta função é realizada pelo ligador. O resultado é um arquivo chamado de programa executável, também em linguagem de máquina.
Finalmente, o programa executável é copiado para a memória principal pelo carregador (loader), quando pode finalmente ser executado pelo processador.
O processador não entende nada além da linguagem de montagem, em formato binário, e só pode executar um programa depois que este for carregado na memória principal.
Uma instrução em alto nível pode corresponder a uma ou várias instruções em linguagem de
montagem, contudo, uma instrução em linguagem de montagem corresponde, normalmente, a
apenas uma instrução em linguagem de máquina.
Cada arquitetura de processador define um formato específico para as instruções em linguagem
de máquina. Isto implica que, para diferentes arquiteturas de processador, teremos diferentes instruções em linguagem de montagem.
Na página seguinte apresentamos um exemplo em linguagem de alto nível (“C”) e o seu respectivo código em linguagem de montagem para o processador DLX. Ao longo das demais seções
deste capítulo estaremos apresentando exemplos de trechos em linguagem de alto nível e o seu
correspondente em linguagem de montagem, para uma melhor compreensão do funcionamento
de cada instrução.
No exemplo podemos observar, além das instruções em linguagem de montagem que foram geradas, uma série de diretivas para o montador e os rótulos. Os rótulos são uma representação
simbólica para os endereços de memória, ou seja, ao invés de utilizarmos endereços, são utilizados nomes e o montador fica responsável por calcular o endereço real. As diretivas orientam o
montador sobre que posição de memória as instruções e dados irão ocupar depois de carregados
na memória. Servem para definir onde as variáveis ficarão armazenadas e quanto espaço está
reservado para cada uma delas. As diretivas são mostradas com mais detalhes na seção 5.5.
O trecho de código apresentado pode ser executado no simulador WinDLX, que emula a arquitetura DLX, e que pode ser baixado do seguinte endereço internet:
Organização de Computadores
Antônio Borges / Gabriel P. Silva
http://equipe.nce.ufrj.br/gabriel/orgcomp2
As operações de entrada e saída são emuladas pelo simulador WindDLX com a ajuda do sistema
operacional, no caso o Windows. Maiores detalhes sobre a forma de realizar operações de entrada e saída podem ser vistas no tutorial sobre o simulador, ao final desta apostila.
main ()
{
int a,b,c;
a=3;
b=4;
c=a+b;
if (c==7)
printf ("A soma deu certo! \n");
else
printf ("A soma deu errado! \n");
exit(0);
}
;; Compilado por Gabriel
;; Área de Dados
.data
.align 2
string1:
.ascii "A soma deu certo! \n\000"
.align 2
string2:
.asciiz "A soma deu errado! \n"
.align 2
certo:
.word string1
.align 2
errado:
.word string2
valor:
.space 4
;;Área de Código
.text
.align 2
.global Main
main:
addi R2,R0,#3
addi R3,R0,#4
add R4,R2,R3
addi R1,R0,#7
sub R5,R4,R1
bnez R5,else
then:
sw
valor, R0
addi R14,R0,certo
trap #5
j
fim
else:
sw
Valor, R0
addi R14,R0,errado
trap #5
fim:
trap #0
Lembramos que na arquitetura DLX alguns registradores tem uso especial, entre ele podemos
destacar o registrador R0, que sempre retorna o valor 0, o registrador R29, também conhecido
como apontador de pilha (SP – stack pointer), o registrador R30, também conhecido com apontador de quadro (FP – frame pointer), e registrador R31, utilizado para guardar o endereço de retorno nas chamadas de rotinas e procedimentos (RA – return address).
Organização de Computadores
Antônio Borges / Gabriel P. Silva
5.2. Detalhamento das Instruções
5.2.1.
Convertendo de Alto Nível para Linguagem de Montagem
Para apresentar as instruções do DLX, utilizamos pequenos trechos escritos em linguagem
de alto nível (no caso a linguagem “C”), e mostramos o seu correspondente em linguagem
de montagem. Esperamos assim facilitar a compreensão do funcionamento das instruções
em linguagem de montagem e, por conseqüência, do processador.
a) A = 10;
Hipótese: A variável A está armazenada no endereço de memória 100 e armazena 1 byte.
addi
sb
R8, R0, #10
100(R0), R8
;; registrador R8 recebe 10
;; A recebe o conteúdo de R8
;; que é 10
Comentário: O programa equivalente em linguagem de montagem para realizar esta atribuição deve carregar a constante 10 em um registrador temporário. Em seguida o conteúdo do registrador é armazenado na memória pela instrução de sb (store byte). Como a
variável A armazena apenas 1 byte, deve-se utilizar a instrução sb, já que esta transfere
exatamente 1 byte para a memória. O endereço de memória onde a instrução sb vai armazenar este byte é obtido pela soma do operando imediato (no caso 100) como conteúdo do
registrador R0 (que é sempre 0 no DLX).
Não existe uma instrução em linguagem de montagem do DLX que atribua o valor de uma
constante diretamente a uma variável armazenada na memória.
b) A = B + 2;
Hipótese: As variáveis inteiras A e B estão armazenadas nos endereços de memória definidos pelos rótulos A e B. Cada inteiro armazena 4 bytes.
lw
R8, B(R0)
addi
sw
R8, R8, #2
A(R0), R8
;; registrador R8 recebe o
;; conteúdo de B
;; R8 = B + 2
;; A recebe o conteúdo de R8
;; que é B + 2
Comentário: Um rótulo é um símbolo definido pelo programador ou pelo compilador e corresponde a um endereço de memória. Pode então ser utilizado como operando imediato
para, junto com um registrador, referenciar as variáveis na memória com as instruções de
load e store. Depois do conteúdo da variável B ser armazenado no registrador temporário
R8, ele é adicionado à constante especificada (2) e então seu conteúdo é armazenado na
posição de memória correspondente à variável A.
Não existe uma instrução em linguagem de montagem do DLX que faça uma operação aritmética diretamente entre variáveis armazenadas na memória.
c) A = (B + C) – (D + E);
Hipótese: As variáveis inteiras A, B, C, D e E estão armazenadas nos registradores R1,
R2, R3, R4 e R5.
add R8, R2, R3
add R9, R4, R5
sub R1, R8, R9
;; registrador R8 recebe B + C
;; registrador R9 recebe D + E
;; A recebe R8 – R9
;; que é (B+C) – (D+E)
Organização de Computadores
Antônio Borges / Gabriel P. Silva
Comentário: Como as variáveis estão armazenadas em registrador não é necessário que
seja feita sua leitura da memória. Neste caso a solução implica apenas em somar as variáveis armazenadas nos registradores, colocando os resultados intermediários em registradores temporários. Esses valores intermediários (R8 e R9) são finalmente subtraídos e o
resultado é colocado em R1, que armazena a variável A.
d) C = B + A[8];
Hipótese: As variáveis B e C estão armazenadas na memória nos endereços definidos pelos rótulos B e C. O vetor A possui o primeiro elemento armazenado do endereço 100 de
memória. Todas as variáveis armazenam 1 byte cada.
addi
lb
lb
add
sb
R16, R0, #100
R8, 8(R16)
R9, B(R0)
R10, R8, R9
C(R0), R10
;; R16 recebe o endereço de A[0]
;; registrador R8 recebe A[8]
;; registrador R9 recebe B
;; R10 = B + A[8]
;; C = R10
Comentário: A maior dificuldade neste caso consiste no cálculo do endereço de memória
do elemento A[8]. Para isto é necessário saber a posição do primeiro elemento (A[0]) e o
tamanho de cada elemento do vetor. Como cada elemento possui apenas 1 byte, basta
apenas somar 8 (8x1) ao endereço do elemento A[0] para obter o endereço de A[8]. A instrução lb permite que esta soma seja feita automaticamente, não sendo necessária nenhuma operação adicional para este cálculo. O conteúdo de A[8] é copiado para um
registrador temporário (R8), que é então somado à variável B (R9) e o resultado (R10) é
guardado na variável C.
e) A[12] = B + A[8];
Hipótese: A variável B está armazenada na memória. O vetor A tem o seu endereço inicial
definido pelo rótulo A. Todas as variáveis armazenam 4 bytes.
addi
lw
lw
add
sw
R16, R0, A
R8, 32(R16)
R9, B(R0)
R8, R9, R8
48(R16), R8
;; R16 = endereço de A[0]
;; R8 = A[8]
;; R9 = B
;; R8 = B + A[8]
;; A[12] = R8
Comentário: Este exemplo é similar ao anterior, sendo necessário o uso de uma instrução adicional sw para armazenar o resultado no elemento A[12]. No cálculo do endereço
de A[8] e A[12] deve-se levar em conta que cada elemento contém 4 bytes, logo deve-se
multiplicar cada índice por 4 para determinar as constantes a serem utilizadas nas instruções lw e sw.
f) C = B + A[i];
Hipótese: O vetor A e as variáveis B, C estão armazenadas em memória. A variável i está
armazenada em R1. Todas as variáveis armazenam 4 bytes.
Organização de Computadores
addi
add
add
add
lw
lw
add
sw
R16, R0, A
R9, R1, R1
R9, R9, R9
R17, R16, R9
R8, 0(R17)
R7, B(R0)
R9, R8, R7
C(R0), R9
Antônio Borges / Gabriel P. Silva
;; R16 = endereço de A[0]
;; R9 = i + i
;; R9 = 2i + 2i (R9 = 4i)
;; R17 = endereço de A[i]
;; R8 = A[i]
;; R7 = B
;; R9 = A[i] + B
;; C = R9
Comentário: Neste exemplo a maior dificuldade é o cálculo do endereço de memória do
elemento A[i]. Como i é uma variável, cujo valor a princípio desconhecemos, deve-se multiplicar o seu valor por 4 e então somar ao endereço inicial do vetor A. As quatro primeiras
instruções fazem esse cálculo. A instrução lw que carrega o valor de A[i] no registrador deve ser utilizada com a constante 0 pois a arquitetura DLX não possui modo de endereçamento indexado, como iremos ver na seção 5.4, ítem d.
g)
if (A = = B)
{
A = B + C;
}
D = D - B;
Hipótese: As variáveis de A até D estão armazenadas nos registradores de R1 até R4,
respectivamente.
L1:
sub
bnez
add
sub
R8, R1, R2
R8, L1
R1, R2, R3
R4, R4, R2
;; R8 = A - B
;; Se R8 ! = 0 então PC = L1
;; Se A = = B executa
;; D = D – B (é sempre executado)
Comentário: Este caso é o de um “if” simples, onde a sentença guardada (dentro do if) só
será executada se a condição for verdadeira. Se A e B forem iguais o resultado (R8) será
zero. O teste do desvio deve verificar se o conteúdo de R8 é diferente de zero, quando a
instrução guardada deverá ser pulada. A última sentença do programa em alto nível (D = D
– B) deverá ser sempre executada.
h)
if (A = = B)
D = B + C;
else
D = C – B;
Hipótese: a mesma do exemplo anterior
ELSE:
FIM:
seq
beqz
add
j
sub
nop
R8, R1, R2
R8, ELSE
R4, R2, R3
FIM
R4, R3, R2
;; Se A == B então R8 = 1
;; desvia para ELSE se A ! = B
;; não executa se A ! = B
;; tem que pular o ELSE
;; não executa se A = = B
Organização de Computadores
Antônio Borges / Gabriel P. Silva
Comentário: Este caso é o de um “if” completo, com os dois ramos: “then” e “else”. Nesta
situação, caso um ramo seja executado, o outro não poderá ser executado. Para a comparação foi utilizada a instrução seq do DLX, que coloca em 1 o registrador destino se a condição for verdadeira. No final do trecho de código correspondente ao “then” deve haver
uma instrução de desvio incondicional (j) para que o trecho correspondente ao “else” não
seja também executado.
i)
i = 0;
do {
A = A +( B[i]/2);
i = i +1;
} while ( i < 10 );
Hipótese: A variável i está armazenada no registrador R1 e a variável A e o vetor B estão
armazenados em memória. Todas as variáveis armazenam 4 bytes.
LACO:
addi
addi
slli
add
lw
srli
lw
add
sw
addi
slti
bnez
R16, R0, B
R1, R0, R0
R8, R1, #2
R17, R16, R8
R8, 0(R17)
R8, R8, #1
R9, A(R0)
R8, R8, R9
A(R0), R8
R1, R1, #1
R8, R1, #10
R8, LACO
;; R16 = endereço de B[0]
;; i = 0
;; R8 = 4i
;; R17 = endereço de B[i]
;; R8 = B[i]
;; R8 = B[i] /2
;; R9 = A
;; R8 = A + B[i]/2
;; A = R8
;; i = i +1
;; Se R1 < 10 então R8 = 1
;; Se R8 ! = 0 então PC = LACO
Comentário: O comando do testa a condição depois que entrar no laço. Neste exemplo,
antes de se entrar no laço do programa, o endereço inicial do vetor B é carregado em R16
e depois o valor 0 é atribuído à variável i.
Um dos problemas deste exemplo é calcular o endereço do elemento B[i]. Para isso, o valor de valor de i deve ser multiplicado por 4 para ser somado ao endereço armazenado em
R16, de modo a obter-se o endereço de B[i]. O conteúdo de B[i] é carregado em R8, dividido por 2 com uma instrução de deslocamento e somado ao conteúdo da variável A,
que anteriormente foi carregado em R9. Depois o resultado é novamente armazenado na
memória no endereço correspondente à variável A. O índice i é incrementado e verificado
o número total de iterações previstas para o laço (10) foi alcançado.
j)
for ( i = 0, i < 10, i++)
{
A = A + B[i]/2;
}
Hipótese: A variável i está armazenada no registrador R1 e a variável A e o vetor B estão
armazenados em memória. Todas as variáveis armazenam 4 bytes.
Organização de Computadores
LACO:
FIM:
addi
addi
R16, R0, B
R1, R0, R0
slti
beqz
slli
add
lw
srli
lw
add
sw
addi
j
nop
R8, R1, #10
R8, FIM
R8, R1, #2
R17, R16, R8
R8, 0(R17)
R8, R8, #1
R9, A(R0)
R8, R8, R9
A(R0), R8
R1, R1, #1
LACO
Antônio Borges / Gabriel P. Silva
;; R16 = endereço de B[0]
;; i = 0
;; Verifica se é a ultima iteração
;; Se R1 < 10 então R8 = 1
;; Se R8 == 0 então PC= FIM
;; R8 = 4i
;; R17 = endereço de B[i]
;; R8 = B[i]
;; R8 = B[i] /2
;; R9 = A
;; R8 = A + B[i]/2
;; A = R8
;; i = i +1
;; Retorna para o início do laço
;; Saída
Comentário: O comando for testa a condição antes de entrar no laço. Neste exemplo, antes de
se entrar no laço do programa, o endereço inicial do vetor B é carregado em R16 e depois o valor
0 é atribuído à variável i.
Um dos problemas deste exemplo é calcular o endereço do elemento B[i]. Para isso, o valor de
valor de i deve ser multiplicado por 4 para ser somado ao endereço armazenado em R16, de modo a obter-se o endereço de B[i]. O conteúdo de B[i] é carregado em R8, dividido por 2 com uma
instrução de deslocamento e somado ao conteúdo da variável A, que anteriormente foi carregado
em R9. Depois o resultado é novamente armazenado na memória no endereço correspondente à
variável A. O índice i é incrementado e verificado o número total de iterações previstas para o laço
(10) foi alcaçado.
k) A seguir apresentamos o exemplo de uma pequena rotina em “C” que recebe os endereços
iniciais de duas cadeias de caracteres como parâmetros e realiza a cópia entre as cadeias até
encontrar um caractere nulo (0x0):
void copia (char *x, char *y){while (*x != 0)
{
*y = *x;
x++;
y++;
}}
Hipótese: 0s endereços iniciais das cadeias x e y estão em R1 e R2, respectivamente.
copia:
lb
R6, 0(R1)
fim
;; se *x == \0 então termina
laço:
sb
0(R2), R6
addi R1, R1, #1
addi R2, R2, #1
lb
R6, 0(R1)
bnez R6, laço
fim:
jr
R31
;; R1 = *x ( conteúdo da memória )
beqz
R6,
;; *y = R6 ( faz a cópia )
;; x++ ( aponta para a posição seguinte )
;; y++ ( da cadeia de origem e destino )
;; R6 = *x ( lê um novo caractere )
;; desvia se diferente de \0
;; retorna do procedimento
Comentário: O comando while tem um comportamento semelhante ao for. O teste da
condição de término deve ser feito antes da execução de qualquer instrução do laço. Se for
falsa, o laço não será executado. Dado que o endereço inicial dos vetores já está em R1 e
R2, o programa primeiro verifica se a cadeia de origem não está vazia, ou seja, contém
apenas o caractere nulo (\0). Se houver dados para serem copiados, a leitura de um caractere da cadeia de origem é feita para um registrador temporário R6 e depois seu conteúdo
Organização de Computadores
Antônio Borges / Gabriel P. Silva
é armazenado na cadeia de destino. Os respectivos ponteiros são então incrementados,
quando então a condição de saída do laço, que é encontrar um caractere igual a 0x0 na
cadeia de origem, é novamente testada. A última instrução copia o conteúdo do registrador
R31 para o PC e faz o retorno do procedimento. Essa instrução será estudada com maiores detalhes na seção seguinte.
5.2.2.
As Instruções de Desvio em Detalhe
A execução de um programa requer o uso de instruções de desvio para permitir a mudança da execução de um ponto para outro do programa. Isto é feito alterando-se o valor do
apontador de instruções (PC). Existem dois tipos de desvios: condicionais e incondicionais.
Na arquitetura DLX os desvios condicionais (beqz e bnez) verificam o valor de um registrador para saber se é zero ou não. Existem instruções auxiliares (do tipo sCC) que comparam dois registradores e colocam como resultado o valor 1 no registrador destino
quando a condição determinada pela instrução é verdadeira ou valor 0 quando é falsa.
As condições que são testadas pela instrução sCC são as seguintes: maior ou igual (sge),
maior (sgt), menor ou igual (sle), menor (slt), igual (seq) e diferente (sne).
Outros processadores possuem instruções em que a comparação entre dois registradores
é feita pela própria instrução de desvio; outras comparam o valor de um registrador chamado de código de condição. O registrador de código de condição guarda o resultado da
última operação realizada pela ALU, informando se o resultado foi negativo, positivo, igual
a zero, e assim por diante. Qual a forma de implementação que será utilizada vai depender
da escolha feita pelo projetista da arquitetura. O desvio será tomado ou não de acordo com
o resultado da operação de comparação.
Os desvios incondicionais (j) também alteram o valor do apontador de instruções (PC)
mas, como o nome já diz, sem depender de condição alguma.
5.2.3.
As Chamadas de Procedimentos
As rotinas ou procedimentos são trechos de código que podem ser chamados várias vezes
em um programa. Normalmente se utilizam instruções de desvio para mudar a execução
de um ponto para outro do programa. As rotinas, entretanto, não podem ser chamadas
desta maneira, pois ao seu término devem retornar para pontos diferentes, o que não seria
possível apenas com as instruções de desvio.
Existem diversas variações nas soluções propostas para resolver este problema. A maneira encontrada no DLX foi criar duas novas instruções: uma (jal) que guarda em um registrador, antes de realizar o desvio, o endereço da instrução seguinte àquela que chamou a
rotina. Na arquitetura DLX esse endereço é salvo no registrador R31, que recebe o nome
especial de RA (return address). Existe uma outra instrução de desvio indireto (jr) que altera o conteúdo do PC de acordo com o valor que está armazenado em um registrador. Na
realidade esta instrução pode utilizar qualquer registrador mas para o retorno de uma rotina utiliza o registrador R31 como parâmetro.
Assim, ao final da execução do código da rotina, uma instrução de retorno (jr R31) copia o
valor salvo de volta para o PC e a execução das instruções é retomada no endereço seguinte à instrução que chamou a rotina. Com o uso deste mecanismo, a rotina pode ser
chamada de vários pontos do programa principal e o retorno é feito sempre para a instrução seguinte à chamada.
Organização de Computadores
5.2.4.
Antônio Borges / Gabriel P. Silva
A função da Pilha
O mecanismo descrito anteriormente para a chamada de procedimentos não permite que
uma rotina seja chamada a partir de uma outra rotina. O valor do endereço de retorno seria
perdido quando da execução de uma nova instrução de chamada de rotina. Para resolver
este problema utiliza-se salvar o valor do endereço de retorno (R31) na memória, antes de
chamarmos uma nova rotina, em uma estrutura de dados chamada pilha. Outros valores
que são guardados na pilha são os parâmetros adicionais para a rotina, que não cabem
nos registradores.
A principal característica da pilha é que os elementos colocados por último são os primeiros a serem retirados. É uma estrutura que cresce do final da memória para o começo.
Existe um registrador reservado para esta função do DLX chamado de apontador de pilha
(sp), que indica o endereço do último elemento no “topo” da pilha.
Antes do uso de qualquer registrador por uma rotina, os registradores antigos, que contenham valores que já possam estar em uso pelo programa, são também salvos na pilha.
Assim, os parâmetros de entrada são colocados na pilha, para serem utilizados pelas instruções na rotina. Antes de os parâmetros serem colocados na pilha, o apontador de pilha
(sp) é decrementado (porque a pilha cresce no sentido inverso aos endereços da memória) com um valor igual ao tamanho total dos parâmetros que serão colocados na pilha.
Algumas arquiteturas possuem instruções especiais que movem os valores para a pilha e,
automaticamente, decrementam o valor do ponteiro de pilha (sp). A arquitetura DLX não
possui instruções desse tipo e utiliza o registrador R29 como ponteiro de pilha.
5.2.5.
As Chamadas ao Sistema Operacional
O Sistema Operacional é um programa que é carregado quando o computador é ligado. A
sua função é gerenciar e compartilhar os recursos do computador (memória, dispositivos
de E/S, etc.) entre os diversos programas em execução (processos) no computador. O
Sistema Operacional tem também uma função de proteção, ou seja, evitar que um
programa de usuário interfira em programas ou arquivos de outro usuário, se não tiver
autorização para isso.
Para que o Sistema Operacional possa realizar estas funções é preciso que existam
funções adequadas no processador. Entre elas, destacamos a existência de dois modos
de execução dos programas: um normal e outro privilegiado. Os programas de usuário
executam em modo normal, isto é, algumas instruções não podem ser executadas, o
acesso aos periféricos é restrito e o programa pode ver apenas a área de memória que foi
reservada pelo Sistema Operacional.
As tarefas mais sensíveis e/ou repetitivas devem ser executadas pelo Sistema
Operacional. Os programas do núcleo do S.O. executam em modo privilegiado, podem
fazer acesso a todos os dispositivos de E/S e a toda memória. A interface com o Sistema
Operacional é feita através de uma instrução especial (chamada trap), que muda o estado
da máquina para privilegiado, mas desvia imediatamente para endereços pré-fixados de
rotinas do núcleo do S.O.. Um ou mais parâmetros podem ser passados em registradores,
de modo a orientar o Sistema Operacional sobre que tarefas são desejadas.
Ao receber esse pedido, o Sistema Operacional verifica se o usuário tem privilégios
adequados para o serviço que solicitou. Por exemplo, se o programa de usuário solicita a
Organização de Computadores
Antônio Borges / Gabriel P. Silva
abertura de um arquivo para escrita, os atributos do arquivo são verificados para saber se
aquele usuário é o dono do arquivo e, caso não seja, se o seu dono permite que outros
usuários possam escrevê-lo. Se as permissões estiverem de acordo, o serviço é então
realizado.
Depois de realizar as tarefas solicitadas, o Sistema Operacional retorna o controle para o
programa de usuário, através de uma instrução de retorno especial, rett, que muda o modo
do processador para o estado normal ao ser executada. A próxima instrução a ser
executada nesse retorno está no endereço seguinte à instrução de trap que fez a chamada
ao S.O..
5.2.6.
Instruções de Ponto Flutuante
Além das instruções que manipulam valores inteiros, os processadores necessitam ter instruções específicas para operar com valores reais. Normalmente esses dados estão no
formato IEEE 754, adotado por quase todos os computadores a partir de 1980. Essas instruções são chamadas de instruções de ponto flutuante.
As instruções de ponto flutuante fazem uso, normalmente, de registradores especiais, denominados de registradores de ponto flutuante, de onde são lidos/escritos os operandos
das instruções.
O principal motivo para a separação das instruções em inteiras e de ponto flutuante, é que
as últimas necessitam de circuitos muito mais complexos para realizarem as operações aritméticas, o que resulta em um tempo muito maior de execução. Como os números inteiros representam uma classe muito grande dos problemas resolvidos por computador,
foram criadas duas classes de instruções, para que as instruções inteiras pudessem ser
executadas mais rapidamente.
5.3. O Formato das Instruções em Linguagem de Máquina
As instruções em linguagem de montagem são traduzidas em instruções em linguagem de
máquina, em um formato binário específico para cada tipo de processador. As instruções
do processador são organizadas de acordo com o especificado pelo projetista, levando em
conta o histórico das máquinas produzidas pelo fabricante.
No DLX, todas as instruções possuem 32 bits de largura com um código de operação de 6
bits. Basicamente há 3 tipos diferentes de instruções, apresentados a seguir de acordo
com a seguinte convenção:
opcode:
rs1:
rs2:
rd:
imediato:
função:
desloc.:
O código da operação (6 bits)
Registrador com o primeiro operando fonte (5 bits)
Registrador com o segundo operando fonte (5 bits)
Registrador que guarda o resultado da operação (5 bits)
Valor utilizado como dado imedifato (16 bits)
Especifica uma variação da instrução básica.
Uma constante de 26 bits para ser adicionada com sinal ao valor atual do PC.
Organização de Computadores
5.3.1.
Antônio Borges / Gabriel P. Silva
Instruções tipo-I:
As instruçõe do tipo I possuem sempre um campo reservado para o dado imediato, que é
uma constante de 16 bits com sinal. Além disso, possuem dois campos de 5 bits cada reservados para o registrador fonte e para o registrador destino da operação. Finalmente,
como todas as instruções do DLX, possuem um campo de 6 bits que codificam a operação
que será realizada pelo processador.
Exemplos:
•
•
•
•
loads e stores: lw, sw, lb e sb.
Instruções com dado imediato: ADDI, SUBI, etc.
Instruções de desvio condicional: BEQZ, BNEZ.
Instruções de desvio indireto: JR.
5.3.2.
Instruções tipo-R
As instruções do tipo R são aquelas que possuem dois registradores como operando fontes (rs1 e rs2) e um registradror operando de escrita (rd). As instruções deste tipo possuem
o campo “opcode” com o valor “0” e a operação que realizam está codificada no campo
“func”, de 11 bits.
Exemplos:
Instruções lógicas e aritméticas de registrador para registrador: ADD, SUB, SLL, etc.
Instruções de comparação: SEQ, SGT, etc.
5.3.3.
Instruções Tipo-J
As instruções de desvio incondicional e de chamada e retorno de procedimento são do tipo
J. Para permitir uma amplitude maior nos saltos realizados, as instruções possuem um
campo de 26 bits para uma constante com sinal que, em tempo de execução, será adicionada ao valor armazenado no PC.
Organização de Computadores
Antônio Borges / Gabriel P. Silva
Exemplos:
Instruções de desvio incondicional: J (jump) e JAL (jump and link)
Instruções de Chamada ao Sistema: TRAP
Instruções de Retorno de Exceção: RFE.
5.3.4.
Exemplos
Algumas instruções utilizam todos os campos, e outras possuem formatos semelhantes, mas
com campos diferentes.
Ex:
add r1, r2, r3
000000
00010
00011
00001
00000001000
and r4, r5, r6
000000
00101
00110
00100
00000010010
addi r8, r0, 10
001000
00000
01000
0000000000001010
lw R1, 10(R5)
001000
00101
00001
0000000000001010
bnez R8, 2048
001000
01000
00000
0000100000000000
j 1024
001000
00000000000000001000000000
5.4. Modos de Endereçamento
As instruções podem obter os operandos necessários a cada operação através de vários modos de endereçamento, conforme descrito a seguir:
a) Endereçamento Imediato: O operando (dado) está codificado na própria instrução.
addi
r0
r8
0x10
Organização de Computadores
Antônio Borges / Gabriel P. Silva
b) Endereçamento de Registrador: O dado está contido no registrador especificado.
add
r4
r2
r3
c) Endereçamento Indireto: O campo da instrução aponta para o registrador que contém o
endereço do dado. Pode ser acompanhado de uma constante de deslocamento ou não,
que é somada ao valor do registrador, para calcular o endereço do dado na memória.
lw
r4
0 (r3)
d) Endereçamento Indexado: Além do registrador contendo o endereço, um outro registrador é utilizado como índice, sendo somado ao valor do registrador, para calcular o endereço de memória. Isto é utilizado, por exemplo, quando queremos endereçar os elementos
de um vetor ou matriz. Este modo de endereçamento não existe no DLX.
lw
r4
r2
(r3)
e) Endereçamento de Relativo ao PC: Neste modo de endereçamento, a instrução utiliza o
valor atual do apontador de instruções, para adicionar ou subtraí-lo de um determinado valor, guardado na própria instrução ou armazenado em um registrador.
j
deslocamento
f) Endereçamento de Pilha: Uma pilha consiste em itens de dados armazenados em ordem
consecutiva na memória, que normalmente cresce do “final” da memória para o início. O
primeiro item é denominado “fundo da pilha” e o último “topo da pilha”. São permitidas apenas operações de “pop” e “push” para retirar ou colocar dados da pilha, respectivamente. Este modo de endereçamento não existe no DLX.
push
R3
5.5. Diretivas para o montador
As diretivas são comandos que auxiliam o montador organizar os dados e instruções na memória
de forma a obter-se uma execução adequada do programa em linguagem de montagem. Para o
montador que estamos utilizando as seguintes diretivas são válidas:
Organização de Computadores
Antônio Borges / Gabriel P. Silva
n.
.align n
Alinha o dado ou instrução seguinte em uma fronteira de 2
.ascii str
Armazena a cadeia str na memória.
.asciiz str
Armazena a cadeia str na memória e termina com “null” (0x0).
.data <addr>
Indica que os itens a seguir devem ser armazenados na área
de memória reservada para os dados a partir do endereço indicado.
.text <addr>
Indica que itens a seguir devem ser armazenados na área de
memória reservada para as instruções a partir do endereço indicado.
.globl sym
Declara que o rótulo sym é global, podendo ser referenciado em
em outros arquivos.
.byte b1, ..., bn
Armazena os bytes listados seqüencialmente na memória.
.word w1, ..., wn
Armazena as n quantidades de 32 bits em posições
sucessivas de memória.
Armazena os valores listados seqüencialmente na memória como
número de ponto flutuante de precisão dupla.
.double n1,..., nn
.space size
Reserva um espaço na memória equivalente em bytes ao parâmetro size.
Organização de Computadores
Antônio Borges / Gabriel P. Silva
5.6. Tabela de Instruções
Instrução
Formato
Add
Add Immediate
And
And Immediate
Subtract
Sub Immediate
Or
Or Immediate
Xor
Xor Immediate
Branch on Equal
Zero
Branch on Not
Equal Zero
Jump
Jump and Link
R
I
R
I
R
I
R
I
R
I
I
add
addi
and
andi
sub
subi
or
ori
xor
xori
beqz
Exemplo
R1, R2, R3
R1, R2, #100
R1, R2, r3
R1, R2, #100
R1, R2, R3
R1, R2, #100
R1, R2, R3
R1, R2, #100
R1, R2, R3
R1, R2, #100
R1, desvio
Significado
R1 = R2 + R3
R1 = R2 + 100
R1 = R2 AND R3
R1 = R2 AND 100
R1 = R2 - R3
R1 = R2 - 100
R1 = R2 OR R3
R1 = R2 OR 100
R1 = R2 XOR R3
R1 = R2 XOR 100
Se (R1 == 0) faça PC = desvio
senão PC = PC+ 4
Se (R1 != 0) faça PC = desvio
senão PC = PC + 4
PC = desvio (desvia para desvio)
R31 = PC + 4; PC = desvio
I
bnez
R1, desvio
J
J
j
jal
desvio
desvio
Jump Register
I
jr
R31
Jump and Link Register
Set If Equal
I
jalr
R15
PC = R31 (desvia para o endereço em R31)
(retorno de procedimento)
R31 = PC+ 4; PC = R15
R
seq
R1, R2, R3
Se (R2 == R3)
I
seqi
R1, R2, #10
R
sle
R1, R2, R3
I
slei
R1, R2, #10
R
slt
R1, R2, R3
(chamada de procedimento)
Set if Equal to Immediate
Set if less than or
equal
Set if Less than or
Equal to Immediate
Set if Less than
(chamada de procedimento em R15)
Set if less than immediate
Load word
Store word
I
slti
R1, R2, #10
I
I
lw
sw
R1, 100(R2)
100(R2), R1
Load byte
Store byte
I
I
lb
sb
R1, 200(R2)
200(R2), R1
Load High
Shift Left Log.
Shift Left Log. Imm.
Shift right arithmetic
I
R
I
R
lhi
sll
slli
sra
R1, #1024
R1, R2, R3
R1, R2, #2
R1, R2, R3
Shift Right Arit. Imm
Sift Right Logical
I
R
srai
srl
R1, R2, #5
R1, R2, R3
Shift Right Log. Imm
Trap
I
srli
trap
R1, R2, # 4
#5
então R1 = 1
senão R1 = 0
Se (R2 == 10)
então R1 = 1
senão R1 = 0
Se (R2 <= 10)
então R1 = 1
senão R1 = 0
Se (R2 <= 10)
então R1 = 1
senão R1 = 0
Se (R2 < 10)
então R1 = 1
senão R1 = 0
Se (R2 < 10)
então R1 = 1
senão R1 = 0
R1 = Memória [R2+ 100] ( lê 4 bytes)
Memória [R2+ 100] = R1
(escreve 4 bytes)
R1 = Memória [R2+ 200] (lê 1 byte)
Memória [R2+ 200] = R1
(escreve 1 byte)
R1 = 1024 << 16
R1 = R2 << R3 (R1 = R2 * 2 ** R3)
R1 = R2 << 2 (R1 = R2 * 4)
R1 = R2 >> R3 (R1 = R2 / (2 ** R3))
(o sinal é replicado à esquerda)
R1 = R2 >> 4 (R1 = R2 / 32) (idem)
R1 = R2 >> R3 (R1 = R2 / (2**R3))
( 0´s são colocados a esquerda)
R1 = R2 >> 4 (R1 = R2 / 16) (idem)
Desvia para o sistema operacional.
Tabela 3 – Instruções do DLX
Download