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");

}