Prévia do material em texto
5-1 Computador hipotético Ahmes O computador AHMES1 foi criado com intenções didáticas, para possibilitar a implementação de rotinas aritméticas simples (como adição e subtração) e a programação de rotinas relativamente complexas (como multiplicação e divisão). É compatível com o NEANDER, mas apresenta instruções extras para facilitar a execução de operações aritméticas. 5.1 Características O computador AHMES tem as seguintes características: • Largura de dados e endereços de 8 bits. • Dados representados em complemento de dois. • 1 acumulador de 8 bits (AC), onde é armazenado o resultado das operações. • 1 apontador de programa de 8 bits (PC), que indica qual a próxima instrução a ser executada. • 1 registrador de estado com 5 códigos de condição: negativo (N), zero (Z), carry out (vai-um) (C), borrow out (empresta-um) (B) e overflow (estouro) (V). 5.2 Modos de endereçamento O AHMES só possui um modo de endereçamento: o modo direto. Neste modo, a palavra que segue o código da instrução contém, nas instruções de manipulação de dados, o endereço do operando (Figura 5.1). Nas instruções de desvio, o endereço contido na instrução corresponde ao endereço da próxima instrução. endereço memória operando instrução Figura 5.1 - Modo de endereçamento direto 1Este computador simulado foi batizado em homenagem ao escriba Ahmes, do antigo Egito (1650 A.C.), autor de uma série de papiros contendo regras que possibilitavem cálculos aritméticos “complexos”, como o cálculo de área de polígonos e manipulação de frações. 5-2 5.3 Conjunto de instruções O conjunto de instruções de AHMES compreende 24 instruções, codificadas através de um byte de código (Tabela 5.1). Note-se que, na maioria das vezes, os quatro bits mais significativos são suficientes para definir completamente a instrução. Código binário (relevante) Código binário (com zeros) Código hexadecimal Código decimal Instrução (mnemônico) 0000 xxxx 0000 0000 0 0 0 NOP 0001 xxxx 0001 0000 1 0 16 STA end 0010 xxxx 0010 0000 2 0 32 LDA end 0011 xxxx 0011 0000 3 0 48 ADD end 0100 xxxx 0100 0000 4 0 64 OR end 0101 xxxx 0101 0000 5 0 80 AND end 0110 xxxx 0110 0000 6 0 96 NOT 0111 xxxx 0111 0000 7 0 112 SUB end 1000 xxxx 1000 0000 8 0 128 JMP end 1001 00xx 1001 0000 9 0 144 JN end 1001 01xx 1001 0100 9 4 148 JP end 1001 10xx 1001 1000 9 8 152 JV end 1001 11xx 1001 1100 9 C 156 JNV end 1010 00xx 1010 0000 A 0 160 JZ end 1010 01xx 1010 0100 A 4 164 JNZ end 1011 00xx 1011 0000 B 0 176 JC end 1011 01xx 1011 0100 B 4 180 JNC end 1011 10xx 1011 1000 B 8 184 JB end 1011 11xx 1011 1100 B C 188 JNB end 1110 xx00 1110 0000 E 0 224 SHR 1110 xx01 1110 0001 E 1 225 SHL 1110 xx10 1110 0010 E 2 226 ROR 1110 xx11 1110 0011 E 3 227 ROL 1111 xxxx 1111 0000 F 0 240 HLT Tabela 5.1 - Conjunto de instruções do AHMES A primeira coluna da Tabela 5.1 indica, em binário, quais são os bits relevantes da codificação. Somente os bits indicados em zero e em um são relevantes para identificar a instrução. Os bits marcados com um “x” são irrelevantes (don’t care), ou seja, seu valor não interfere na decodificação da instrução. Por simplicidade, todos os “x” serão substituídos por zeros, como pode ser visto nas demais colunas. A última coluna indica o mnemônico da instrução, ou seja, uma sigla de duas ou três letras que visa facilitar a “compreensão” da instrução por um ser humano. Para o computador estes mnemônicos são desnecessários, uma vez que ele sempre trabalha com códigos binários. Note-se inclusive que os próprios códigos hexadecimal e decimal também só são usados para a conveniência humana. A Tabela 5.2 mostra a execução de cada instrução, tal como ela é realizada pelo computador. Nesta tabela, AC representa o acumulador, PC representa o program counter (apontador de instruções), end indica um endereço de memória, MEM(end) o conteúdo da posição de memória endereçada por end, e N, V, Z, C e B indicam os códigos de condição negativo, overflow, zero, carry e borrow, respectivamente. As instruções podem ser divididas em diversas classes ou categorias, de acordo com a sua função principal. As instruções STA e LDA formam o grupo de movimentação de dados, ou seja, são responsáveis por levar os dados de e para a memória. As instruções ADD e SUB são as instruções aritméticas, e as instruções OR, AND e NOT formam o grupo das instruções 5-3 lógicas, uma vez que usam operações da álgebra booleana (mas manipulando oito bits de cada vez, e não um só bit). As instruções JMP, JN, JP, JV, JNV, JZ, JNZ, JC, JNC, JB e JNB são as instruções de desvio, e nelas end corresponde ao endereço de desvio, ou seja, qual o endereço da próxima instrução a ser executada. As instruções SHR, SHL, ROR e ROL são as instruções de deslocamento. Instrução Execução Comentário NOP nenhuma operação nenhuma operação STA end MEM(end) ‹ AC armazena acumulador na memória (store) LDA end AC ‹ MEM(end) carrega acumulador da memória (load) ADD end AC ‹ AC + MEM(end) soma OR end AC ‹ AC or MEM(end) “ou” lógico AND end AC ‹ AC and MEM(end) “e” lógico NOT AC ‹ NOT AC inverte (complementa) acumulador SUB end AC ‹ AC – MEM(end) subtração JMP end PC ‹ end desvio incondicional (jump) JN end IF N=1 THEN PC ‹ end desvio condicional (jump if negative) JP end IF N=0 THEN PC ‹ end desvio condicional (jump if positive) JV end IF V=1 THEN PC ‹ end desvio condicional (jump if overflow) JNV end IF V=0 THEN PC ‹ end desvio condicional (jump if not overflow) JZ end IF Z=1 THEN PC ‹ end desvio condicional (jump if zero) JNZ end IF Z=0 THEN PC ‹ end desvio condicional (jump if non-zero) JC end IF C=1 THEN PC ‹ end desvio condicional (jump if carry) JNC end IF C=0 THEN PC ‹ end desvio condicional (jump if not carry) JB end IF B=1 THEN PC ‹ end desvio condicional (jump if borrow) JNB end IF B=0 THEN PC ‹ end desvio condicional (jump if not borrow) SHR C ‹ AC(0); AC(i-1)‹ AC(i); AC(7)‹ 0 deslocamento para direita (shift right) SHL C ‹ AC(7); AC(i)‹ AC(i-1); AC(0)‹0 deslocamento para esquerda (shift left) ROR C ‹ AC(0); AC(i-1)‹ AC(i); AC(7)‹ C rotação para direita (rotate right) ROL C ‹ AC(7); AC(i)‹ AC(i-1); AC(0)‹ C rotação para esquerda (rotate left) HLT Interrompe o processamento término de execução - (halt) Tabela 5.2 - Ações executadas 5.4 Códigos de condição A unidade lógica e aritmética de AHMES fornece os seguintes códigos de condição, que são usados pelas instruções de desvio condicional (conforme Tabela 5.2): N - (negativo) : sinal do resultado, interpretado como complemento de dois 1 - resultado é negativo 0 - resultado é positivo Z - (zero) : indica resultado igual a zero, interpretado como complemento de dois 1 - resultado é igual a zero 0 - resultado é diferente de zero V - (overflow) : indica estouro de representação após uma instrução de soma ou subtração, interpretando os operandos e o resultado como complemento de dois 1 - houve estouro de representação do resultado 0 - não houve estouro (resultado está correto) C - (carry) : indica a existência de um ”vai-um” após uma operação de soma 1 - ocorreu ”vai-um” 5-4 0 - não ocorreu ”vai-um” B - (borrow) : indica a existência de um ”empresta-um” após uma operação de subtração 1 - ocorreu ”empresta-um” 0 - não ocorreu ”empresta-um” As instruções do AHMES afetam os códigos de condição conforme indicado na Tabela 5.3. Note-se que somente as instruções aritméticas, lógicas e de deslocamento (além da instrução LDA) afetam os códigos de condição. As instruções de desvio (e a instrução STA), apesar de testarem este códigos, não os alteram. Instrução Códigos alterados NOP nenhum STA end nenhum LDA end N, Z ADD end N, Z, V, C OR end N, Z AND end N, Z NOT N, Z SUB end N, Z, V, B JMP end desvio incondicional - nenhum Jxxx end desvios condicionais - nenhum SHR N, Z, C SHL N, Z, C RORN, Z, C ROL N, Z, C HLT nenhum Tabela 5.3 - Códigos de condição ajustados Observação: como o AHMES é um computador com propósitos didáticos, os seus códigos de condição não correspondem exatamente àqueles encontrados em um computador real. Assim, por exemplo, borrow (B) e carry (C) são normalmente reunidos em um único código de condição, chamado simplesmente de carry. 5.5 Manipulação aritmética Nos trechos de programas apresentados a seguir e nos capítulos seguintes será utilizada a notação simbólica, com mnemônicos no lugar de códigos decimais ou hexadecimais. No simulador AHMES, entretanto, a codificação deverá necessariamente ser realizada em forma numérica. Esta restrição também possui caracter didático, para que a ”linguagem de máquina” do computador seja bem exercitada. 5 .5 .1 Aritmética em complemento de dois AHMES trabalha naturalmente com complemento de dois (os seus componentes de hardware foram projetados para isto). Assim, somas e subtrações são realizadas diretamente através das instruções de ADD e SUB. Os cinco códigos de condição (N, Z, V, C e B) também refletem diretamente o resultado destas instruções. Para inverter o sinal de número, existem duas possibilidades. Seja “a” o número a ter seu sinal trocado. Então tem-se: 1. Realizar a operação 0 – a, através da operação SUB. Isto exige carregar zero no acumulador e depois subtrair o número “a”. O resultado está no acumulador. 5-5 2. Realizar a operação not(a) + 1. Isto utiliza a troca de sinal em complemento de um (que inverte todos os bits do número, e é implementado através da operação NOT) e a seguir soma um para obter o complemento de dois: not(a) = –a – 1 (em complemento de um) not(a) + 1 = –a –1 + 1 = –a (em complemento de dois) Sobre os códigos de condição, algumas observações importantes devem ser feitas: 1. Carry (C) e overflow (V) não são sinônimos. Conforme foi visto na seção 2.6, em aritmética de complemento de dois podem ocorrer as quatro combinações possíveis: sem carry nem overflow, somente carry, somente overflow e tanto carry como overflow. Isto pode ser verificado com um programa simples, como ilustrado a seguir. LDA 128 % primeiro operando está na posição 128 ADD 129 % segundo operando está na posição 129 HLT % resultado está no acumulador Experimente agora com diversos pares de operandos nos endereços 128 e 129: 1.1: 7 e 5, 15 e 12, 100 e 26, 110 e 17. Em todos estes casos, a soma não produz nem carry nem overflow. 1.2: 7 e 251 (-5 em complemento de dois), 15 e 244 (-12 em complemento), 100 e 230 (-26), 110 e 239 (-17). Nestes casos, a soma produz carry (C=1), mas não overflow (V=0). Isto indica que o resultado está correto. O mesmo ocorre para 249 e 251 (-7 e - 5 em complemento de dois), 241 e 244 (-15 e -12 em complemento de dois), 156 e 230 (-100 e -26), 146 e 239 (-110 e -17). 1.3: 127 e 5, 116 e 12, 100 e 28, 110 e 120. Nestes casos, não é produzido carry (C=0), mas ocorre overflow (V=1). Em todos os casos exemplificados, os operandos são positivos, mas o resultado é negativo, o que indica estouro de representação. 1.4: 128 e 251 (-128 e -5 em complemento de dois), 241 e 136 (-15 e -120 em complemento de dois), 156 e 226 (-100 e -30), 146 e 238 (-110 e -18). Nestes casos, ocorre tanto carry (C=1) como overflow (V=1). Em todos os casos exemplificados, os operandos são negativos, mas o resultado é positivo, o que indica estouro de representação. 2. O sinal de carry, em múltiplas somas, é cumulativo. Isto significa que, quando se somam três ou mais parcelas, o número de vai-uns deve ser contado, para determinar qual a quantidade final (ou seja, se ocorreu um ”vai-dois”, ”vai-três”, etc) 3. O sinal de overflow, em múltiplas somas, deve ser analisado cuidadosamente. Se o overflow ocorrer um número ímpar de vezes, então é garantido que ocorreu overflow no resultado final. Se entretanto ocorrer overflow um número par de vezes, isto não significa necessariamente que ocorreu estouro da representação do resultado final. Seja, por exemplo, 127 + 1 + -1. Em complemento de dois, tem-se 01111111 + 00000001 + 11111111. A soma das duas primeiras parcelas resulta em 10000000, com indicação de overflow. A soma seguinte, 1000000 + 11111111, resulta em 011111111, também com indicação de overflow. Neste caso, o resultado final está correto (127 em decimal), e os dois sinais de overflow anulam-se mutuamente. No caso de 127 + 127 + 127 + 127, entretanto, também ocorre overflow duas vezes, mas o resultado final (252 em decimal, ou seja, -4) está incorreto, ou seja, realmente ocorreu overflow. 4. O sinal de borrow é o inverso do carry. Isto pode ser verificado comparando-se uma operação de subtração com uma adição com o complemento do subtraendo, ou seja, no 5-6 lugar de a – b realiza-se a + not (b) + 1. Como pode ser visto na tabela abaixo, o carry resultante é sempre o inverso do borrow: a + not(b) + 1 r carry a – b r borrow 0 1 1 0 1 0 0 0 0 0 0 1 1 0 0 1 1 1 1 1 1 1 1 1 0 1 0 1 0 1 0 1 1 1 0 0 Assim, se o carry for um, significa que o borrow é zero, e vice-versa. Isto pode ser observado comparando-se o borrow (B) da operação 5 – 7 através de uma instrução de SUB com o carry (C) gerado pela operação 5 + (-7) através de uma instrução de ADD. Note-se que o AHMES gera carry e borrow em instruções distintas - o borrow é o da última operação de SUB, e o carry e o da última operação de ADD ou de deslocamento. Assim, no AHMES, os indicadores de carry e borrow (C e B) não possuem nenhuma relação entre si. 5 .5 .2 Aritmética de inteiros positivos Apesar de projetado para trabalhar com números em complemento de dois, AHMES (e também todos os processadores que utilizam complemento de dois) também pode manipular números inteiros positivos. As operações de adição e subtração (instruções ADD e SUB) podem ser utilizadas sem restrições ou modificações, mas os códigos de condição devem ser analisados de forma diversa, conforme indicado a seguir: 1. O código de sinal (N) não tem mais significado. 2. O código de overflow (V) também perde o significado e não deve ser utilizado. 3. Os códigos de carry (C) e borrow (B) mantêm o seu significado original, e passam adicionalmente a indicar também estouro de representação. Isto significa que a indicação de carry após uma soma (C=1 após ADD) e borrow após uma subtração (B=1 após SUB) são sinônimos de estouro de representação. 4. O código de zero (Z) mantém seu significado. 5 .5 .3 Aritmética em complemento de um A aritmética em complemento de um é em si bem semelhante à de complemento de dois; a diferença básica está na representação de número negativos, que possuem a quantidade –1 a mais. Isto permite que um computador projetado com aritmética em complemento de dois também possa manipular, com relativa facilidade, números em complemento de um. Conforme visto na seção 2.3.3, a soma de dois números em complemento de um requer uma eventual correção do resultado, através da soma do carry: 0 LDA 128 % primeiro operando está na posição 128 2 ADD 129 % segundo operando está na posição 129 4 JNC 8 % se não houve carry, resultado está correto 6 ADD 130 % posição 130 contém a constante 1 8HLT % resultado está no acumulador De maneira análoga, a subtração de dois números em complemento de um também requer uma eventual correção do resultado, subtraindo-se o borrow se este for um: 0 LDA 128 % primeiro operando está na posição 128 2 SUB 129 % segundo operando está na posição 129 5-7 4 JNB 8 % se não houve borrow, resultado está correto 6 SUB 130 % posição 130 contém a constante 1 8HLT % resultado está no acumulador Os códigos de condição devem ser interpretados de maneira um pouco diversa: 1. O código de zero (Z) somente detecta o zero positivo (00000000); o zero negativo (11111111) deve ser testado à parte e convertido para o zero positivo. 2. Os códigos de sinal (N), carry (C), borrow (B)e overflow (V) mantêm o seu significado original, mas devem ser analisados após a correção do resultado, ou seja, após as duas operações de ADD ou SUB. 5 .5 .4 Aritmética em sinal/magnitude Um computador projetado para aritmética em complemento de dois não manipula facilmente números em sinal/magnitude. As operações de soma e subtração necessitam de grandes ajustes para serem efetuadas; não basta o simples uso das instruções de ADD e SUB. Além disto, os códigos de condição não são de grande ajuda: 1. O código de zero (Z) somente detecta o zero positivo (00000000); o zero negativo (10000000) deve ser testado a parte. 2. O código de overflow (V) não tem significado e não deve ser utilizado. 3. Os códigos de carry (C) e borrow (B) também perdem seu significado original. 4. O código de sinal (N) mantém seu significado e pode ser utilizado. Basicamente o bit de sinal e os bits de magnitude devem ser isolados e tratados separadamente. Para esta separação, podem ser utilizadas máscaras, como indicado na rotina abaixo: LDA 128 % operando está na posição 128 AND 131 % posição 131 contem 10000000 (para isolar o sinal) STA 129 % posição 129 recebe o sinal LDA 128 % carrega novamente o operando AND 132 % posição 132 contem 01111111 (para isolar a magnitude) STA 130 % posição 130 recebe os sete bits da magnitude Uma vez isolados, sinal e magnitude podem ser manipulados individualmente, conforme a tabela 2.4 do capítulo 2, seção 2.3.2. Um eventual estouro de representação pode ser detectado através do oitavo bit da magnitude (o bit de sinal do AHMES). Se este bit for ligado após uma soma, isto indica que a magnitude necessita de oito bits para ser representada, ou seja, não pode mais ser representada somente com sete bits. Após a realização da operação desejada, os bits de sinal e magnitude devem ser novamente reunidos. Assumindo-se a mesma ocupação de memória do trecho de programa anterior, tem-se a seguinte rotina: LDA 130 % carrega a magnitude (oitavo bit em zero) OR 129 % inclui o bit de sinal (no oitavo bit; demais estão em zero) STA 128 % armazena o operando na posição 128