next up previous contents
Next: Jogos de Instruções: Modos Up: Arquitectura e Organização Interna Previous: Aritmética do Computador


Jogos de instruções: Características e Funções

Muito do que é discutido neste livro não é imediatamente aparente para o utilizador ou programador de um computador. Se um programador estiver a usar uma linguagem de alto-nível, tal como Pascal ou Ada, é visível muito pouco da arquitectura da máquina subjacente.

Uma linha de fronteira de onde o projectista de computadores e o programador de computadores podem ver a mesma máquina é do jogo de instruções máquina. Do ponto de vista do projectista, o jogo de instruções da máquina fornece os requisitos funcionais do processador: a implementação do processador é uma tarefa que, em grande parte, envolve a implementação do jogo de instruções máquina. Do lado do utilizador, o utilizador que escolhe programar em linguagem máquina (na realidade, em linguagem de montagem; ver secção 9.4) toma conhecimento da estrutura dos registos e da memória, dos tipos de dados directamente suportados pela máquina e do funcionamento do processador.

Uma descrição do jogo de instruções máquina de um processador percorre um longo caminho que acompanha a elucidação do processador do computador. Em concordância, nós pomos a ênfase, neste capítulo e no próximo, nas instruções máquina para então nos virarmos para a examinação da estrutura da função dos processadores.

Características das Instruções Máquina

A operação de um processador é determinada pelas instruções que executa. Estas instruções são designadas por instruções máquina ou instruçõedo do computador. O processador pode executar uma variedade de funções e isso reflecte-se na variedade de funções definidas para o processador. A colecção de diferentes instruções que o processador pode executar é designada por jogo de instruções do processador.

Elementos de uma Instrução Máquina

Cada instrução deve conter a informação requerida pelo processador para execução. A figura 9.1, que repete a figura 3.6, mostra os passos envolvidos na execução de instruções, o que implicitamente define os elementos da instrução máquina. Estes elementos são: A próxima instrução a ser extraída está localizada na memória principal ou, no caso de um sistema de memória virtual, na memória principal ou na memória secundária (disco). Na maior parte dos casos, a próxima instrução a extrair segue imediatamente a instrução corrente. Nestes casos, não há referência explícita à próxima instrução. Quando é necessária uma referência explícita, então, o endereço da memória principal ou da memória virtual deve ser suprido. A forma através da qual o endereço é suprido é discutido no capítulo 10.

Os operandos na origem e de resultado podem estar numa de três áreas:

Figura 9.1: Diagrama de estado do ciclo de instrução
\begin{figure}
\end{figure}

Representação da Instrução

No interior do computador, cada instrução é representada por uma sequência de bits. A instrução está dividida em campos, correspondentes aos elementos constituintes da instrução. Este esquema da instrução é conhecido como formato da instrução. Um exemplo simples é apresentado na figura 9.2. O formato de instrução do IAS é mostrado na figura 2.2, como um outro exemplo. Na maior parte dos jogos de instruções é usado mais do que um formato. Durante a execução da instrução, uma instrução é lida para um registo de instrução (IR) no processador. O processador deverá ser capaz de extrair os dados dos vários campos da instrução para executar a instrução requerida.

É difícil, tanto para o programador como para o leitor de manuais, haver-se com representações binárias de instruções máquina. Assim, tornou-se prática comum usar uma representação simbólica de instruções máquina. Um exemplo disto foi usado para o jogo de instruções do IAS, na tabela 2.1.

Os opcodes são representados por abreviaturas, chamadas mnemónicas que indicam a operação. Exemplos comuns incluem

ADD Soma
SUB Subtrai
MPY Multiplica
DIV Divide
LOAD Carregar dados da memória
STOR Guardar dados na memória

Os operandos são também representados simbolicamente. Por exemplo, a instrução
ADD R,Y

pode significar somar o valor contido na posição de dados Y ao conteúdo do registo R.
Neste exemplo, Y designa o endereço de uma posição de memória e R designa um registo particular. É de notar que a operação é executada sobre o conteúdo da posição, não sobre o seu endereço.

Assim, é possível escrever em forma simbólica um programa em linguagem-máquina.

Figura 9.2: Formato de uma instrução simples.
\begin{figure}
\end{figure}

Cada símbolo do opcode tem uma representação binária fixa e o programador especifica a posição de cada símbolo de operando. Por exemplo, o programador pode começar com uma lista de definições:
X = 513
Y = 514

e assim sucessivamente. Um programa simples aceitaria esta entrada simbólica, converteria as referências a opcodes e operandos para formato binário e construiria instruções máquinas binárias. Hoje em dia, a maioria dos programas são escritos em linguagem de alto-nível ou, não sendo esse o caso, em linguagem de montagem, a qual é discutida no fim deste capítulo. Apesar de tudo, a linguagem máquina simbólica continua a ser um instrumento útil para descrever instruções máquina e usá-la-emos para esse fim.

Tipos de Instrução

Consideremos uma instrução em linguagem de alto-nível que podia ser expressa numa linguagem tal como o BASIC ou o FORTRAN. Por exemplo,
X = X + Y

Esta declaração indica ao processador para somar o valor guardado em Y ao valor guardado em X e pôr o ressaltado em X. Como poderia isto ser realizado com instruções máquina? Vamos assumir que as variáveis X e Y correspondem às posições 513 e 514. Se considerarmos um jogo de instruções máquina simples, esta operação poderia ser realizada com três instruções: Como podemos ver, uma simples instrução BASIC pode requerer três instruções máquina. Isto é típico da relação que se estabelece entre uma linguagem de alto-nível e uma linguagem máquina. Uma linguagem de alto-nível expressa as operações numa forma algébrica concisa, usando variáveis. Uma linguagem máquina expressa as operaçoes numa forma básica que envolve o movimento de dados de, ou para, registos.

Com este exemplo simples para nos guiar, consideremos os tipos de instruções que devem ser incluídos num computador prático. Um computador deveria ter um jogo de instruções que permitisse ao utilizador formular qualquer tarefa de processamento de dados. Uma outra forma de ver isto é considerar as capacidades de uma linguagem de programação de alto-nível. Qualquer programa escrito numa linguagem de alto-nível deve ser convertido em linguagem máquina para poder ser executado. Assim, o jogo de instruções máquina deve ser suficiente poderoso para poder expressar qualquer uma das instruções de uma linguagem de alto-nível. Com isto no pensamento podemos categorizar os tipos de instruções como se segue:

As instruções aritméticas proporcionam capacidades computacionais para o processamento de dados numéricos. As instruções lógicas operam nos bits de uma palavra como bits em vez de números; deste modo, providenciam capacidades para processar qualquer outro tipo de dados que o utilizador possa querer empregar. Estas operações são primeiramente efectuadas em dados nos registos do processador. Pelo que, têm que existir instruções de memória para mover dados entre a memória e os registos. As instruções de E/S são necessárias para transferir programas e dados para a memória e entregar os resultados da computação ao utilizador. As instruções de teste são usadas para testar o valor dos dados de uma palavra ou o status de uma computação. As instruções de Derivação são usadas para fazer uma derivação para um sequência diferente de instruções, dependendo da decisão tomada.

Examinaremos com grande detalhe os vários tipos de instruções, mais tarde, neste capítulo.

Número de Endereços

Uma das formas tradicionais de descrever a arquitectura do processador é em termos do número de endereços contidos em cada instrução. Esta dimensão tornou-se menos significativa com o aumento de complexidade do desenho do processador. Não obstante, é útil neste ponto esboçar e analisar esta distinção.

Qual é o número máximo de endereços que pode ser necessário numa instrução? Evidentemente que a maior parte das operações lógicas e aritméticas necessitam de operandos. Virtualmente, todas as operações lógicas e aritméticas são ou unárias (um operando) ou binárias (dois operandos). Desta forma, necessitaríamos de um máximo de dois endereças para designar os operandos. O resultado de uma operação deve ser guardado, o que sugere um terceiro operando. Finalmente, após a conclusão de uma instrução, deve ser extraída a próxima instrução, sendo necessário o seu endereço.

A linha de pensamento, acima, sugere ser plausível que uma instrução necessite de conter quatro referências a endereços : dois operandos, um resultado e o endereço da próxima instrução. Na prática, instruções de quatro endereços são extremamente raras. A maior parte dos processadores são da variedade de um, dois, ou três endereços, sendo implícito o endereço da próxima instrução (obtido do contador de programa).

A figura 9.3 compara instruções típicas de um, dois, e três endereços que podem ser usadas para calcular $Y = (A - B) / (C + D * E)$. Com três endereços, cada instrução especifica a localização de dois operandos e a localização de um resultado. Porque não pretendemos alterar o valor de nenhum dos operandos, um espaço temporário, T, é usado para guardar alguns resultados intermédios. É de notar que há quatro instruções e que a expressão original tem cinco operandos.

Formatos de instruções com três endereços não são habituais, porque requerem uma instrução relativamente longa para poder conter as três referências a endereços. Para operações binárias, com instruções de dois endereços, um dos endereços deve cumprir o duplo papel de operando e de resultado. Deste modo, a instrução SUB Y, B produz o cálculo Y - B e guarda o resultado em Y. O formato de dois endereços reduz os requisitos de espaço mas também introduz algum embaraço. Para evitar alterar o valor de um operando a instrução MOVE é usada para transferir um dos valores para o resultado ou para uma localização temporária antes de efectuar a operação. O nossos programa de exemplo estende-se para seis instruções.

Figura 9.3: Programas para executar $Y = (A - B) / (C + D * E)$
\begin{figure}
\begin{tabular}{llr}
\multicolumn{2}{l}{Instruções}&Comentários...
...
\hline
\multicolumn{1}{l}{(c) Um \ endereço.}
\end{tabular}
\end{figure}

Ainda mais simples é a instrução de um endereço. Para que esta funcione, um segundo endereço tem de estar implícito. Isto era habitual nas instruções mais antigas, sendo o endereço implícito um registo do processador conhecido como acumulador ou AC. O acumulador contém um dos operandos e é usado para guardar o resultado. No nosso exemplo, são necessárias oito instruções para completar o cálculo.

É, de facto, possível fazer qualquer coisa de instruções de zero endereços. Instruções de zero endereços são aplicáveis a organizações especiais da memória, chamadas pilha. Uma pilha é um conjunto de localizações, primeiro a chegar, último a sair. A pilha está num localização conhecida e, muitas vezes, pelo menos os dois primeiros elementos estão em registos do processador. Desta forma, as instruções de zero endereços podem fazer referência aos dois elementos no topo da pilha. As pilhas são apresentadas no apêndice 9A. O seu uso é explorado em profundidade mais tarde, neste capítulo e no capítulo 10.

A tabela 9.1 resume a interpretação das instruções com 0, 1, 2 ou 3 endereços. Em cada caso da tabela, assume-se que o endereço da próxima instrução está implícito e que a operação a efectuar tem dois operandos na origem e um operando no destino.

O número de endereços por instrução é uma opção básica de desenho. Menos endereços por instrução tem como resultado instruções mais elementares, que requerem uma menor complexidade do processador. Tem também como resultado instruções mais curtas. Noutra perspectiva, os programas contêm um número total superior de instruções, o que em geral tem como consequência tempos de execução mais longos e programas mais complexos. Há, também, um patamar importante entre as instruções de um endereço e as de múltiplos endereços. Com instruções de um endereço, o programador geralmente tem apenas disponíveis registos de uso geral, o acumulador. Com instruções de um múltiplos endereços, é comum ter múltiplos registos de uso geral. Isto permite que alguma operações possam ser efectuadas apenas sobre registos. Uma vez que a referência a registos é mais rápida do que a referência à memória, isto torna mais rápida a execução. Por razões de flexibilidade e de habilidade para usar múltiplos registos, as máquina mais actuais empregam uma mistura de instruções de dois e de três endereços.

Os compromissos de desenho envolvidos na escolha do número de endereços por instrução são complicados por outros factores. Há o problema de saber se os endereços referenciam localizações da memória ou de registos. Uma vez que há menos registos, são necessários menos bits para a referência a registos. Também, como veremos no próximo capítulo, uma máquina pode disponibilizar uma variedade de modos de endereçamento e a especificação do modo tomar um ou mais bits. O resultado é que a maior parte dos desenhos de processadores envolvem uma variedade de formatos de instruções.

Tabela 9.1: Utilização de endereços de instrução (instruções sequenciais).
Número de endereços Representação Simbólica Interpretação
3 OP A, B, C A$\leftarrow$ B OP C
2 OP A, B A$\leftarrow$ A OP B
1 OP A AC$\leftarrow$ AC OP A
0 OP $T\leftarrow$ T OP (T - 1)

Projecto de Jogos de Instruções

Uma dos mais interessantes, e mais analisados, aspectos do desenho de computadores é o projecto de jogo de instruções. O desenho de jogo de instruções é muito complexo, uma vez que afecta muitos aspectos do sistema de computação. O jogo de instruções define muitas das funções realizadas pelo processador e, por isso, tem um impacte fundamental na realização do processador. O jogo de instruções é o meio usado pelo programador para controlar o processador. Por isso, os requisitos do programador têm de ser considerados no desenho de jogo de instruções.

Pode ficar surpreendido por saber que alguns dos mais importantes tópicos relacionados com o desenho de jogo de instruções continuam a ser controversos. Na verdade, em anos recentes, o nível de desacordo respeitantes àqueles tópicos tem vindo a crescer. Os mais importantes destes tópicos de desenho incluem:

Estes tópicos estão altamente inter-relacionados e tem de ser considerados em conjunto no desenho de jogo de instruções. Este livro tem, obviamente, de os considerar em sequência, mas a intenção é mostrá-los em inter-relação.

Por causa da importância deste tópico, muito da Parte III é dedicada ao desenho de jogo de instruções. Seguindo a vista geral desta secção, o capítulo examina o repertório de tipos de dados e de operações. O capítulo 10 examina os modos de endereçamento (o que inclui os registos) e os formatos de instrução. O capítulo 12 examina um excitante desenvolvimento, recente, conhecido como jugo reduzido de instruções (RISC). As arquitecturas RISC trazem a discussão muitas das decisões de desenho de jogos de instruções tomadas por muitos computadores comerciais, contemporâneos. Um exemplo de máquina RISC é o PowerPC. Neste capítulo e no próximo, utilizá-lo-emos como um dos nossos exemplos. Ainda assim, a importância do desenho do PowerPC, no contexto RISC, é discutido no capítulo 12.

Tipos de Operandos

As instruções máquina operam em dados. As categorias gerais mais importantes de dados são: Veremos, na discussão sobre os modos no capítulo 10, que os endereços são de facto uma forma de dados. Em muitos casos, alguns cálculos têm de ser efectuados nas referências aos operandos de uma instrução para determinar o endereço principal ou virtual da memória. Neste contexto, os endereços podem ser considerados como inteiros sem sinal.

Outros tipos de dados habituais são os números, caracteres, e dados lógicos e cada um destes é, brevemente, analisado nesta secção. Para além destes, alguma máquinas definem tipos de dados especializados ou estruturas de dados. Por exemplo, podem existir operadores máquina que actuam directamente numa lista ou sequência de caracteres.

Números

Todas as linguagens máquina incluem tipos de dados numéricos. Mesmo no processamento de dados não numéricos, há necessidade de números para actuar como contadores, largura de campos e assim sucessivamente. Uma distinção importante entre números usados em matemática comum e os números guardados num computador é que os últimos são limitados. Isto é verdade em dois sentidos. Em primeiro lugar, há um limite para a grandeza dos números representados num computador e em segundo lugar, no caso de números em vírgula flutuante, há um limite para a sua precisão. Assim, ao programador é exigida a compreensão das consequências do arredondamento, overflow e underflow.

Três tipos de dados numéricos são habituais em computadores:

Examinámos os primeiros dois com algum detalhe no capítulo 8. Resta dizer alguma palavras àcerca de números decimais.

Apesar de todas as operações interna de um computador serem binárias, por natureza, os utilizadores humanos do sistema manipulam números decimais. Por isso, à entrada, há necessidade de convertê-los de decimal para binário e, à saída, de binário para decimal. Em aplicações em que há uma grande quantidade de transacções de E/S e, comparativamente, poucos cálculos, é preferível guardar e operar em números em formato decimal. A representação mais habitual para este efeito é a decimal compactado.

Cada dígito decimal, em decimal compactado, é representado por um código de 4-bits, de forma óbvia. Assim, 0 = 0000, 1 = 0001, ..., 8 = 1000 e 9 = 1001. É de notar que este código é bastante ineficiente uma vez que apenas 10 das 16 possibilidades em 4-bits são usadas.. Para formar números, são enfileirados em conjunto códigos de 4-bits , habitualmente em múltiplos de 8-bits. Assim, o código para 246 é 0000001001000110. Este código é claramente menos compacto que um número em representação binária estrita, mas evita a sobrecarga da conversão. Os números negativos podem ser representados incluindo um dígito de sinal de 4-bits, à esquerda ou à direita da sequência de digito decimal compactados. Por exemplo, o código 1111 pode ser usado para o sinal menos.

Muitas máquina dispõem de instruções máquina para efectuar operações aritméticas directamente sobre números decimais compactados. Os algoritmos são muito semelhantes aos que foram descritos na secção 8.3 mas têm de ter em consideração o transporte em operações decimais.

Caracteres

Uma forma muito comum de dados é o texto ou sequência de caracteres. Apesar dos dados textuais serem apropriados para os seres humanos, estes não podem, em formato de caracteres, ser facilmente armazenados ou transmitidos através de sistemas de processamento e de comunicação. Tais sistemas foram projectados para dados binários. Assim, foram desenvolvidos um certo número de códigos, através dos quais os caracteres são representados por uma sequência de bits. Talvez o mais antigo dos exemplos é o código de Morse. Hoje, o mais habitual dos códigos de caracteres usado (nos Estados Unidos da América) é o código ASCII ( American Standard Code for Information Interchange) (Tabela 6.1) promulgado pelo American National Standard Institute (ANSI). O código ASCII é, também, muito usado fora do EUA. Cada carácter neste código é representado por um padrão único de 7-bits; desta forma, podem ser representados 128 caracteres diferentes. Isto é um número maior do que necessário para representar caracteres imprimíveis sendo alguns dos códigos usados para representar caracteres de controlo. Alguns destes caracteres de controlo têm a ver com o impressão de caracteres numa página. Outros têm a ver com procedimentos de comunicação. Os caracteres codificados em ASCII são quase sempre guardados e transmitidos usando 8-bits por carácter. O oitavo bit pode ser posto a 0 ou usado como bit de paridade para detecção de erros. Neste último caso, o bit é ajustado de forma a que o número total de bits a 1 em cada octeto é sempre ímpar (paridade ímpar) ou sempre par (paridade par).

É de notar que para o padrão ASCII 011XXXX, os dígitos 0 a 9 são representados pelo binário equivalente, 000 até 1001, nos últimos 4-bits. Este é o mesmo código que o decimal compactado. Isto facilita a conversão entre representações ASCII de 7-bits e decimais empacotados de 4-bits.

Um outro código usado para codificar caracteres é o (EBCDIC) Extended Binary Coded Decimal Interchange Code. O EBCDIC é usado nas máquinas IBM S/370. É um código de 8-bits. Tal como o ASCII, o EBCDIC é compatível com o decimal empacotado. No caso do EBCDIC, os códigos 11110000 até 11111001 representam os dígitos 0 até 9.

Dados Lógicos

Normalmente, cada palavra de uma unidade endereçável (octeto, meia palavra, ...) é tratada como uma unidade simples de dados. Contudo, é algumas vezes útil considerar uma unidade como consistindo de n itens de dados de 1-bit, cada item tendo o valor 0 ou 1. Quando os dados são vistos desta maneira são considerados como sendo dados lógicos.

Há duas vantagens na visão de dados orientada ao bit. Em primeiro lugar, podemos algumas vezes querer guardar uma lista de itens de dados binários ou booleanos, de tal forma que cada item possa tomar apenas os valores 1 (verdade) ou 0 (falso). Com dados lógicos, a memória pode ser usada muito eficientemente para este tipo de armazenamento. Em segundo lugar, há ocasiões em que pretendemos manipular os bits de dados de um item. Por exemplo, se as operações em vírgula flutuante forem realizadas por software, necessitamos de ser capazes de deslocar os bits mais significativos em alguma operações. Um outro exemplo: para converter de ASCII para decimal empacotado, necessitamos de extrair os 4-bits mais à direita de cada octecto.

É de notar que, nos exemplos acima, o mesmo dado pode ser tratado umas vezes logicamente e outras vezes numericamente ou como texto. O `` tipo'' de uma unidade de dados é determinado pela operação de que está a ser alvo. Enquanto isto não é normalmente verdade nas linguagens de alto nível (e.g. Pascal) é quase sempre o caso em linguagem máquina.

Tipos de Dados no Pentium

O Pentium pode lidar com tipos de dados de 8 (octeto), 16 (palavra), 32 (dupla palavra) e 64 (tetra palavra) bits de comprimento. Para permitir a máxima flexibilidade em estruturas de dados e utilização eficiente da memória, as palavras não têm de estar alinhadas em endereços pares; as palavras duplas não têm de estar alinhadas em endereços divisíveis por quatro; e as tetra palavras não necessitam de estar alinhadas em endereços divisíveis por 8. Contudo, quando é feito o acesso aos dados através de barramentos de 32-bits, as transferências de dados são realizadas em unidades de duplas palavras que começam em endereços divisíveis por quatro. O processador converte os pedidos de valores desalinhados em sequências de pedidos para a transferência através do barramento. Tal como todas as máquinas Intel 80x86, o Pentium usa o estilo little-endiam, isto é, o octeto menos significativo é guardado nos endereços mais baixos (ver Apêndice 9B).

O octeto, palavra, dupla palavra e tetra palavra são designados em geral como tipos de dados. Cumulativamente, o Pentium suporta uma lista impressionante de tipos específicos de dados que são reconhecidos e manipulados por instruções específicas. A tabela 9.2 resume estes tipos.

Tabela 9.2: Tipos de Dados no Pentium.
\begin{table}
\end{table}


O tipo vírgula flutuante refere um conjunto de tipos que são usados pela unidade de vírgula flutuante e manipulados pelas instruções de vírgula flutuante. Tal como é ilustrado na figura 9.4, as instruções em vírgula flutuante podem manipular inteiros e inteiros decimais empacotados assim como em números em vírgula flutuante. Os inteiros estão representados em complemento para dois e podem ter o tamanho de 16, 32 ou 64 bits, Os inteiros decimais empacotados são armazenados em representação de sinal e grandeza com 18 bits na gama desde 0 a 9. As três representações em vírgula flutuante obedecem à norma IEEE 754.

Figura 9.4: Formatos de dados numéricos na unidade de vírgula flutuante do Pentium.
\begin{figure}
\end{figure}

Tipos de dados do PowerPC

O PowerPc pode manipular tipos de dados com 8 (octeto), 16 (meia-palavra), 32 (palavra) e 64 (dupla-palavra) bits de comprimento. Algumas instruções requerem que os operandos em memória estejam alinhados em limites de 32-bits. Em geral, contudo, não é obrigatório o alinhamento. Uma interessante característica do PowerPc é que pode usar o estilo little-endiam e big-endiam; isto é, o octeto menos significante é guardado no endereço inferior ou superior (ver apêndice 9B para uma discussão do tema).

O octeto, a palavra e a dupla-palvra são tipos gerais de dados. O processador interpreta o conteúdo de um determinado item ou dado dependendo da instrução. O processador de ponto fixo reconhece os seguintes tipos de dados:

Adicionalmente, O PowerPc suporta os tipos de dados de precisão simples e dupla em vírgula flutuante definidos pela norma IEEE 754.

Tipos de operação

O número de códigos diferentes varia largamente de máquina para máquina. Contudo, encontram-se o mesmo tipo de operações gerais em todas as máquinas. Uma classificação útil e típica por categorias é a seguinte: A tabela 9.3 lista os tipos de instruções típicas em cada categoria. Esta secção fornece uma visão geral dos vários tipos de operações, em conjunto com uma breve discussão das acções levadas a cabo pelo processador para executar um tipo determinado de operação (sumariada na table 9.4). O último tópico é examinado em maior detalhe no capítulo 11.

Transferência de Dados

O mais fundamental dos tipos de instrução máquina é a instrução de transferência de dados. As instruções de transferência de dados têm de especificar várias coisas. Em primeiro lugar, a posição dos operandos na origem e no destino tem de ser especificada. Cada posição pode ser a memória, um registo ou o topo da pilha. Em segundo lugar o tamanho dos dados a serem transferidos têm de ser indicados. Em terceiro lugar, tal como com todas as instruções com operandos, o modo de endereçamento para cada operando tem de ser especificado. Este último ponto é discutido no capítulo 10.

A escolha das instruções de transferência de dados para incluir num jogo de instruções exemplifica o tipo de compromissos que o projectista tem de tomar. Por exemplo, a localização geral de um operando (memória ou registo) pode ser indicada tanto na especificação do código de operação como do operando. A tabela 9.5 mostra exemplos das instruções de transferência de dados mais habituais do IBM S/370. É de notar que há variantes para indicar a quantidade de dado a transferir (8, 16, 32 ou 64 bits). Também, há muitas instruções para transferências de registos para registos, registos para memória e de memória para registos. Em contraste, o VAX tem uma instrução (MOV) com variantes para mover diferentes quantidades de dados, mas especifica se um operando é registo ou memória como parte do operando. A abordagem VAX é de alguma forma mais fácil para o programador que tem de lidar com menos mnemónicas. Contudo, é também, de alguma forma, menos compacta do que a abordagem do IBM S/370, uma vez que a localização (registo versus memória) de cada operando tem de ser especificada separadamente da instrução. Voltaremos a esta distinção quando discutirmos os formatos de instruções, no próximo capítulo.

Em termos de acções do processador, as operações de transferência de dados são talvez as do tipo mais simples. Se tanto a origem como o destino forem registos, então o processador provoca a simples transferência de um registo para outro; isto é uma operação interna do processador. Se um ou ambos os operandos estão na memória, então o processador tem de efectuar alguma ou todas as seguintes acções:

  1. Calcular o endereço de memória, baseado no modo de endereçamento (discutido no capítulo 10).
  2. Se o endereço se referir a memória virtual, fazer a conversão de memória virtual para real.
  3. Determinar se o endereço do item está na cache.
  4. Se não, emitir comando para o módulo de memória.

Tabela 9.3: Operações comuns em jogos de instruções.
\begin{table}
\end{table}


Aritmética

A maior parte das máquinas oferece operações aritméticas básicas de soma, subtracção, multiplicação e divisão. Estas são invariavelmente fornecidas para números inteiros com sinal (ponto fixo). São, também, muitas vezes oferecidas para vírgula flutuante e números decimais empacotados.

Outras operações possíveis incluem uma variedade de instruções de um único operando; por exemplo:

A execução de uma instrução aritmética pode envolver operações de transferência de dados para a posição de operandos de entrada da ALU e para entregar resultados de saída da ALU. A figura 3.5 ilustra os movimentos associados a operações aritméticas e de transferência de dados. Adicionalmente, claro, a porção da ALU do processador efectua a operação pretendida.

Lógica

A maior parte das máquinas oferece, também, uma variedade de operações para a manipulação de bits isolados de uma palavra e de outras unidades endereçáveis, também designada como ''tamborilar de bits'. Estas são baseadas em operações boolenas (ver apêndice neste livro).

Tabela 9.4: Acções do processador em vários tipos de operações.
\begin{table}
\end{table}


Algumas das operações lógicas básicas que podem ser efectuadas em dados binários ou booleanos são mostradas na table 9.6. A operação NOT inverte um bit. AND, OR e OR exclusivo (XOR) são as operações lógicas de dois operandos mais comuns. EQUAL é um teste binário útil.

Estas operações lógicas podem ser aplicada em modo bit a unidades de dados lógicos com n-bits. Assim se dois registos contêm os dados
(R1) = 10100101
(R2) = 00001111
então
(R1) AND (R2) = 00000101
Desta forma, a operação AND pode ser usada como uma máscara que selecciona certos bits de uma palavra e põe a zero os bits que sobram. Como um outro exemplo, se dois registos (R1) = 10100101
(R2) = 11111111 então

(R1) XOR (R2) = 01011010 Com uma palavra com os bits todos a 1, a operação XOR inverte todos os bits na outra palavra (complemento para 1).

Em complemento das operações lógicas orientadas ao bit, a maior parte das máquinas fornecem uma variedade de funções de deslocação e de rotação.

Tabela 9.5: Exemplos de operações de transferências de dados no IBM S/370.
\begin{table}
\end{table}



Tabela 9.6: Operações lógicas básicas.
\begin{table}
\end{table}


As operações mais básicas são ilustradas na figura 9.5. Com a deslocação lógica, os bits de uma palavra são empurrados para a esquerda ou para a direita. Numa das extremidades, o bit empurrado perde-se. Na outra extremidade, um 0 é empurrado para dentro. A utilidade primária das deslocações lógicas é isolar campos dentro de uma palavra. 0s que são empurrados para dentro de uma palavra substituem informação não desejada que é empurrada para fora em direcção da outra extremidade.

Como um exemplo, suponha que pretendíamos transmitir dados em caracteres para um dispositivo de E/S, 1 carácter de cada vez. Se cada palavra da memória tiver 16 bits de comprimento e contiver 2 caracteres, devemos desempacotar os caracteres antes que estes possam ser transmitidos. Para enviar os dois caracteres na palavra,

  1. Carregar a palavra num registo.
  2. Fazer o AND com o valor 111111110000000. Isto mascara o carácter na direita.
  3. Empurrar para a direita oito vezes. Isto desloca o carácter restante para a metade direita do registo.
  4. Efectuar a E/S. O módulo de E/S vai ler os 8-bits inferiores do barramento de dados.
Os passos anteriores têm como resultado no envio do carácter à esquerda. Para enviar o carácter à direita,
  1. Carregar de novo um registo.
  2. Fazer o AND com o valor 000000011111111.
  3. Efectuar a E/S.

Figura 9.5: Operações de deslocação e de rotação.
\begin{figure}
\end{figure}

As operações de deslocação aritmética tratam os dados como inteiros com sinal e não empurram o bit de sinal. Numa deslocação aritmética para a direita, o bit de sinal é normalmente replicado no bit na posição à direita. Estas operações podem acelerar certas operações aritméticas. Com números em notação complemento para dois, uma deslocação para a esquerda ou para a direita corresponde, respectivamente, à multiplicação ou à divisão por 2, se estiver garantido que não excesso positivo ou negativo.

A operações de rotação, ou deslocação cíclica, preservam todos os bits operados. Um uso possível da rotação é trazer cada bit sucessivamente para a posição do bit mais à esquerda, onde este pode ser identificado através do teste do sinal do dado (tratado como número).

Da mesma forma que as operações aritméticas, as operações lógicas envolvem actividade da ALU e pode envolver operações de transferência de dados.

Conversão

As instruções de conversão são as que alteram o formato ou operam no formato dos dados. Um exemplo é a conversão de decimal para binário.

Um exemplo duma operação mais completa de conversão é a instrução de Transposição (TR) ) do S/370. Esta instrução pode ser usada para converter de um código de 8-bits para um outro e toma três operandos. TR R1, R2, L O operando R2 contém o endereço de início de uma tabela de códigos de 8-bits. Os L octetos começam no endereço especificado em R1 são convertidos, sendo cada octeto substituído pelo conteúdo da entrada de uma tabela indexada por aquele octeto, por exemplo para converter de EBCDIC para ASCII, começamos por criar uma tabela de 256 entradas em localizações para armazenamento, seja, 1000-10FF hexadecimal. A tabela contém os caracteres do código ASCII na sequência da representação binária do código EBCDIC; isto é, o código ASCII é colocado na tabela na localização relativa igual ao valor binário do código EBCDIC para o mesmo carácter. Deste modo, as localizações 10F0 até 10F9 deverão conter os valores 30 até 39, uma vez que F0 é o código EBCDIC para o dígito 0 e assim sucessivamente até ao dígito 9. Agora suponhamos que temos o EBCDIC para os dígitos 1984 a partir da localização 2100 e queríamos fazer a conversão para ASCII. Considere o que segue:
As localizações 2100-2103 contêm F1 F9 F8 F4.
R1 contém 2100.
R2 contém 1000.
Então se executarmos TR R1, R2, 4
as localizações 2100-2103 conterão 31 39 38 34.

Entrada/Saída

As instruções de entrada/saída foram discutidas com algum detalhe, no capítulo 6. Tal como vimos, há uma variedade de possíveis abordagens incluindo E/S isolada, E/S por correspondência da memória, DMA e o uso de processadores de E/S. Muitas implementações fornecem apenas umas poucas instruções para E/S, com as acções especificas descrita por parâmetros, códigos ou palavras de comando.

Controlo de Sistema

As instruções de controlo de sistema são geralmente instruções privilegiadas que podem ser executadas apenas quando o processador está num estado privilegiado ou está a executar um programa numa área especial privilegiada da memória. Tipicamente, estas instruções estão reservadas para o uso do sistema de exploração.

Alguns exemplos de operações de controlo de sistema são as seguintes. Uma instrução de controlo de sistema pode ler ou alterar um registo de controlo; discutiremos os registos de controlo no capítulo 11. Um outro exemplo é uma instrução para ler ou modificar uma chave de protecção de memória, tal como é usada pelo sistema de memória do S/370. Um outro exemplo é o acesso aos blocos de controlo de processos num sistema de multi-programação.

Transferência de Controlo

Para todos os tipos de operação discutidos atrás, a instrução especifica a operação a efectuar e os operandos. Implicitamente, a próxima instrução a ser executada é a que sucede, imediatamente, a instrução corrente na memória. Desta forma, seguindo o curso normal de acontecimentos, as instruções são executadas consecutivamente da memória. O processador incrementa simplesmente o contador de programa antes de extrair a próxima instrução.

Contudo, uma fracção significativa de instruções, em qualquer programa, tem como função alterar a sequência de execução das instruções. Para estas instruções, a operação efectuada pelo processador é actualizar o contador de programa para que contenha o endereço de uma qualquer instrução na memória.

Há um certo número de razões pelas quais são necessárias as operações de transferência de controlo. Entre as mais importantes estão:

Voltamo-nos agora para a discussão das operações mais comuns de transferência de controlo que se encontram em jogos de instruções:

Instruções de Derivação

Uma instrução de derivação, também chamada instrução de salto, tem como um dos operandos o endereço da próxima instrução a ser executada. A maior parte das vezes, a instrução é uma instrução derivação condicional. Isto é, a derivação (a actualização do contador de programa para o endereço especificado pelo operando) tem efeito, apenas, se ocorrer uma certa condição. Caso contrário, a próxima instrução na sequência é executada ( o contador de programa é incrementado como habitualmente).

Há duas formas comuns de gerar a condição a ser testada numa instrução de derivação condicional. Em primeiro lugar, a maioria das máquinas dispõe de um código de condição de 1-bit ou de múltiplos-bits que é ajustado de acordo como resultado de certas instruções. Este código pode ser pensado como um pequeno registo visível pelo utilizador. Como um exemplo, uma operação aritmética (ADD, SUBTRACT, e outras) pode ajustar uma condição de 2-bits para um de quatro valores: 0, positivo, negativo, excesso. Em tal máquina, poderiam existir quatro diferentes instruções de derivação condicional:
BRP X salto para a localização X se o resultado for positivo.
BRN X salto para a localização X se o resultado for negativo.
BRZ X salto para a localização X se o resultado for zero.
BRO X salto para a localização X se ocorrer excesso.

Em todos estes casos, o resultado referido corresponde ao resultado da mais recente operação que ajustou o código de condição.

Uma outra abordagem que pode ser usada com formatos de instrução com três endereços é efectuar uma comparação e especificar uma derivação na mesma instrução. Por exemplo
BRE R1, R2, X salto para X se o conteúdo de R1 = ao conteúdo de R2.

A figura 9.6 mostra exemplos destas operações. É de notar que a derivação pode ser para a frente (uma instrução com endereço mais elevado) ou para trás (endereço mais baixo). O exemplo mostra como uma instrução incondicional e uma derivação condicional podem ser usadas para criar um laço repetitivo de instruções. As instruções na localizações 202 até 210 deverão ser executadas repetidamente até que o resultado da subtracção de Y de X for 0.

Figura 9.6: Instruções de derivação.
\begin{figure}
\end{figure}

Instruções de Avanço

Um outro formato comum de instrução de transferência de controlo é a instrução de avanço. A instrução de avanço inclui um endereço implícito. Tipicamente, o avanço implica que uma instrução seja omitida; desta forma, o endereço implícito iguala o endereço da próxima instrução mais um comprimento da instrução.

Como a instrução de avanço não requer um campo endereço de destino, está livre para fazer outras coisas. Um exemplo típico é a instrução incremento-e-omissão-se-zero (ISZ). Considere o seguinte fragmento de programa:
301    
.    
.    
309 ISZ R1
310 BR 301
311    

Neste fragmento as duas instruções de transferência de controlo são usadas para implementar um laço iterativo. R1 é ajustado com o valor negativo do número de iterações para serem efectuadas. Na extremidade do laço R1 é incrementado. Se não for 0, o programa salta de novo para o início do laço. Caso contrário, o programa avança para a próxima instrução a seguir ao laço.

Instruções de Chamada a Sub-rotinas

A sub-rotina é talvez a mais importante inovação no desenvolvimento das linguagens de programação. Uma sub-rotina é um programa de computador independente que é incorporado num programa maior. Em qualquer ponto do programa a sub-rotina pode ser evocada, ou chamada. Isto é, naquele ponto, o computador é instruído para ir e executar a sub-rotina na sua totalidade e depois regressar ao ponto de onde foi efectuada a chamada.

As duas principais razões para o uso de sub-rotinas são a economia e a modularidade. Uma sub-rotina permite ao mesmo pedaço de código ser usado muitas vezes. Isto é importante para a economia de esforço do programador e para fazer o uso mais eficiente do espaço de armazenamento no sistema (o programa tem de ser guardado). As sub-rotinas também permitem aos programas grandes serem sub-divididos em unidades mais pequenas. Este uso da modularidade facilita substancialmente a tarefa do programador.

O mecanismo de sub-rotina envolve duas instruções básicas: uma instrução de chamada que salta da posição actual para a sub-rotina e uma instrução de retorno que retorna da sub-rotina para o lugar de onde foi chamada. Ambas são formas de instruções de derivação.

A figura 9.7 ilustra o uso de sub-rotinas para construir um programa. Neste exemplo, há um programa principal que começa na posição 4000.

Figura 9.7: Rotinas aninhadas.
\begin{figure}
\end{figure}

Este programa inclui uma chamada à sub-rotina SUB1, que começa na posição 4500. Quando é encontrada esta instrução de chamada, o processador suspende a execução do programa principal e inicia a execução de SUB1 extraindo a próxima instrução da localização 4500. Dentro de SUB1 há duas chamadas a SUB2 na localização 4800. Em cada caso, é suspensa a execução de SUB1 e SUB2 é executada. A instrução RETURN causa o processador voltar ao programa chamadar e continuar a execução na instrução a seguir à instrução correspondente de CALL. Este comportamento é ilustrado na figura 9.8.

Vários pontos são merecedores de nota:

  1. Uma sub-rotina pode ser chamada de mais do que uma localização.
  2. Uma chamada a sub-rotina pode aparecer numa sub-rotina. Isto permite ninhadas de sub-rotinas com profundidade arbitrária.
  3. Cada chamada a sub-rotina tem de fazer parelha com um retorno do programa chamado.

Figura 9.8: Sequência de execução para as rotinas aninhadas da figura 9.7.
\begin{figure}
\end{figure}

Uma vez que gostaríamos de ser capazes de chamar uma sub-rotina de uma multiplicidade de pontos, o processador deve de, alguma forma, guardar o endereço de retorno para que o retorno possa ocorrer de modo apropriado. Há três lugares habituais para guardar o endereço de retorno: Considere uma instrução CALL X, em linguagem máquina, que significa chamar uma sub-rotina na posição X. Se seguirmos a abordagem registo, CALL X provoca as seguintes acções:
RN $\leftarrow$ PC + $\Delta$
PC $\leftarrow$ X


sendo RN um registo que é sempre usado para este efeito, PC é o contador de programa e $\Delta$ o comprimento da instrução. A sub-rotina evocada pode agora guardar o conteúdo de RN para ser usada para o próximo retorno.

Uma segunda possibilidade é guardar o endereço de retorno no início da sub-rotina. Neste caso, CALL X tem como efeito
X $\leftarrow$ PC + $\Delta$
PC $\leftarrow$ X + 1


Isto é assaz conveniente. O endereço de retorno foi guardado seguramente.

Ambas as abordagens anteriores funcionam e têm sido usadas. A única limitação destas abordagens é que impedem o uso de sub-rotinas reentrantes.[#!a!#,#!b!#]. Uma sub-rotina reentrante é aquela para a qual é possível haver várias chamadas em aberto ao mesmo tempo. Um procedimento recursivo é um exemplo do uso desta propriedade[#!a!#,#!b!#].

Uma abordagem mais geral e mais poderosa é usar a pilha (ver apêndice 9A para uma definição de pilha). Quando o processador executa a chamada coloca o endereço de retorno na pilha. Quando efectiva o retorno, usa o endereço na pilha. A figura 9.9 ilustra o uso da pilha.

Figura 9.9: Uso da pilha para implementar as rotinas aninhadas da figura 9.7.
\begin{figure}
\end{figure}

Para além de fornecer um endereço de retorno, é também necessário, muitas vezes, passar parâmetros juntamente com a chamada da sub-rotina. Estes podem ser passados em registos. Uma outra possibilidade é guardar os parâmetros na memória imediatamente a seguir da instrução CALL. Neste caso, o retorno deve fazer-se para a localização a seguir aos parâmetros. De novo, os duas abordagens têm inconvenientes. Se forem usados os registos, o programa chamado e o programa chamador têm de ser escritos de forma a assegurar que os registos são usados de modo apropriado. O armazenamento dos parâmetros em memória torna difícil trocar um número variável de parâmetros. E ambas as abordagens impedem o uso de sub-rotinas reentrantes.

Uma abordagem mais flexível à passagem de parâmetros é a pilha. Quando o processador executa uma chamada, não empilha, apenas, o endereço de retorno, empilha os parâmetros a serem passados ao procedimento chamado. O procedimento evocado pode ter acesso aos parâmetros através da pilha. Aquando do retorno, os parâmetros de retorno podem ser colocados na pilha, por baixo do endereço de retorno. O conjunto total de parâmetros, incluindo o endereço de retorno que são guardados durante a evocação do procedimento são designados por caixilho de pilha9.1

Figura 9.10: Crescimento do caixilho de pilha em procedimentos simples, P e Q.
\begin{figure}
\end{figure}

Um exemplo é apresentado na figura 9.10. O exemplo refere-se ao procedimento P, em que são declaradas as variáveis locais x1 e x2, e ao procedimento Q, em que são declaradas as variáveis locais y1 e y2, e que pode ser chamado por P. Naquela figura, o ponto de retorno para cada procedimento é o primeiro item a ser guardado no caixilho de pilha correspondente. A seguir é guardado um apontador para o início do caixilho anterior. Isto é necessário se o número ou tamanho dos parâmetros forem variáveis.

Tipos de operação do Pentium

O Pentium oferece uma complexa lista de tipos de operações, incluindo um número de instruções especializadas. O objectivo era fornecer os instrumentos para que quem escreve o compilador possa produzir conversões optimizadas para linguagem máquina de programas em linguagens de alto-nível. A tabela 9.7 lista e mostra exemplos de cada um dos tipos. Muitos são instruções convencionais que podem ser encontradas na maior parte dos jogos de instruções, mas vários tipos de instruções são feitos à medida das arquitecturas 80x86/Pentium e são de particular interesse .

Instruções de Chamada/Retorno

O Pentium oferece quatro instruções para dar suporte à chamada/retorno de sub-rotinas. CALL, ENTER, LEAVE, RETURN. É instrutivo olhar para o suporte dado a estas instruções. Lembrar da figura 9.10 que uma forma habitual de implementar a chamada/retorno de sub-rotinas é através do uso de caixilhos de pilha. Quando é chamado um novo procedimento, deve ser efectuado o seguinte após a entrada no novo procedimento: A instrução CALL põe o apontador para a instrução corrente na pilha e provoca o salto para o ponto de entrada do procedimento através da colocação do endereço do ponto de entrada no apontador de instrução. Nas máquinas 8088 e 8086, o procedimento típico começa com a sequência:
PUSH EBP
MOV EBP,ESP
SUB ESP, espaço para locais

Onde EBP é o apontador de caixilho e ESP o apontador de pilha. No 80286 e máquinas mais tardias, a instrução de ENTER efectua todas as operações acima numa única instrução.

A instrução ENTER foi acrescentada ao jogo de instruções para fornecer suporte directo ao compilador. A instrução também inclui uma característica para dar suporte aos chamados procedimentos aninhados em linguagens tais como PASCAL, COBOL e ADA (que não se encontram em C ou Fortran). Procedente controverso, uma vez que há melhores maneiras de manipular procedimentos aninhados nestas linguagens. Além disso, embora a instrução ENTER poupe alguns octetos de memória quando comparada com a sequência PUSH, MOV, SUB (4 octetos versus 6 octetos) na verdade leva mais tempo a ser executada (11 ciclos de relógio versus 1,5 ciclos de relógio).

Tabela 9.7: Tipos de operação do Pentium.
\begin{table}
\end{table}


Assim, embora possa parecer ser uma boa ideia do projectista do jogo de instruções adicionar esta possibilidade, isto vem complicar a implementação do processador trazendo um pequeno ou nenhum benefício. Veremos que em contraste, a abordagem RISC ao desenho de processadores evita instruções complexas, tais como a ENTER e pode produzir uma implementação mais eficiente com uma sequência de instruções mais simples.

Gestão de Memória

Um outro jogo de instruções especializadas trata da segmentação da memória. Estas são instruções com privilégios que apenas podem ser executadas pelo sistema de operação. Permitem que tabelas de segmentos locais e globais (chamadas tabelas de descriptores) possam ser carregadas e lidas, e testados e alterados os níveis de privilégio de um segmento.

As instruções especiais para tratar com a memória cache no integrado já foram discutidas no capítulo 5.

Códigos de condição

Mencionámos que os códigos de condição são bits em registos especiais que podem ser alterados por certas operações e usados em instruções de derivação condicional. Estas condições são ajustadas por operações aritméticas e de comparação. A operação de comparação na maior parte das linguagens subtraem dois operandos, tal como a operação de subtracção. A diferença é que a operação de comparação apenas ajusta os códigos de condição, enquanto que a operação de subtracção também guarda o resultado da subtracção no operando de destino.

Tabela 9.8: Códigos de condição do Pentium.
\begin{table}
\end{table}


A tabela 9.8 lista os códigos de condição usados pelo Pentium. Cada condição ou combinação daquelas condições, pode ser testada para saltos condicionais. A tabela 9.9 mostra a combinação de condições para as quais foram definidas códigos de derivação condicional.

Várias observações com interesse podem ser feitas sobre aquela lista. Em primeiro lugar podemos pretender testar dois operandos para determinar se um número é maior do que outro. Mas isto irá depender dos números terem sinal ou não. Por exemplo, o número de 8-bits 11111111 é maior do que 00000000 se os dois números forem interpretados como inteiros sem sinal (255 > 0), mas é menor se estes forem considerados como números de 8-bits em complemento para dois. (-1<0). Consequentemente, muitas linguagens de montagem introduzem dois conjuntos de termos para distinguir os dois casos.: Se estivermos a comparar dois números como inteiros com sinal usamos o termos menor que e maior que; se estivermos a compará-los como inteiros sem sinal usamos os termos inferior e superior.

Tabela 9.9: Códigos de condição do Pentium para instruções de salto condicional e SET.
\begin{table}
\end{table}


Uma segunda operação está relacionada com a complexidade da comparação de inteiros com sinal. Um resultado com sinal é maior ou igual a zero se (a) o sinal é zero e não há excesso (S= 0 e O = 0), ou (b) o bit de sinal é um e há excesso. O estudo da figura 8.4 deverá convencê-lo que as condições testadas para as várias operações com sinal são apropriadas (ver problema 9.14)

Tipos de operação do PowerPC

O PowerPc oferece uma vasta gama de tipos de operação. A tabela 9.10 lista os tipos e dá exemplos de cada um. São de realçar várias características.

Instruções de Derivação

O PowerPc suporta as habituais capacidade de derivação condicional e não-condicional. As instruções de derivação condicional fazem o teste de verdade, falsidade, ou qualquer-valor de um bit singular do registo de condição, ou o teste de zero, não-zero ou qualquer-valor, do conteúdo do registo de contagem. Assim, há nove operações separadas de condição que podem ser definidas para as instruções de derivação. Se o registo é sujeito ao teste de zero ou de não-zero, então é diminuído de 1 imediatamente antes do teste. Isto é apropriado para ajustar os laços iterativos.

Tabela 9.10: Tipos de operação do PowerPc (com exemplos de operações típicas).
\begin{table}
\end{table}


As instruções de derivação podem também indicar que o endereço de posição a seguir à derivação deverá ser posto no registo de elo descrito no capítulo 13. Isto facilita o processo de chamada e de retorno.

Instruções de Carregar/Guardar

Na arquitectura do PowerPc, apenas as instruções de carregar e de guardar fazem acesso a posições da memória. Isto é característico do projecto RISC e é explorado mais adiante no capítulo 12.

Há duas particularidades que caracterizam as diferentes instruções de carregar/guardar:


Linguagem de Montagem

Um processador pode compreender e executar instruções máquina. Tais instruções são simplesmente números binários guardados no computador. Se um programador pretendesse programar directamente em linguagem máquina, então, seria necessário introduzir o programa como dados binários.

Considere a declaração simples em BASIC
N = I + J + K

Suponha que pretendíamos programar aquela declaração em linguagem máquina e iniciar I, J e K com 2, 3 e 4 respectivamente. Isto é mostrado na figura 9.11a. O programa arranca na posição 101 (hexadecimal). É reservada memória para as quatro variáveis a partir da posição 201. O programa compreende quatro instruções:

  1. Carregar o conteúdo da posição 201 em AC.
  2. Somar o conteúdo da posição 202 a AC.
  3. Somar o conteúdo da posição 203 a AC.
  4. Guardar o conteúdo do AC na posição 204.

Isto é, claramente, um processo tedioso e muito propenso a erros.

Um melhoramento pouco significativo é escrever o programa em notação hexadecimal em vez de binária (Figura 9.11b). Poderíamos escrever o programa como uma série de linhas. Cada linha contém o endereço da posição de memória e o código hexadecimal do valor binário a guardar naquela posição. Então precisamos de um programa que aceita esta entrada, converta cada linha num número binário e guarde o valor na posição especificada.

Isto é apenas um pequeno melhoramento. Para fazer muito melhor, podemos recorrer ao nome simbólico ou mnemónica de cada instrução. Usemos esta hipótese em vez do opcode efectivo. Isto resulta no programa simbólico mostrado na figura 9.11c. Cada linha de entrada representa uma posição da memória. Cada linha consiste em três campos, separados por espaços. O primeiro campo contém o endereço da posição. Para uma instrução, o segundo campo contém o símbolo de três letras para o opcode. Se é uma instrução de referência à memória, então um terceiro campo contém o endereço. Para guardar dados arbitrários numa posição, inventamos uma pseudo-instruções com o símbolo DAT. Isto é uma mera indicação de que o terceiro campo da linha contém um número hexadecimal para ser guardado na posição especificada no primeiro campo.

Figura 9.11: Cálculo da fórmula N = I + J + K.
\begin{figure}
\end{figure}

Para este tipo de entrada necessitamos de um programa um ligeiramente mais complexo. O programa aceita cada linha de entrada, gera um número binário baseado no segundo e terceiro (se presente) campos e guarda-o na posição especificada pelo primeiro campo.

O uso de um programa simbólico torna a vida mais fácil mas é ainda difícil de manejar. Em particular, temos de dar um endereço absoluto a cada palavra. Isto quer dizer que o programa e os dados podem ser carregados somente num lugar da memória e temos de conhecer, antes de tudo, aquele lugar. Pior, suponhamos que pretendemos mudar o programa dias mais tarde através da adição ou remoção de uma linha. Isto mudaria o endereços de todas as palavras subsequentes.

Um sistema muito melhor, usado habitualmente, é usar endereços simbólicos. Isto é ilustrado na figura 9. 11(d). Cada linha continua a consistir de três campos. O primeiro campo é ainda para o endereço, mas é usado um nome simbólico em vez de um endereço numérico absoluto. Algumas linhas não têm endereços, o que implica que o endereço daquelas linhas é mais um do que o endereço da linha prévia. Para as instruções de referência à memória, o terceiro campo também contém um endereço simbólico.

Com este último refinamento inventamos a linguagem de montagem9.2. Os programas escritos em linguagem de montagem (programas para montagem) são traduzidos em linguagens máquina por um montador. Este programa tem não apenas de fazer a conversão simbólica discutida anteriormente mas também de atribuir, de alguma forma, endereços de memória aos endereços simbólicos.

O desenvolvimento da linguagem de montagem foi um marco maior da evolução da tecnologia dos computadores. Foi o primeiro passo para as linguagens de alto-nível, hoje em uso. Embora poucos programadores usem linguagem máquina, virtualmente todas as máquinas dispõem de uma. Estas são usadas, quando usadas, por programas de sistemas tais como compiladores e rotinas de E/S.


next up previous contents
Next: Jogos de Instruções: Modos Up: Arquitectura e Organização Interna Previous: Aritmética do Computador

2000-05-10