Um simulador de computador. A princípio, pode parecer incrível, mas ao resolver esse problema você estará construindo seu próprio computador. Não, você não terá 252 C: como programar
que soldar componentes. Na verdade, você usará a técnica poderosa da simulação baseada no software para criar um modelo de software do Simpletron. Você não ficará desapontado. Seu simulador do Simpletron transformará o computador que você está usando em um Simpletron, e você realmente poderá executar, testar e depurar os programas SML que foram escritos no Exercício 7.27. Ao executar seu simulador do Simpletron, ele deverá começar imprimindo:
*** Bem vindo ao Simpletron! ***
*** Favor digitar seu programa, uma instrução ***
*** (ou palavra de dados) por vez. Mostrarei ***
*** o número do local e uma interrogação (?). ***
*** Você, então, deverá digitar a palavra para esse ***
*** local. Digite a sentinela -99999 para ***
*** encerrar a entrada do seu programa. ***
Simule a memória do Simpletron com um array de subscrito único, memory, que contenha 100 elementos. Agora, suponha que o simulador esteja sendo executado. Examinemos o diálogo enquanto digitamos o programa do Exemplo 2 do Exercício 7.27:
00 ? +1009
01 ? +1010
02 ? +2009
03 ? +3110
04 ? +4107
05 ? +1109
06 ? +4300
07 ? +1110
08 ? +4300
09 ? +0000
10 ? +0000
11 ? -99999
*** Carga do programa concluída ***
*** Iniciando execução do programa ***
Agora, o programa SML foi colocado (ou carregado) no array memory. O Simpletron executa o programa SML. Ele começa com a instrução no local 00 e continua a sequência, a menos que seja direcionado para alguma outra parte do programa por uma transferência de controle.
Use a variável accumulator para representar o registrador do acumulador. Use a variável instruction-Counter para registrar o local na memória que contém a instrução que está sendo executada. Use a variável operationCode para indicar a operação que está sendo executada no momento — ou seja, os dois dígitos da esquerda da palavra de instrução. Use a variável operand para indicar o local da memória em que a instrução está operando no momento. Assim, operand são os dois dígitos mais à direita da instrução que está sendo executada
no momento. Não execute instruções diretamente da memória. Em vez disso, transfira a próxima instrução a ser executada da memória para uma variável chamada Register. Depois, ‘recolha’ os dois dígitos da esquerda e coloque-os na variável operationCode, e ‘recolha’ os
dois dígitos da direita e coloque-os em operand. Quando o Simpletron iniciar sua execução, os registradores especiais serão inicializados da seguinte forma:
accumulator +0000
instructionCounter 00
instructionRegister +0000
operationCode 00
Operand 00
Agora, vamos ‘caminhar’ pela execução da primeira instruçãoSML, +1009, no local de memória 00. Isso é chamado ciclo de execução da instrução. O instructionCounter nos diz o local da próxima instrução a ser executada. Buscamos o conteúdo desse local a partir de memory usando a instrução C instructionRegister = memory[ instructionCounter];
O código da operação e o operando são extraídos do registrador de instrução por meio das instruções operationCode = instructionRegister / 100; operand = instructionRegister % 100;
Agora, o Simpletron precisa determinar se o código da operação é realmente uma leitura (ou uma escrita, uma carga e assim por diante). Um switch diferencia entre as doze operações da SML. Na estrutura switch, o comportamento de diversas instruções SML é simulado da seguinte forma (deixamos as outras para o leitor):
ler: scanf( “%d”, &memory[ operand ] );
carregar: accumulator = memory[ operand ];
somar: accumulator += memory[ operand ];
diversas instruções de desvio: Discutiremos em breve.
halt: Essa instrução imprime a mensagem
*** Execução do Simpletron encerrada ***
e depois imprime o nome e o conteúdo de cada registrador,
bem como o conteúdo completo da memória. Essa
listagem normalmente é chamada de dump do computador.
Para ajudá-lo a programar sua função de dump, uma
amostra do formato de dump aparece na Figura 7.33. Um
dump, após a execução do programa Simpletron, mostraria
os valores reais das instruções e os valores dos dados no
momento em que a execução foi encerrada.
Prosseguiremos com a execução da primeira instrução
do nosso programa, a saber, o +1009 no local 00. Conforme
indicamos, a estrutura switch simula isso ao
executar a instrução C
scanf( “%d”, &memory[ operand ] );
O ponto de interrogação (?) deve ser exibido na tela antes
que o scanf seja executado, como modo de pedir a
entrada do usuário. O Simpletron espera que o usuário
digite um valor e depois pressione a tecla Enter. O valor
é, então, lido no local 09.
Nesse ponto, a simulação da primeira instrução foi concluída.
Tudo o que resta é preparar o Simpletron para a
execução da próxima instrução. Como a instrução recém-
-executada não foi uma transferência de controle, precisamos
simplesmente incrementar o registrador do contador
de instrução da seguinte forma:
++instructionCounter;
Isso conclui a execução simulada da primeira instrução.
O processo inteiro (ou seja, o ciclo de execução da instrução)
começa novamente com a busca da próxima instrução
a ser executada.
Agora, consideremos como as instruções de desvio — as
transferências de controle — são simuladas. Tudo o que
precisamos é ajustar o valor no contador de instrução de
forma adequada. Portanto, a instrução de desvio incondicional
(40) é simulada dentro do switch como
instructionCounter = operand;
A instrução condicional de ‘desvio se acumulador é zero’
é simulada como
if ( accumulator == 0 ) {
instructionCounter = operand;
}
Nesse ponto, você deverá implementar seu simulador Simpletron
e executar os programas SML que escreveu no
Exercício 7.27. Você poderá embelezar a SML com outros
recursos e oferecê-los em seu simulador.
Seu simulador deverá verificar os diversos tipos de erros.
Durante a fase de carga do programa, por exemplo, cada
número que o usuário digita na memory do Simpletron
deverá estar no intervalo -9999 a +9999. Seu simulador
deverá usar um loop while para testar se cada número
inserido está nessa faixa e, se não estiver, deverá
continuar
pedindo ao usuário que informe o número
novamente até que um número correto seja digitado.
Durante a fase de execução, seu simulador deverá verificar
vários erros sérios, como tentativas de divisão por zero, tentativas de execução de códigos de operação invá-lidos e estouros do acumulador (ou seja, operações aritméticas que resultam em valores maiores que +9999 ou menores que -9999). Esses erros sérios são chamados erros fatais. Quando um erro fatal é detectado, seu simulador deverá imprimir uma mensagem de erro como:
*** Tentativa de divisão por zero ***
*** Execução do Simpletron encerrada de forma
anormal. ***
e deverá imprimir um dump completo do computador no formato que apresentamos anteriormente. Isso ajudará o usuário a localizar o erro no programa.
Passo 1
No exercício anterior, nos familiarizamos com uma linguagem inventada SML (Simpletron Machine Language).
Nesta tarefa, usaremos a linguagem C para escrever um programa que simulará o computador Simpletron.
A maioria das instruções é fornecida na tarefa, portanto, nos concentraremos nas etapas que não são explicadas em detalhes.
Começaremos incluindo as bibliotecas necessárias e definindo macros que representarão todos os códigos de operação existentes na linguagem SML.
#include "stdio.h"
#include "stdlib.h"
#define READ 10
#define WRITE 11
#define LOAD 20
#define STORE 21
#define ADD 30
#define SUBTRACT 31
#define DIVIDE 32
#define MULTIPLY 33
#define BRANCH 40
#define BRANCHNEG 41
#define BRANCHZERO 42
#define HALT 43
Em seguida, escreveremos três protótipos de função. Discutiremos todos eles mais tarde.
void load(int* memory);
void execute(int* memory, int* accumulator, int* instructionCounter,
int* operationCode, int* operand, int* instructionRegister);
void dump(int* memory, int accumulator, int instructionCounter,
int instructionRegister, int operationCode, int operand);
Passo 2
Na função main(), vamos simular os processos básicos do computador Simpletron.
Primeiro, ele dará as boas-vindas ao usuário e imprimirá as instruções.
Depois disso, vamos definir um array int chamado memory e inicializar todos os seus elementos com 00.
int main()
{
printf("*** Bem vindo ao Simpletron! ***\n"\
"*** Por favor, insira uma instrução de seu programa ***\n"\
"*** (ou palavra de dados) de cada vez. Vou digitar o ***\n"\
"*** número do local e um ponto de interrogação (?). ***\n"\
"*** Você então digita a palavra para esse local. ***\n"\
"*** Digite o sentinela -9999 para parar de digitar ***\n"\
"*** seu programa. ***\n\n");
int memory[100] = { 0 };
A matriz de memória será passada para a função load() para armazenar as instruções que o usuário inserir nela.
Uma mensagem será impressa indicando que o programa foi carregado e a execução começa.
load(memory);
printf("^^^ Carregamento do programa concluído ^^^\n"\
"*** A execução do programa começa ***\n\n");
A última parte da função main() será inicializar todos os registradores do Simpletron para 00.
Esses registradores, juntamente com o array de memória, serão usados em uma chamada à função execute() para executar as instruções inseridas.
Vamos passá-los por referência, para que a função possa alterá-los.
int accumulator = 0;
int instructionCounter = 0;
int operationCode = 0;
int operand = 0;
int instructionRegister = 0;
execute(memory, &accumulator, &instructionCounter, &operationCode,
&operand, &instructionRegister);
}
Passo 3
A função load( ):
Esta função recebe apenas um parâmetro e é o array de memória.
A função usa duas variáveis int adicionais para armazenar as instruções sequencialmente no array.
Se a instrução inserida for diferente de -99999 e tiver mais de quatro dígitos, a consideraremos inválida, pois a linguagem SML consiste em palavras, que são valores de quatro dígitos. Nesse caso, o usuário será solicitado a reinserir uma instrução válida.
Também garantiremos que o número de instruções não exceda a memória da máquina Simpletron, que pode armazenar até 100 palavras/instruções.
void load(int* memory)
{
int count = 0;
int instruction;
do {
do {
printf("%02d ? ", count);
scanf("%d", &instruction);
if (instruction != -99999 &&
(instruction instruction > 9999))
printf("*** Instrução inválida. Repita."\
" ***\n");
} while (instruction != -99999 &&
(instruction instruction > 9999));
if(instruction != -99999)
memory[count++] = instruction;
} while (instruction != -99999 && count
}
A função dump()
Esta função recebe todos os registradores e o array de memória como argumentos.
Exibe os valores armazenados nos registradores e o conteúdo da memória como saída formatada.
Esta função é chamada sempre que a execução é finalizada, normal ou anormalmente. A saída ajuda o usuário a encontrar um erro, se houver, e perceber como o computador Simpletron funciona.
void dump(int* memory, int accumulator, int instructionCounter,
int instructionRegister, int operationCode, int operand)
{
printf("\nREGISTERS:\n");
printf("%-25s%+05d\n", "accumulator", accumulator);
printf("%-28s%02d\n", "instructionCounter", instructionCounter);
printf("%-25s%+05d\n", "instructionRegister", instructionRegister);
printf("%-28s%02d\n", "operationCode", operationCode);
printf("%-28s%02d\n", "operand", operand);
printf("\nMEMORY:\n ");
for (int i = 0; i
printf(" %5d", i);
}
printf("\n");
for (int i = 0; i
if (i % 10 == 0)
printf("%2d", i);
printf(" %+05d", memory[i]);
if (i % 10 == 9)
printf("\n");
}
printf("\n");
}
A função execute()
Esta é a função mais complicada deste programa. Ele recebe todos os registradores e o array memory() como argumentos e todos os valores são passados por referência, para que possam ser alterados.
A função usa um loop do-while para processar todas as instruções inseridas pelo usuário. Esse loop itera desde que o código de operação na instrução não seja igual a 4343, que é o código de operação HALT que encerra a execução.
No início de cada iteração desta função, uma instrução é lida da memória e ela é separada em OperationCode e operando usando os operadores % e /.
void execute(int* memory, int* accumulator, int* instructionCounter,
int* operationCode, int* operand, int* instructionRegister)
{
do {
*instructionRegister = memory[*instructionCounter];
*operationCode = *instructionRegister / 100;
*operand = *instructionRegister % 100;
// the following code that processes all the operations
} while (*operationCode != HALT && *instructionCounter
}
Passo 4
Para processar diferentes operações, usaremos uma instrução switch que recebe o operationCode como argumento.
Se o operationCode for igual à macro READ, um loop do-while é usado para solicitar ao usuário que insira um valor válido (um que possa ser armazenado na memória), que é armazenado na memória na localização do operando.
switch (*operationCode) {
case READ:
do {
printf("? ");
scanf("%d", &memory[*operand]);
if (memory[*operand]
memory[*operand] > 9999) {
printf("*** Invalid value ***\n");
}
} while (memory[*operand]
memory[*operand] > 9999);
break;
Se o operationCode for WRITE, o valor armazenado na localização do operando será exibido.
case WRITE:
printf("%d\n", memory[*operand]);
break;
Se o código da operação for LOAD, um valor armazenado na localização do operando na memória será armazenado no registrador do acumulador.
Se o OperationCode for STORE, um valor armazenado no registrador do acumulador será armazenado na localização do operando da memória.
case LOAD:
*accumulator = memory[*operand];
break;
case STORE:
memory[*operand] =* accumulator;
break;
Agora, escreveremos o código que processará a adição, a subtração, a divisão e a multiplicação. Para cada uma dessas quatro operações, garantiremos que o resultado seja um valor válido e que possa ser armazenado na memória. Se não for um valor válido, encerraremos o programa usando a função exit() e o usuário será notificado sobre a causa do encerramento.
Se o operationCode for ADD retornaremos o resultado da soma do valor armazenado no acumulador e o valor armazenado na localização do operando na memória.
case ADD:
*accumulator += memory[*operand];
if (*accumulator > 9999) {
printf("\n*** Accumulator overflow ***\n");
printf("*** Simpletron execution abnormally"\
" terminated ***\n");
dump(memory, *accumulator, *instructionCounter,
*instructionRegister, *operationCode,
*operand);
exit(1);
}
break;
Se o operationCode for SUBTRACT retornaremos o resultado da subtração do valor armazenado no acumulador e o valor armazenado na localização do operando na memória.
case SUBTRACT:
*accumulator -= memory[*operand];
if (*accumulator
printf("\n*** Accumulator overflow ***\n");
printf("*** Simpletron execution abnormally"\
" terminated ***\n");
dump(memory, *accumulator, *instructionCounter,
*instructionRegister, *operationCode,
*operand);
exit(1);
}
break;
Passo 5
Se o operationCode for DIVIDE retornaremos o resultado da divisão do valor armazenado no acumulador e o valor armazenado na localização do operando na memória.
Para esta operação, garantiremos que o valor armazenado na localização do operando na memória não seja zero, para evitar a divisão por 00. Se for o caso, a execução do Simpletron será finalizada de forma anormal.
case DIVIDE:
if (memory[*operand] == 0) {
printf("\n*** Attempt to divide by zero ***\n");
printf("*** Simpletron execution abnormally"\
" terminated ***\n");
dump(memory, *accumulator, *instructionCounter,
*instructionRegister, *operationCode,
*operand);
exit(1);
}
else {
*accumulator /= memory[*operand];
}
if (*accumulator > 9999 || *accumulator
printf("\n*** Accumulator overflow ***\n");
printf("*** Simpletron execution abnormally"\
" terminated ***\n");
dump(memory, *accumulator, *instructionCounter,
*instructionRegister, *operationCode,
*operand);
exit(1);
}
break;
Se o operationCode for MULTIPLY retornaremos o resultado da multiplicação do valor armazenado no acumulador e o valor armazenado na localização do operando na memória.
case MULTIPLY:
*accumulator *= memory[*operand];
if (*accumulator > 9999 || *accumulator
printf("\n*** Accumulator overflow ***\n");
printf("*** Simpletron execution abnormally"\
" terminated ***\n");
dump(memory, *accumulator, *instructionCounter,
*instructionRegister, *operationCode,
*operand);
exit(1);
}
break;
Agora, escreveremos o código que processará as três operações de ramificação. Essas operações são usadas para loops e instruções de decisão.
Se o operationCode for BRANCH o registrador InstructionCounter será ajustado para o valor do operando. Isso sempre acontecerá, pois não há condições nesta operação de ramificação
Isso significa que a execução irá para a instrução que está armazenada na localização do operando na memória.
case BRANCH:
*instructionCounter = *operand;
break;
Quando o operationCode for igual a BRANCHNEG o registrador InstructionCounter será configurado para o valor do operando somente se o valor armazenado no acumulador for negativo.
Caso contrário, o valor de instruçãoCounter será simplesmente incrementado.
case BRANCHNEG:
if (*accumulator
*instructionCounter = *operand;
}
else
(*instructionCounter)++;
break;
O caso BRANCHZERO é semelhante ao anterior, exceto que a ramificação ocorrerá somente se o valor do acumulador for zero.
case BRANCHZERO:
if (*accumulator == 0) {
*instructionCounter = *operand;
}
else
(*instructionCounter)++;
break;
O código de operação HALT representa o fim da execução do programa. Quando esse código de operação ocorrer, chamaremos a função dump() para exibir todos os dados e o loop será interrompido, o que basicamente significa que a execução foi finalizada.
case HALT:
printf("\n*** Simpletron execution terminated ***\n");
dump(memory, *accumulator, *instructionCounter,
*instructionRegister, *operationCode, *operand);
break;
Por fim, escreveremos o caso padrão para a instrução switch. Esta parte da instrução tratará de todos os códigos de operação que não existem na linguagem SML.
Se tal código de operação for inserido, a mensagem de erro será exibida e a execução será finalizada de forma anormal.
default:
printf("\n*** Invalid opration code! ***\n");
printf("*** Simpletron execution abnormally"\
" terminated ***\n");
dump(memory, *accumulator, *instructionCounter,
*instructionRegister, *operationCode,
*operand);
exit(1);
break;
}
Para a última parte do loop, incrementaremos a variável instruçãoCounter, se a operação mais recente não tiver sido ramificada.
Também garantiremos que o valor de instruçãoCounter seja menor que 100100 porque esse é o tamanho da memória.
Isso é redundante, pois garantimos que o usuário não possa inserir mais de 100.100 instruções, mas manteremos isso para mostrar a outra maneira de lidar com esse possível erro.
if (*operationCode != BRANCH && *operationCode != BRANCHNEG
&& *operationCode != BRANCHZERO) {
(*instructionCounter)++;
}
if (*instructionCounter >= 100) {
printf("\n*** Insufficient memory ***\n");
printf("*** Simpletron execution abnormally"\
" terminated ***\n");
dump(memory, *accumulator, *instructionCounter,
*instructionRegister, *operationCode,
*operand);
exit(1);
}
} while (*operationCode != HALT && *instructionCounter
}
Resposta
O código final!
#include "stdio.h"
#include "stdlib.h"
#define READ 10
#define WRITE 11
#define LOAD 20
#define STORE 21
#define ADD 30
#define SUBTRACT 31
#define DIVIDE 32
#define MULTIPLY 33
#define BRANCH 40
#define BRANCHNEG 41
#define BRANCHZERO 42
#define HALT 43
void load(int* memory);
void execute(int* memory, int* accumulator, int* instructionCounter,
int* operationCode, int* operand, int* instructionRegister);
void dump(int* memory, int accumulator, int instructionCounter,
int instructionRegister, int operationCode, int operand);
int main()
{
printf("*** Welcome to Simpletron! ***\n"\
"*** Please enter your program one instruction ***\n"\
"*** (or data word) at a time. I will type the ***\n"\
"*** location number and a question mark (?). ***\n"\
"*** You then type the word for that location. ***\n"\
"*** Type the sentinel -9999 to stop entering ***\n"\
"*** your program. ***\n\n");
int memory[100] = { 0 };
load(memory);
printf("^^^ Program loading completed ^^^\n"\
"*** Program execution begins ***\n\n");
int accumulator = 0;
int instructionCounter = 0;
int operationCode = 0;
int operand = 0;
int instructionRegister = 0;
execute(memory, &accumulator, &instructionCounter,
&operationCode, &operand, &instructionRegister);
}
void load(int* memory)
{
int count = 0;
int instruction;
do {
do {
printf("%02d ? ", count);
scanf("%d", &instruction);
if (instruction != -99999 &&
(instruction 9999))
printf("*** Invalid instruction. Please repeat."\
" ***\n");
} while (instruction != -99999 &&
(instruction 9999));
if(instruction != -99999)
memory[count++] = instruction;
} while (instruction != -99999 && count
}
void execute(int* memory, int* accumulator, int* instructionCounter,
int* operationCode, int* operand, int* instructionRegister)
{
do {
*instructionRegister = memory[*instructionCounter];
*operationCode = *instructionRegister / 100;
*operand = *instructionRegister % 100;
switch (*operationCode) {
case READ:
do {
printf("? ");
scanf("%d", &memory[*operand]);
if (memory[*operand]
memory[*operand] > 9999) {
printf("*** Invalid value ***\n");
}
} while (memory[*operand]
memory[*operand] > 9999);
break;
case WRITE:
printf("%d\n", memory[*operand]);
break;
case LOAD:
*accumulator = memory[*operand];
break;
case STORE:
memory[*operand] =* accumulator;
break;
case ADD:
*accumulator += memory[*operand];
if (*accumulator > 9999) {
printf("\n*** Accumulator overflow ***\n");
printf("*** Simpletron execution abnormally"\
" terminated ***\n");
dump(memory, *accumulator, *instructionCounter,
*instructionRegister, *operationCode,
*operand);
exit(1);
}
break;
case SUBTRACT:
*accumulator -= memory[*operand];
if (*accumulator
printf("\n*** Accumulator overflow ***\n");
printf("*** Simpletron execution abnormally"\
" terminated ***\n");
dump(memory, *accumulator, *instructionCounter,
*instructionRegister, *operationCode,
*operand);
exit(1);
}
break;
case DIVIDE:
if (memory[*operand] == 0) {
printf("\n*** Attempt to divide by zero ***\n");
printf("*** Simpletron execution abnormally"\
" terminated ***\n");
dump(memory, *accumulator, *instructionCounter,
*instructionRegister, *operationCode,
*operand);
exit(1);
}
else {
*accumulator /= memory[*operand];
}
if (*accumulator > 9999 || *accumulator
printf("\n*** Accumulator overflow ***\n");
printf("*** Simpletron execution abnormally"\
" terminated ***\n");
dump(memory, *accumulator, *instructionCounter,
*instructionRegister, *operationCode,
*operand);
exit(1);
}
break;
case MULTIPLY:
*accumulator *= memory[*operand];
if (*accumulator > 9999 || *accumulator
printf("\n*** Accumulator overflow ***\n");
printf("*** Simpletron execution abnormally"\
" terminated ***\n");
dump(memory, *accumulator, *instructionCounter,
*instructionRegister, *operationCode,
*operand);
exit(1);
}
break;
case BRANCH:
*instructionCounter = *operand;
break;
case BRANCHNEG:
if (*accumulator
*instructionCounter = *operand;
}
else
(*instructionCounter)++;
break;
case BRANCHZERO:
if (*accumulator == 0) {
*instructionCounter = *operand;
}
else
(*instructionCounter)++;
break;
case HALT:
printf("\n*** Simpletron execution terminated ***\n");
dump(memory, *accumulator, *instructionCounter,
*instructionRegister, *operationCode, *operand);
break;
default:
printf("\n*** Invalid opration code! ***\n");
printf("*** Simpletron execution abnormally"\
" terminated ***\n");
dump(memory, *accumulator, *instructionCounter,
*instructionRegister, *operationCode,
*operand);
exit(1);
break;
}
if (*operationCode != BRANCH && *operationCode != BRANCHNEG
&& *operationCode != BRANCHZERO) {
(*instructionCounter)++;
}
if (*instructionCounter >= 100) {
printf("\n*** Insufficient memory ***\n");
printf("*** Simpletron execution abnormally"\
" terminated ***\n");
dump(memory, *accumulator, *instructionCounter,
*instructionRegister, *operationCode,
*operand);
exit(1);
}
} while (*operationCode != HALT && *instructionCounter
}
void dump(int* memory, int accumulator, int instructionCounter,
int instructionRegister, int operationCode, int operand)
{
printf("\nREGISTERS:\n");
printf("%-25s%+05d\n", "accumulator", accumulator);
printf("%-28s%02d\n", "instructionCounter", instructionCounter);
printf("%-25s%+05d\n", "instructionRegister", instructionRegister);
printf("%-28s%02d\n", "operationCode", operationCode);
printf("%-28s%02d\n", "operand", operand);
printf("\nMEMORY:\n ");
for (int i = 0; i
printf(" %5d", i);
}
printf("\n");
for (int i = 0; i
if (i % 10 == 0)
printf("%2d", i);
printf(" %+05d", memory[i]);
if (i % 10 == 9)
printf("\n");
}
printf("\n");
}