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
← r
i
,
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
r
i 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