O Computador CM16/22
Esta página descreve a arquitetura do computador CM16/22, uma máquina hipotética
usada como exemplo durante o oferecimento da discipina Computação I (CCM0118)
para a turma 22 do Curso de Ciências Moleculares da USP. Embora o CM16/22 seja
um computador totalmente imaginário, sua arquitetura é muito semelhante à dos
microcomputadores
da década de 1980. Por ser bem mais simples que os computadores atuais, mas
possuir as características essenciais dos microprocessadores empregados nas
máquinas atuais, o CM16/22 ilustra de modo realista os conceitos da
arquitetura de
Von Neumann.
Registradores internos e memória principal
O CM16/22 é um computador de 16 bits. Sua CPU tem 8 registradores de propósito
geral, denominados r0, r1, ..., r7,
cada um deles com 16 bits. No contexto do CM16/22, o termo
palavra (word)
denota um agrupamento de 16 bits, isto é, uma sequência cujos elementos são
dígitos binários e cujo comprimento é 16. Se considerarmos uma palavra como
um número inteiro não negativo, a palavra é um valor hexadecimal no intervalo de
0000 a FFFF. Com essa terminologia, podemos dizer que cada um dos registradores
r0, r1, ..., r7 contém uma palavra.
Assim como a quase totalidade das máquinas atuais, o CM16/22 tem sua memória
principal dividida em células de 8 bits (1 célula = 1 byte). Cada célula da
memória tem um endereço e um conteúdo. O conteúdo é o byte armazenado na célula.
O endereço é um número inteiro não negativo, representável por uma palavra de
16 bits. Como os endereços têm 16 bits, o número máximo de células é
216 =
26 * 210 =
64 * 1024 =
65536. Em outras palavras, a memória principal do CM16/22 tem no máximo
64 Kbytes. Vamos supor que esse máximo é atingido: trabalharemos com uma
máquina imaginária com 64 Kbytes de memória, com endereços no intervalo
de 0000 a FFFF.
Além dos oito registradores de propósito geral, o CM16/22 tem
- três registradores de instrução (instruction registers)
ir0, ir1 e ir2, cada um deles com
8 bits,
- um registrador
ip (instruction pointer ou
apontador de instruções) de 16 bits, e
- um registrador de flags, com 16 bits.
A cada instante os registradores de instrução contêm a instrução que está sendo
executada pela CPU naquele instante. Como o programa em execução reside na
memória principal, o que os registradores de instrução contêm é uma cópia de
uma das instruções residentes na memória principal — a instrução que a CPU
estiver executando no momento. Já o apontador de instruções contém o endereço da
próxima instrução a ser executada. Finalmente, o registrador de flags guarda
informações sobre o resultado de uma operação aritmética ou lógica efetuada
previamente. A versão atual do CM16/22 usa apenas dois dos 16 bits do
registrador de flags: o bit na extremidade direita desse registrador, denominado
zf (zero flag), e o bit imediatamente anterior (o
penúltimo da esquerda para a direita), denominado
cf (carry flag). Esses bits têm o seguinte
significado:
- O
zf indica se o resultado da última operação aritmética ou
lógica executada pela CPU foi igual a zero (nesse caso o valor do
zf é 1) ou foi diferente de zero (nesse caso o valor do
zf é 0).
- O
cf indica se ocorreu "vai um" (carry) saindo do
bit mais significativo do resultado da última operação aritmética
executada pela CPU. Se houve "vai um", o valor do cf é 1,
senão o valor do cf é 0.
Formato das instruções
As instruções do CM16/22 têm comprimento variável. Há instruções de 1 byte,
instruções de 2 bytes e instruções de 3 bytes. Os dois bits mais
significativos (mais à esquerda) do primeiro byte de uma instrução
determinam o número de bytes e o formato da instrução. Há quatro formatos,
descritos a seguir. Esses formatos comportam instruções com zero, um ou dois
parâmetros. O CM16/22 não tem instruções com três ou mais parâmetros. Uma
operação como "some os valores de x e y e guarde o resultado
em z" (z ← x + y),
que precisaria dos três parâmetros x, y e z, não
existe no conjunto de instruções do CM16/22. O que há são instruções como
"some os conteúdos dos registradores ri e
rj e guarde o resultado em
ri" (ri ← ri + rj),
que toma apenas dois parâmetros — os registradores
ri e rj.
- Formato 1: instruções de 1 byte.
Esse formato é usado pelas instruções que não necessitam de parâmetros e pelas
que tomam como único parâmetro um registrador
ri.
Exemplo de instrução que não tem parâmetros: a instrução que finaliza o programa
em execução no CM16/22. Exemplo de instrução que tem como parâmetro um
registrador ri: a instrução
ri ← ri,
que complementa todos os bits do registrador ri (troca
os 1s por 0s e vice-versa).
Os bits fmt indicam que a instrução tem formato 1. O campo
op3 especifica uma das instruções com formato 1. (O valor
op3 = 111 tem um significado especial, que será descrito
logo abaixo.) Caso a instrução tome como parâmetro um registrador
ri, o campo i indica qual é esse
registrador. Note que os 3 bits do campo i comportam
23 = 8 valores de 000 a 111, que
correspondem aos registradores r0, ... r7. Como o
campo op3 é de 3 bits, poderiam existir no máximo
23 = 8 instruções com formato 1 que têm um
registrador ri como parâmetro. Já que certas instruções
não necessitam de parâmetro algum, para todas essas instruções usamos
op3 = 111 e empregamos o campo i para
distinguir as instruções:
Com esse arranjo, podemos ter até 15 instruções de um byte:
23 - 1 = 7 instruções que têm um registrador
ri como parâmetro e
23 = 8 instruções sem nenhum parâmetro.
- Formato 2: instruções de 2 bytes.
Esse formato é usado pelas instruções que tomam como parâmetros dois
registradores
ri e rj.
Exemplo: a instrução ri ← ri + rj,
que soma os conteúdos dos registradores ri e
rj e guarda o resultado em
ri.
Os bits fmt indicam que a instrução tem formato 2. O campo
op8 especifica uma das instruções com formato 2. Como esse
campo é de 8 bits, podem existir até
28 = 256 instruções com formato 2.
Os campos i e j indicam quais são os registradores
ri e rj que a instrução usa
como parâmetros.
- Formato 3: instruções de 3 bytes que tomam como parâmetros
um registrador
ri e um valor de 16 bits, que pode
ser um endereço addr ou uma constante numérica
num.
| byte 0 | byte 1 | byte 2 |
|
|
|
|
|
|
addr/num |
Exemplo de instrução com esse formato: a instrução
ri ← mem[addr], que carrega
um registrador com o conteúdo de duas células contíguas (uma palavra) da
memória. Essa instrução copia para o registrador ri os
bytes armazenados nos endereços addr e addr+1. Outro
exemplo: a instrução
ri ← num,
que carrega um registrador com uma constante numérica de 16 bits especificada
como parte da instrução. Essa instrução copia para o registrador
ri a palavra contida no campo num da
própria instrução.
Como nos formatos anteriores, os bits fmt indicam o formato da
instrução, que agora é o formato 3. O campo op3 especifica uma das
instruções com formato 3, o campo i determina o registrador
ri usado como parâmetro e o campo
addr/num contém o valor de 16 bits usado como
parâmetro. Podem existir até 23 = 8
instruções com esse formato, pois o campo op3 é de 3 bits.
- Formato 4: instruções de 3 bytes que tomam como único
parâmetro um valor de 16 bits, que representa um endereço
addr.
| byte 0 | byte 1 | byte 2 |
|
|
|
|
|
|
addr |
Exemplo de instrução com esse formato: a instrução de desvio, que faz a execução
do programa prosseguir na instrução cujo endereço é addr. Na
ausência de desvios, as instruções são executadas na ordem em que elas aparecem
na memória: a próxima instrução a ser executada é aquela que está imediatamente
depois da que a CPU estiver executando no momento. Em outras palavras, a
execução de um programa tende a seguir um fluxo linear. A instrução de desvio
rompe esse fluxo linear.
Novamente os bits fmt indicam o formato da instrução, que agora é o
formato 4. O campo op6 especifica uma das instruções com formato 4
e o campo addr/num contém o endereço usado como
parâmetro. Como o campo op6 é de 6 bits, podem existir até
26 = 64 instruções com formato 4.
Conjunto de instruções
-
halt
- Formato 1, com
op3 = 7 e
instr = 7
- Código (1 byte): 3F
- Operação: Faz a CPU do CM16-22 sair do estado ativo e entrar no
estado "parado". (No estado ativo a CPU fica constantemente
executando instruções. No estado parado ela deixa de executar
instruções.)
-
syscall
- Formato 1, com
op3 = 7 e
instr = 6
- Código (1 byte): 3E
- Operação: Faz uma solicitação de serviço ao sistema operacional
do CM16-22. O serviço solicitado é especificado pelo conteúdo do
registrador
r0. A versão atual do sistema operacional
do CM16-22 provê apenas os dois serviços descritos abaixo.
- Leitura de um número inteiro do teclado: serviço especificado por
r0 = 0. O sistema operacional lê um número
digitado no teclado e o coloca no registrador r1.
- Escrita de um número inteiro na tela: serviço especificado por
r0 = 1. O sistema operacional apresenta
na tela o número que estiver no registrador r1.
-
mov ri,rj (move register to register)
- Formato 2, com
op8 = 0
- Código (2 bytes): O primeiro byte é sempre igual a 40. O segundo
varia com os valores de
i e de j, no
intervalo de 00 a 3F. (A instrução mov r0,r0
corresponde aos bytes 40 00 e a instrução
mov r7,r7 aos bytes 40 3F.)
- Operação:
ri ← rj
-
add ri,rj
- Formato 2, com
op8 = 1
- Código (2 bytes): O primeiro byte é sempre igual a 40. O segundo
varia com os valores de
i e de j, no
intervalo de 40 a 7F. (A instrução add r0,r0
corresponde aos bytes 40 40 e a instrução
add r7,r7 aos bytes 40 7F.)
- Operação:
ri ← ri + rj.
Os flags zf e cf são atualizados da
seguinte maneira:
- Se o resultado da adição for zero, então
zf ← 1. Caso contrário
zf ← 0.
- Se houver vai um saindo do bit mais significativo do resultado,
então
cf ← 1. Caso contrário
cf ← 0.
-
adc ri,rj (add with carry)
- Formato 2, com
op8 = 2
- Código (2 bytes): O primeiro byte é sempre igual a 40. O segundo
varia com os valores de
i e de j, no
intervalo de 80 a BF. (A instrução adc r0,r0
corresponde aos bytes 40 80 e a instrução
adc r7,r7 aos bytes 40 BF.)
- Operação:
ri ← ri + rj + cf.
Os flags zf e cf são atualizados da
seguinte maneira:
- Se o resultado da adição for zero, então
zf ← 1. Caso contrário
zf ← 0.
- Se houver vai um saindo do bit mais significativo do resultado,
então
cf ← 1. Caso contrário
cf ← 0.
-
sub ri,rj
- Formato 2, com
op8 = 3
- Código (2 bytes): O primeiro byte é sempre igual a 40. O segundo
varia com os valores de
i e de j, no
intervalo de C0 a FF. (A instrução sub r0,r0
corresponde aos bytes 40 C0 e a instrução
sub r7,r7 aos bytes 40 FF.)
- Operação:
ri ← ri - rj.
Os flags zf e cf são atualizados da
seguinte maneira:
- Se o resultado da subtração for zero, então
zf ← 1. Caso contrário
zf ← 0.
- Se houver "empresta um" para o bit mais significativo do
resultado, então
cf ← 1. Caso
contrário cf ← 0.
-
sbb ri,rj (subtract with borrow)
- Formato 2, com
op8 = 4
- Código (2 bytes): O primeiro byte é sempre igual a 41. O segundo
varia com os valores de
i e de j, no
intervalo de 00 a 3F. (A instrução sbb r0,r0
corresponde aos bytes 41 00 e a instrução
sbb r7,r7 aos bytes 41 3F.)
- Operação:
ri ← ri - rj - cf.
Os flags zf e cf são atualizados da
seguinte maneira:
- Se o resultado da subtração for zero, então
zf ← 1. Caso contrário
zf ← 0.
- Se houver "empresta um" para o bit mais significativo do
resultado, então
cf ← 1. Caso
contrário cf ← 0.
-
cmp ri,rj (compare)
- Formato 2, com
op8 = 5
- Código (2 bytes): O primeiro byte é sempre igual a 41. O segundo
varia com os valores de
i e de j, no
intervalo de 40 a 7F. (A instrução cmp r0,r0
corresponde aos bytes 41 40 e a instrução
sub r7,r7 aos bytes 41 7F.)
- Operação: Efetua a subtração
ri - rj,
descarta o resultado dessa subtração, e atualiza os flags
zf e cf de seguinte maneira:
- Se o resultado da subtração for zero, então
zf ← 1. Caso contrário
zf ← 0.
- Se houver "empresta um" para o bit mais significativo do
resultado, então
cf ← 1.
Caso contrário cf ← 0. Note que
haverá "empresta um" quando o primeiro
operando (ri), considerado como um
número inteiro sem sinal, for estritamente menor que o segundo
operando (rj), também considerado
como um número inteiro sem sinal. Em outras palavras, após a
execução de
cmp ri,rj
teremos
cf = 1 se
ri < rj,
sendo ri e rj
números inteiros sem sinal.
-
mov ri,[addr] (move memory to register)
- Formato 3, com
op3 = 0
- Código (3 bytes): O primeiro byte varia com o valores de
i, no
intervalo de 80 a 87. Os dois bytes seguintes contém o valor do
endereço addr. (A instrução
mov r0,[1234h] corresponde aos bytes
80 34 12 e a instrução mov r7,[1234h]
aos bytes 87 34 12.)
- Operação:
ri ← mem[addr].
-
mov [addr],ri (move register to memory)
- Formato 3, com
op3 = 1
- Código (3 bytes): O primeiro byte varia com o valores de
i, no
intervalo de 88 a 8F. Os dois bytes seguintes contém o valor do
endereço addr. (A instrução
mov [1234h],r0 corresponde aos bytes
88 34 12 e a instrução mov [1234h],r7
aos bytes 8F 34 12.)
- Operação:
mem[
addr] ← ri.
-
mov ri,num (move immediate value to register)
- Formato 3, com
op3 = 2
- Código (3 bytes): O primeiro byte varia com o valores de
i, no
intervalo de 90 a 97. Os dois bytes seguintes contém o valor da
constante imediata num. (A instrução
mov r0,1234h corresponde aos bytes
90 34 12 e a instrução mov r7,1234h
aos bytes 97 34 12.) O termo "constante imediata" ou
"valor imediato" se refere a um operando cujo valor está num campo
da própria instrução.
- Operação:
ri ← num.
-
jmp addr (desvio incondicional para o endereço addr)
- Formato 4, com
op6 = 0
- Código (3 bytes): O primeiro byte é sempre igual a C0. Os dois bytes
seguintes contém o valor do endereço
addr. (A instrução
jmp 1234h corresponde aos bytes
C0 34 12.)
- Operação:
ip ← addr.
-
jz addr (jump if zero)
- Formato 4, com
op6 = 1
- Código (3 bytes): O primeiro byte é sempre igual a C1. Os dois bytes
seguintes contém o valor do endereço
addr. (A instrução
jz 1234h corresponde aos bytes
C1 34 12.)
- Operação:
Se
zf = 1 então
ip ← addr.
-
jnz addr (jump if not zero)
- Formato 4, com
op6 = 2
- Código (3 bytes): O primeiro byte é sempre igual a C2. Os dois bytes
seguintes contém o valor do endereço
addr. (A instrução
jnz 1234h corresponde aos bytes
C2 34 12.)
- Operação:
Se
zf = 0 então
ip ← addr.
Exemplos de programas para o CM16/22
- "Dados três números inteiros, calcular a soma desses números."
- "Dada uma sequência de números inteiros diferentes de zero, terminada
por zero, calcular a soma dos elementos da sequência."
- "Dado um inteiro não negativo n e dada uma sequência de
n números inteiros, calcular a soma dos elementos da sequência.
(O n não é um elemento da sequência.)"
Examine as listagens desses três programas e observe a correspondência entre
as instruções em linguagem de montagem (a parte central de cada listagem), os
endereços de memória (a coluna da listagem que fica mais à esquerda) e as
instruções na linguagem de máquina (que também aparecem na parte esquerda da
listagem, logo depois dos endereços). Observe também a correspondência entre
as instruções do CM16/22 e os comandos numa linguagem de alto nível, que
foram incluídos como comentários no programa em linguagem de montagem (parte
direita da listagem).
Last modified: Thu Nov 10 02:30:53 BRST 2011
Francisco Reverbel