Sebenta teórica on-line - Manuel Cabral Reis
Transcrição
Sebenta teórica on-line - Manuel Cabral Reis
Introdução à Arquitectura de Computadores Série Didáctica CIÊNCIAS APLICADAS 245 Manuel José Cabral dos Santos Reis António Manuel Silva Pinto Soares Universidade de Trás-os-Montes e Alto Douro Vila Real Índice 1 2 Introdução 1.1 Enquadramento e objectivos 1.2 Organização . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12 4 . . . . . . . . . . . . . . . . . . . . . . . . . 11 Generalidades 15 2.1 Introdução . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 2.2 Conceitos e palavras chave 2.3 Descrição geral de um micro-processador 2.4 Estrutura interna de um CPU 2.5 3 11 . . . . . . . . . . . . . . . . . . . . . . . . . . 21 . . . . . . . . . . . . . . . . . . 25 . . . . . . . . . . . . . . . . . . . . . . . . 28 2.4.1 Unidade Aritmética e Lógica (ALU) . . . . . . . . . . . . . . . . . 30 2.4.2 Unidade de Temporização e Controlo Arquitectura de um micro-processador . . . . . . . . . . . . . . . . 31 . . . . . . . . . . . . . . . . . . . 34 2.5.1 Formato e processamento das instruções . . . . . . . . . . . . . . 36 2.5.2 Registos internos de uma arquitectura básica . . . . . . . . . . . . 37 2.6 Arquitectura do Z80 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40 2.7 Memória . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43 Ferramentas para programação de um micro-processador 47 3.1 Introdução . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47 3.2 Formato de programação . . . . . . . . . . . . . . . . . . . . . . . . . . . 47 3.3 Pseudo-instruções . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48 3.4 Desenvolvimento de um programa em assembly . . . . . . . . . . . . . . . 48 3.5 Assembler 3.6 Loaders . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51 3.7 Macros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49 Estudo do conjunto de instruções 55 4.1 Introdução . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55 4.2 Formato simbólico das instruções 4.3 Flags . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55 4.4 Tipos de endereçamento . . . . . . . . . . . . . . . . . . . . . . 55 . . . . . . . . . . . . . . . . . . . . . . . . . . . 56 i 4.5 4.4.1 Modo registo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57 4.4.2 Absoluto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57 4.4.3 Imediato . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57 4.4.4 Registo indirecto 4.4.5 Auto-incremento e auto-decremento . . . . . . . . . . . . . . . . . 59 4.4.6 Indexado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60 4.4.7 Base 4.4.8 Base-indexado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61 4.4.9 Relativo Instruções 4.5.1 5 . . . . . . . . . . . . . . . . . . . . . . . . . . . 59 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63 Transferência de informação . . . . . . . . . . . . . . . . . . . . . 63 4.5.1.1 Modo absoluto . . . . . . . . . . . . . . . . . . . . . . . 63 4.5.1.2 Modo imediato . . . . . . . . . . . . . . . . . . . . . . . 64 4.5.1.3 Modo base . . . . . . . . . . . . . . . . . . . . . . . . . . 64 4.5.1.4 Modo registo indirecto . . . . . . . . . . . . . . . . . . . 64 4.5.1.5 Outros exemplos de instruções de transferência . . . . . 65 4.5.2 Manipulação de blocos . . . . . . . . . . . . . . . . . . . . . . . . 65 4.5.3 Instruções aritméticas . . . . . . . . . . . . . . . . . . . . . . . . . 67 4.5.3.1 Aritmética de 8 bits . . . . . . . . . . . . . . . . . . . . 67 4.5.3.2 Aritmética de 16 bits . . . . . . . . . . . . . . . . . . . . 68 4.5.4 Instruções lógicas . . . . . . . . . . . . . . . . . . . . . . . . . . . 69 4.5.5 Manipulação de bits . . . . . . . . . . . . . . . . . . . . . . . . . . 70 4.5.6 Controlo de programa . . . . . . . . . . . . . . . . . . . . . . . . . 71 4.5.7 Deslocamento e rotação . . . . . . . . . . . . . . . . . . . . . . . . 74 4.5.8 Grupo aritmético de propósito geral . . . . . . . . . . . . . . . . . 75 4.5.9 Grupo de controlo do CPU . . . . . . . . . . . . . . . . . . . . . . 76 4.6 Stack . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76 4.7 Subrotinas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78 4.8 Passagem de parâmetros . . . . . . . . . . . . . . . . . . . . . . . . . . . 79 4.8.1 Registos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79 4.8.2 Área de memória 4.8.3 Stack . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80 . . . . . . . . . . . . . . . . . . . . . . . . . . . 80 Entrada e Saı́da 83 5.1 Introdução . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83 5.2 Mapas de endereçamento . . . . . . . . . . . . . . . . . . . . . . . . . . . 83 5.3 Interfaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86 5.3.1 Protocolo de programação para periféricos de saı́da 5.3.2 Protocolo de programação para periféricos de entrada . . . . . . . 91 ii . . . . . . . . 88 5.3.3 Protocolo de programação num sistema computacional com periféricos de entrada e de saı́da . . . . . . . . . . . . . . . . . . . . . 94 5.4 Tipos de interfaces 5.4.1 Interface paralela 5.4.1.1 5.4.2 . . . . . . . . . . . . . . . . . . . . . . . . . . . 96 Estudo da interface paralela Z80 PIO . . . . . . . . . . . 96 Interface série 5.4.2.1 6 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101 Estudo da interface série Am8251 . . . . . . . . . . . . . 103 Interrupções 109 6.1 Introdução . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109 6.2 Considerações gerais 6.3 Interrupções múltiplas e prioridades . . . . . . . . . . . . . . . . . . . . . 111 6.4 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109 6.3.1 Polling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112 6.3.2 Vector de interrupção . . . . . . . . . . . . . . . . . . . . . . . . . 113 Interrupções no Z80 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115 6.4.1 Interrupções mascaráveis . . . . . . . . . . . . . . . . . . . . . . . 115 6.4.1.1 Modo 0 (interrupção vectorizada) . . . . . . . . . . . . . 117 6.4.1.2 Modo 1 (interrupção por pesquisa) 6.4.1.3 Modo 2 (interrupção vectorizada) . . . . . . . . . . . . . 117 . . . . . . . . . . . . 117 6.4.2 Interrupções não mascaráveis . . . . . . . . . . . . . . . . . . . . . 119 6.4.3 Programa de entrada/saı́da usando interrupções iii . . . . . . . . . . 121 iv Índice de figuras 2.1 Diagrama de blocos de uma arquitectura tipo Harvard. . . . . . . . . . . . 17 2.2 Diagrama de blocos de uma arquitectura tipo von Newmann. . . . . . . . . 18 2.3 Diagrama de blocos do processador de sinal TMS32010. . . . . . . . . . . . 20 2.4 Diagrama de blocos de uma máquina programável tı́pica. . . . . . . . . . . 26 2.5 Diagrama de blocos de um sistema de computador tı́pico. . . . . . . . . . . 27 2.6 Diagrama temporal associado ao processo de leitura de dados da memória. 28 2.7 Processo tı́pico de transferência de informação entre dois registos. . . . . . 29 2.8 Diagrama de blocos do CPU Z80. . . . . . . . . . . . . . . . . . . . . . . . 30 2.9 Diagrama de blocos de uma secção da ALU. . . . . . . . . . . . . . . . . . 31 2.10 Unidade de temporização e controlo micro-programada. . . . . . . . . . . . 32 2.11 Exemplo de micro-programa. . . . . . . . . . . . . . . . . . . . . . . . . . . 33 2.12 Esquema de ligações externas do Z80. . . . . . . . . . . . . . . . . . . . . . 34 2.13 Diagrama temporal correspondente à fase de fetch. . . . . . . . . . . . . . 36 2.14 Diagrama de blocos de um conjunto mı́nimo de registos. . . . . . . . . . . 38 2.15 Instruções de rotação para a esquerda e para a direita. . . . . . . . . . . . 38 2.16 Diagrama de fluxo para o algoritmo do exemplo 4. . . . . . . . . . . . . . . 40 2.17 Registos do Z80 visı́veis ao programador. . . . . . . . . . . . . . . . . . . . 41 2.18 Exemplificação da utilização dos index registers. . . . . . . . . . . . . . . . 42 2.19 Exemplificação da utilização da stack. . . . . . . . . . . . . . . . . . . . . . 42 2.20 Exemplo de memória organizada em bits. . . . . . . . . . . . . . . . . . . . 43 2.21 Exemplo de memória organizada em bytes. . . . . . . . . . . . . . . . . . . 44 2.22 Diagrama temporal dos sinais afectos ao ciclo de leitura da memória. . . . 45 2.23 Diagrama temporal dos sinais afectos ao ciclo de escrita na memória. . . . 45 3.1 Diagrama de fluxo das fases de desenvolvimento de um programa escrito em assembly. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50 3.2 Exemplo de funcionamento de um loader. . . . . . . . . . . . . . . . . . . . 51 4.1 Modo de endereçamento tipo registo. . . . . . . . . . . . . . . . . . . . . . 57 4.2 Modo de endereçamento tipo absoluto. . . . . . . . . . . . . . . . . . . . . 58 4.3 Modo de endereçamento tipo registo indirecto. . . . . . . . . . . . . . . . . 59 v 4.4 Modos de endereçamento tipo auto-incremento e auto-decremento. . . . . . 60 4.5 Modo de endereçamento tipo indexado. . . . . . . . . . . . . . . . . . . . . 60 4.6 Modo de endereçamento tipo base. . . . . . . . . . . . . . . . . . . . . . . 61 4.7 Modo de endereçamento tipo base-indexado. . . . . . . . . . . . . . . . . . 61 4.8 Modo de endereçamento tipo relativo. . . . . . . . . . . . . . . . . . . . . . 62 5.1 Exemplo de mapa de endereçamento. . . . . . . . . . . . . . . . . . . . . . 84 5.2 Outro exemplo de mapa de endereçamento. . . . . . . . . . . . . . . . . . . 85 5.3 Exemplo de endereçamento por linha de I/O. . . . . . . . . . . . . . . . . . 87 5.4 Esquema geral de interligação de uma interface. . . . . . . . . . . . . . . . 88 5.5 Diagrama de fluxo do protocolo de programação para saı́da de dados. . . . 89 5.6 Esquema de interligação da interface do exemplo 15. . . . . . . . . . . . . . 89 5.7 Diagrama de fluxo para saı́da de dados. . . . . . . . . . . . . . . . . . . . . 91 5.8 Diagrama de fluxo do protocolo de programação para entrada de dados. . . 92 5.9 Esquema de interligação da interface do exemplo 16 . . . . . . . . . . . . . 92 5.10 Diagrama de fluxo para entrada de dados. . . . . . . . . . . . . . . . . . . 93 5.11 Diagrama de blocos dum sistema computacional com periféricos de entrada e periféricos de saı́da. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94 5.12 Diagrama de fluxo do protocolo para entrada e saı́da de dados. . . . . . . . 95 5.13 Diagrama de blocos de interligação de uma interface paralela. . . . . . . . 96 5.14 Diagrama de blocos do circuito Z80 PIO. . . . . . . . . . . . . . . . . . . . 98 5.15 Diagrama de blocos das portas A e B do circuito Z80 PIO. . . . . . . . . . 99 5.16 Esquema de ligações do circuito Z80 PIO. . . . . . . . . . . . . . . . . . . 99 5.17 Programação das interrupções do circuito Z80 PIO. . . . . . . . . . . . . . 100 5.18 Exemplo de interligação de uma interface série. . . . . . . . . . . . . . . . 102 5.19 Transmissão série assı́ncrona. . . . . . . . . . . . . . . . . . . . . . . . . . 103 5.20 Diagrama de blocos do circuito Am8251. . . . . . . . . . . . . . . . . . . . 104 5.21 Códigos de programação do Am8251 nos modos sı́ncrono ou assı́ncrono. . . 106 5.22 Códigos de programação do Am8251: palavras de comando. . . . . . . . . 107 5.23 Códigos de programação do Am8251: consulta do registo de estado. . . . . 107 6.1 Diagrama de fluxo genérico de uma rotina de serviço à interrupção. . . . . 111 6.2 Exemplo de atendimento de interrupções por polling. . . . . . . . . . . . . 112 6.3 Exemplo de atendimento de interrupções por vector de interrupção. . . . . 114 6.4 Estabelecimento de prioridades no atendimento de interrupções com recurso ao esquema Daisy Chain. . . . . . . . . . . . . . . . . . . . . . . . . 116 6.5 Esquema resumido do sistema de interrupções do Z80. . . . . . . . . . . . . 116 6.6 Diagrama de fluxo de atendimento das interrupções para o modo 0 do Z80. 118 6.7 Diagrama de fluxo de atendimento das interrupções para o modo 1 do Z80. 118 6.8 Exemplo de tabela de interrupções para o modo 2 do Z80. . . . . . . . . . 119 vi 6.9 Diagrama de fluxo de atendimento das interrupções para o modo 2 do Z80. 120 6.10 Diagrama de fluxo de atendimento das interrupções não mascaráveis do Z80.121 6.11 Diagrama de fluxo do exemplo 21. . . . . . . . . . . . . . . . . . . . . . . . 123 vii viii Índice de tabelas 2.1 Algumas das caracterı́sticas presentes na famı́lia de processadores Intel. . . 21 2.2 Conjunto de instruções mı́nimo para a arquitectura proposta. . . . . . . . . 39 5.1 Operação funcional do Am8251 (USART). . . . . . . . . . . . . . . . . . . 105 6.1 Sumário do processo de interrupções do Z80. . . . . . . . . . . . . . . . . . 124 ix x Capı́tulo 1 Introdução 1.1 Enquadramento e objectivos Estes apontamentos foram escritos com o objectivo de servirem como “texto de apoio” à disciplina de Arquitectura de Computadores dos cursos de Informática e de Comunicações e Multimédia, funcionando ambos no 1o ano, 2o semestre. Não pretendem de forma alguma ser auto-suficientes. O aluno é totalmente encorajado e incitado a consultar outras referências bibliográficas, nomeadamente as que se apresentam de seguida e as que constam da lista apresentada no final destes apontamentos. Claro que esta lista não é, nem pretende ser, completa, mas sim indicativa. Para além destas referências, tidas como tradicionais, pensamos ser conveniente indicar locais e fontes alternativas onde pode ser encontrada informação geralmente actualizada a uma frequência superior à que é possı́vel nos livros e revistas impressas. Recomenda-se uma visita periódica a http://www.cs.wisc.edu/~arch/www/online.html onde se podem encontrar as mais diversas informações na área da arquitectura de computadores, onde se inclui bibliotecas digitais, periódicos (revistas) on-line e bibliografia. Por outro lado, em http://www.handshake.de/user/kroening/conferences.html pode ser encontrada uma lista actualizada de publicações e conferências internacionais na área da arquitectura de computadores. A partir de http://www.utad.pt/~mcabral/ estão diponı́veis as informações mais recentes relativas à disciplina. Nestas páginas é possı́vel saber, por exemplo, o número de faltas, as normas de avaliação, enunciados de exames anteriores, etc.. 11 Para acompanhamento das aulas práticas também aconselhamos a consulta dos manuais [1, 2, 3]. Esta disciplina recorre a conceitos ou noções introduzidas em disciplinas ou áreas tão diversas como a programação, a electrónica digital, processamento digital de sinal, etc., devendo por outro lado servir de base comum a todas elas. Os objectivos principais de uma disciplina deste tipo são muito gerais. Assim, pretende-se que o aluno estude e compreenda no essencial: • O que é uma arquitectura; • Programe essa arquitectura. Para isso vamos realçar os princı́pios básicos de uma arquitectura genérica e muito simples e não um conjunto detalhado de instruções de uma arquitectura particular. Contudo, sem prejuı́zo no que se refere à simplicidade, também será estudada em pormenor a arquitectura do Z80. Optámos pelo estudo da arquitectura do Z80 devido, essencialmente, à sua simplicidade, se encontrarem disponı́veis no mercado diferentes versões e ser utilizado na indústria (geralmente como micro-controlador). A tı́tulo de exemplo, note-se que algumas arquitecturas de jogos assentam na emulação de dois ou mais micro-processadores Z80. Repare-se ainda que apesar do Z80 possuir uma arquitectura simples as suas unidades principais estão presentes nos micro-processadores mais complexos e actuais. Mais, a forma como algumas unidades se encontram implementadas continua a manter-se actual. Deve ter-se também sempre presente que a discussão não se limita à arquitectura do Z80, sendo geralmente este o ponto de partida para outras soluções. Por exemplo exemplo, a discução que será apresentada no capı́tulo 2, secção 2.4.2, sobre a unidade de temporização e controlo não se limitará à forma como esta unidade é implementada no Z80. Como advertência final deve ser referido que a leitura e estudo destes apontamentos não dispensa de forma alguma a assistência às aulas, sendo considerado de primordial importância a sua frequência. 1.2 Organização Estes apontamentos encontram-se organizados em seis capı́tulos. Este primeiro capı́tulo dispensa apresentação. No capı́tulo 2 começa-se pela introdução de conceitos e definições gerais essenciais aos conteúdos aqui tratados. Apresenta-se também um resumo da evolução dos (micro)processadores. É também feita uma descrição geral de um micro-processador tı́pico, da sua estrutura interna e da sua arquitectura. Depois de apresentadas estas noções e conceitos são estudadas as formas como estes são implementados no caso do Z80. Na parte final é 12 estudada a memória. O aluno é encorajado a ler integralmente este capı́tulo (idealmente os apontamentos completos) para uma perspectiva geral da evolução e constituição de um sistema de computador. O capı́tulo 3 é dedicado à forma como se pode programar um (micro)processador. É dada especial atenção à programação em assembly. São estudadas matérias como o formato de programação, as pseudo-instruções, as fases de desenvolvimento de um programa em assembly, os assemblers, os loaders e as macros. No capı́tulo 4 é proposto e estudado um conjunto de instruções genérico. Para este efeito primeiro apresenta-se o formato simbólico das instruções, o significado e a forma como as flags são afectadas pelas instruções e ainda os tipos de endereçamento. Só depois são estudados os grupos de instruções. Na parte final deste capı́tulo é estudada a stack e as subrotinas. Claro está que não se pode falar de subrotinas sem falar em passagem de parâmetros. Para que um (micro)processador efectue trabalho útil é necessária a “interligação” deste ao mundo que o rodeia. O capı́tulo 5 é dedicado a este assunto. São estudados os mapas de endereçamento e as interfaces, onde se incluem os protocolos de programação, tanto para entrada como para saı́da de dados. São também estudadas tanto as interfaces série como as interfaces paralelo. Os casos dos circuitos Z80PIO e Am8251 são vistos detalhadamente. Por último serão estudadas, no capı́tulo 6, as interrupções. Como será visto neste capı́tulo, o sistema de interrupções permite agelizar fortemente, entre outros, os processos de entrada e saı́da de dados num sistema de (micro)computador. Começa-se por definições e conceitos gerais, sendo depois estudadas as interrupções múltiplas e as prioridades. Para este efeito serão estudadas as técnicas de polling e de vector de interrupção. Na parte final do capı́tulo será estudada a forma como as interrupções são implementadas no Z80. Será feita a distinção entre interrupções mascaráveis e não mascaráveis. Convém deixar claro que, sendo esta área da arquitectura de computadores “adulta” e estando nós a tratar de noções consideradas introdutórias ou mesmo básicas, não é nosso objectivo inovar no que concerne directamente ao aspecto com que os esquemas ou diagramas de blocos de certas unidades são apresentados. Preferimos apresentá-los reflectindo a forma como são desenhados pelos próprios fabricantes e na esmagadora maioria da literatura nesta área. Desta forma contribuimos para não causar os conflitos ou confusões que muitas vezes são detectados, devido essencialmente à falta de experiência dos alunos destes estágios iniciais. Assim, o mérito (ou demérito) da maioria dos esquemas e diagramas aqui apresentados não nos deve ser atribuı́do. 13 14 Capı́tulo 2 Generalidades 2.1 Introdução A primeira questão que se levanta é a de saber “o que é um computador?” Obviamente que a resposta não é simples nem única. Podemos dizer que é uma sala cheia de equipamento, a fazer muito barulho, com muitas luzes a acender e a apagar (pode estar a controlar uma central nuclear, ou os processos necessários a uma grande organização ou empresa, calculando inventários, facturas, etc.; controlando os salários de milhares de funcionários, etc.; enfim, faz tudo menos ir às compras e cozinhar!). Também se pode dizer que é um conjunto de equipamentos, juntamente com um terminal, colocados a um canto de um laboratório de um cientista e que controla ou monitoriza o progresso de uma determinada experiência (eventualmente, depois da experiência completa, apresenta resultados traçando gráficos e analisando resultados). Pode ainda ser considerado como uma caixa pequena que tem um teclado, um écran, um altifalante, uma impressora e uma ranhura onde se introduzem disquetes. Geralmente estes equipamentos permitem guardar informação respeitante à correspondência pessoal, contas bancárias. Pode também ser utilizado para servir de adversário, por exemplo, num jogo de xadrez, etc.. Alternativamente, pode ser visto como um circuito electrónico miniaturizado, que tem apenas uma função, encontrado regularmente nos electrodomésticos, automóveis, brinquedos, etc.. Todas estas respostas são válidas se falarmos genericamente! Mas, se quisermos ser mais precisos, temos que fazer a seguinte divisão (seguindo a mesma ordem de ideias): • Main frames; • Mini-computadores; • Microcomputadores; • Micro-controladores. 15 Neste curso vamos dar especial ênfase ao estudo dos microcomputadores. Vamos analisar em pormenor os diferentes blocos que os constituem e os vários nı́veis em que estão estruturados. O nı́vel mais baixo é conhecido por hardware, ou seja, a parte fı́sica da máquina (os componentes electrónicos). O nı́vel seguinte consiste na interconexão entre os elementos de hardware e as estruturas de interface com o utilizador/operador do equipamento. O software constitui o nı́vel mais elevado, sendo composto pelo conjunto de instruções (programa) que faz com que o computador execute trabalho útil! Existem autores que consideram apenas dois nı́veis: hardware e software, classificando as estruturas de interface como pertencentes quer ao hardware quer ao software. Quando olhamos para o desenvolvimento histórico dos circuitos especiais (num único chip), devemos considerar a arquitectura e a tecnologia dos dispositivos separadamente. Tal como sucedeu com os dispositivos semicondutores, as estruturas básicas já estavam bem definidas antes da tecnologia que as podia suportar. As arquitecturas gerais para computadores e micro-computadores (computadores baseados num único chip para a Unidade Central de Processamento — UCP ou CPU) podem ser dividias em dois grandes grupos ou categorias. A primeira grande arquitectura de um computador electro-mecânico possuı́a espaços de memória separados para as instruções de programa e para os dados. Isto permitia o acesso simultâneo aos dados e às instruções de programa (ao programa). Esta tipologia de arquitectura, que ficou conhecida como arquitectura tipo Harvard, surgiu no final dos anos 30 em Harvard, tendo sido desenvolvida pelo fı́sico Howard Aiken. O primeiro computador deste tipo ficou operacional em 1944 e chamava-se Harvard Mark 1. O primeiro computador electrónico de utilização genérica foi provavelmente o ENIAC (Electronic Numerical Integrator and Calculator), tendo sido construı́do entre 1943 e 1946 na Universidade da Pennsylvania. A sua arquitectura era similar à usada no Harvard Mark 1 (memória de dados e de programa separadas). Esta arquitectura é apresentada esquematicamente na figura 2.1. Devido à complexidade das memórias de dados e programas em separado, este tipo de arquitectura não se tornou popular em computadores e micro-computadores de utilização genérica. Um dos consultores do ENIAC foi John von Neumann, um matemático de origem Húngara. von Neumann foi mais tarde reconhecido como o criador de uma arquitectura muito diferente, a qual foi publicada por Burks, Goldstine e von Neumann, em 1946. A então chamada arquitectura de von Neumann, definiu o padrão ou standard para a arquitectura dos computadores nos mais de quarenta anos seguintes. A ideia era muito simples e assentava em duas premissas principais: 1. Não é feita distinção intrı́nseca entre dados e instruções; 2. As instruções podiam ser divididas em duas partes (dois grupos ou conjuntos de 16 Leitor de cartões Impressora e perfurador de catões barramento de dados Multiplicador Router de funções Divisor e extractor de raiz quadrada Acumuladores (20) barramento de programa Unidade de programação principal Figura 2.1 — Diagrama de blocos de uma arquitectura tipo Harvard. bits). Uma parte ou grupo de bits era dedicado à codificação da instrução (representava a instrução ou operação) e a outra parte ou grupo de bits indicava o endereço do(s) operando(s) (os dados a serem usados). Nesta arquitectura existia, pois, um único espaço de memória partilhado pelas instruções e pelos dados. Nesta linha, em 1951, o Institute for Advanced Studies, Princeton, apresentou a arquitectura do computador conhecido por IAS, figura 2.2. Esta nova arquitectura simplificava o desenho do computador, mas possuı́a a desvantagem de apenas poder aceder aos dados ou às instruções num momento especı́fico. A História veio a revelar que esta limitação não é muito penalizadora para computadores e micro-computadores de utilização genérica. As famı́lias de processadores de utilização genérica MC68000 da Motorola, i86 da Intel e a Advanced Micro Devices (AMD) partilham da arquitectura proposta por von Neumann. Estas e outras famı́lias também possuem outras caracterı́sticas tı́picas dos computadores dos últimos cinquenta anos. Nos principais blocos computacionais inclui-se uma Unidade Aritmética e Lógica (Arithmetic Logical Unit — ALU) e um shifter (“deslocador” para a esquerda ou para a direita). Operações como a adição, subtracção e movimentação de dados são facilmente executadas em poucos ciclos de relógio. Instruções complexas como a multiplicação e a divisão são construı́das a partir de um conjunto de shifts (deslocamento), adições ou subtracções. Dispositivos deste tipo são conhecidos por computadores com conjunto complexo de instruções (Complex Instruction Set Computer — CISC). Geralmente, as arquitecturas do tipo CISC possuem a operação de multiplicação, mas na verdade esta é implementada 17 Unidade Aritmética e Lógica Equipamento de Entrada/Saı́da Instruções e dados Memória principal Endereços Unidade de Controlo de Programa Figura 2.2 — Diagrama de blocos de uma arquitectura tipo von Newmann. recorrendo a instruções em micro-código, geralmente armazenada num chip de memória ROM. Esta instrução de multiplicação demorará por isso vários ciclos de relógio a ser executada. O processamento ou tratamento de dados envolve muitas operações na forma: A = B × C + D. Esta simples equação envolve uma operação de adição e uma multiplicação. Devido à lenta execução da instrução de multiplicação, as arquitecturas do tipo CISC não são muito eficientes no cálculo deste tipo de equações. Para este tipo de aplicação necessitamos de máquinas capazes de executar instruções do tipo multiplicação e adição em apenas um ciclo de relógio. Como tal é necessária uma aproximação diferente à arquitectura dos computadores. Por outras palavras, necessitamos de uma arquitectura moldada à aplicação. Por exemplo, no processamento digital de sinal em tempo real, a maior preocupação é com a quantidade de processamento que pode ser feito antes que novos dados estejam disponı́veis, ou seja, à espera de serem tratados. Os primeiros processadores digitais de sinal (Digital Signal Processors — DSPs) usavam blocos de componentes standard para construir shift-registers (registos de deslocamento), adders (adicionadores) e multipliers (multiplicadores). Veja-se [13], por exemplo, para alguns pormenores relacionados com a 18 forma como certos componentes ou unidades podem ser implementados. O desenho dos multiplicadores evoluiu para a utilização de técnicas de pipe-lining, sendo o primeiro multiplicador de ciclo único implementado no inı́cio dos anos 70. Na sua construção foram utilizados componentes standard de alta velocidade do tipo Lógica de Emissor Acoplado (Emitter-Coupled Logic — ECL). Os laboratórios Lincoln lideravam nesta altura o processo de investigação em DSPs. O Lincoln FDP (Fast Digital Processor) foi apresentado em 1971 e executava uma instrução de multiplicação em 600 nano-segundos e era construı́do por 10 000 circuitos integrados (separados). Também padecia do facto de tentar implementar operações paralelas usando a arquitectura sequencial de von Neumann. O Lincoln LSP/2 foi construı́do a partir da lição aprendida com o FDP e usava uma arquitectura semelhante à do computador Harvard Mark 1. Como esta arquitectura é eminentemente paralela, foi possı́vel construir um DSP quatro vezes mais rápido que o FDP, com cerca em um terço dos circuitos integrados (CIs). Em meados dos anos 70, graças também ao envolvimento de outras instituições de investigação, já era possı́vel executar uma operação de multiplicação em cerca de 200 nano-segundos. Estas novas máquinas eram capazes de fazer processamento digital de sinal em tempo real, mas eram tão volumosas e caras que a sua comercialização ainda não era viável. A arquitectura básica de um DSP tinha sido criada, mas teria que esperar pela tecnologia dos semicondutores evoluı́sse. Se tivesse sido possı́vel a implementação de um DSP utilizando poucos CIs (ou mesmo num único chip) muitas oportunidades comerciais teriam surgido. Ao longo dos anos 70 a tecnologia de integração de circuitos (circuitos integrados) foi-se tornando cada vez mais complexa. Com as exigências da “máquina de guerra húngara” nos Estados Unidos e a utilização gradual da electrónica nos produtos de consumo (como nos electrodomésticos), houve um grande incentivo para a redução dos tamanhos e aumento da velocidade de processamento destas tecnologias. O processo tecnológico utilizado era N-MOS (N-channel Metal Oxide Semiconductor), que trabalhava com uma fonte de alimentação de 5 Volt e podia ser implementado em 3 mı́cron, suportando densidades de 100 000 transı́stores. Entre 1980 e 1982 ficaram disponı́veis quatro DSPs do tipo chip-único. O primeiro DSP deste tipo é atribuı́do à empresa American Microsystems Inc. (AMI) com o seu S2811. O Intel 2920 e o Nippon Electric Company (NEC) mPD7720 também estavam disponı́veis pela mesma altura. Ligeiramente mais tarde a Texas Instruments introduziu o seu TMS32010. Os primeiros DSPs usavam uma arquitectura do tipo Harvard para separar a memória de programa da memória de dados. Isto permitia o acesso simultâneo a uma palavra de 19 Controlador Contador de programa e stack Endereço A11−A0 Instrução ROM de programa (1536x16) D15−D0 Crossover de dados/programa Memória de dados Unidade de endereçamento e aritmética da memória de dados Unidade Central de Processamento Registos auxiliares Figura 2.3 — Diagrama de blocos do processador de sinal TMS32010 (tal como apresentado em [11]). dados e uma de instrução. No processamento digital de sinal em tempo real é fundamental o fluxo eficiente de dados, de e para o processador. A utilização de uma arquitectura do tipo Harvard permitia o fluxo de dados sem interrupção da leitura das instruções. Na figura 2.3 apresenta-se o diagrama de blocos do TMS32010. Neste diagrama pode ver-se a separação das memórias de dados e de programa e a existência de um crossover entre as duas memórias. Por esta razão, a arquitectura do TMS32010 é geralmente conhecida como sendo de Harvard modificada. A inclusão de um núcleo DSP num dispositivo com muitas das facilidades existentes num computador de utilização genérica, a Texas Instruments tornou mais simples a programação deste género de dispositivos. O TMS32010 possuı́a uma linguagem de programação assembly, ferramentas para análise e um emulador semelhante ao dos micro-computadores. Naturalmente, seguiu-se o desenvolvimento de dispositivos mais rápidos, simuladores, debuggers, compiladores de C, etc., para utilização em DSPs (tal como aconteceu para os micro-computadores). Claro que a história não acaba aqui. A miniaturização e a indústria dos computadores pessoais (PCs) exigem cada vez maior densidade. Por sua vez, a cada vez maior densidade de integração, permite o aumento do número de transı́stores num único chip. 20 Ano Processador Transı́stores Relógio Bus de Bus (MHz) endereços dados de Registos Cache 1978 8086 29 mil 4,75 20 bits 16 bits 16 bits — 1982 80286 134 mil 6-25 24 bits 16 bits 16 bits — 1985 80386 275 mil 16-40 24 bits 32 bits 32 bits — 1989 80486 1,2 milhões 25-100 32 bits 32 bits 32 bits 1 nı́vel 1993 Pentium 3,1 milhões 60-233 32 bits 64 bits 32 bits 1 nı́vel 1995 Pentium Pro 5,5 milhões 150-200 32 bits 64 bits 32 bits 2 nı́veis 1997 Pentium II 7,5 milhões 233-450 36 bits 64 bits 32 bits 2 nı́veis 1999 Pentium III 15 milhões 450-1000 36 bits 64 bits 32 bits 2 nı́veis 2001 Pentium 4 100 milhões 1000-3000 36 bits 64 bits 32 bits 2 nı́veis Tabela 2.1 — Algumas das caracterı́sticas presentes na famı́lia de processadores Intel. O desenvolvimento da tecnologia CMOS possibilitou geometrias de integração da ordem dos 0,5 microns. Esta tecnologia não só possibilitou o aumento do número de transı́stores e portas num único chip, mas também a redução nos tempos de comutação e consequente aumento da cadência do relógio e maior capacidade de throughput (entrada/saı́da) dos processadores. Por exemplo, em 1994 conseguiam-se DSPs num chip-único com cerca de 4 000 000 de transı́stores e tempos de execução da instrução de multiplicação inferiores a 40 ns, para instruções de 32 bits em vı́rgula-flutuante, ou inferiores a 25 ns, para alguns dispositivos possuindo instruções de vı́rgula-fixa de 16 bits [11]. Na tabela 2.1 podem ver-se algumas das caracterı́sticas presentes na famı́lia de processadores Intel. 2.2 Conceitos e palavras chave Convém, antes de prosseguirmos, introduzir alguns conceitos e palavras chave, pois alguns deles podem suscitar diferentes interpretações daquelas que vão ser usadas. Os microcomputadores que vamos estudar utilizam circuitos electrónicos de lógica binária. Consequentemente, a unidade de informação usada só consegue distinguir apenas duas situações. A esta unidade de informação chama-se bit. Um conjunto de 8 bit (ou bits) é designado por byte. Com um byte consegue-se distinguir 28 = 256 situações diferentes. Por seu lado, 210 bytes = 1024 bytes = 1 Kbyte (Kilo byte). Existem computadores de 8, 16, 32 e 64 bits. Isto quer dizer que estas máquinas manipulam de cada vez a quantidade de informação contida em 8, 16, 32 e 64 bits, respectivamente. Esta quantidade é designada por palavra do computador. O conjunto de instruções de um micro-processador também pode ser designado por 21 instruction set. Uma instrução não é mais do que uma combinação de bits. Deve ser uma combinação lógica para poder ser reconhecida como tal. Um programa é um conjunto de instruções que fazem com que o computador desempenhe determinada tarefa. Qualquer programa é traduzido num conjunto de zeros e uns (porquê?). Existem muitas dificuldades associadas à criação de programas objecto (ou programas em linguagem máquina binária). Enumerando apenas alguns: • São difı́ceis de compreender; • São longos e difı́ceis de escrever; • São morosos a introduzir, uma vez que é introduzido um bit de cada vez; • Não descrevem a tarefa para a qual foram escritos duma forma legı́vel para qualquer utilizador humano; • O programador comete erros que são difı́ceis de encontrar e corrigir. Podemos melhorar algumas destas dificuldades se escrevermos as instruções em octal (utilizando a base 8 para a representação dos valores, consulte-se por exemplo [14] para mais pormenores acerca das bases para representação de valores) ou hexadecimal (utilizando a base 16) em vez de números binários, mas o micro-processador só “entende” zeros e uns. Que fazer? Uma alternativa consiste em escrever um programa que traduza estes números (octal ou hexadecimal) para números binários. Este programa é geralmente designado por hexadecimal loader. Mas, como é de esperar, um outro melhoramento significativo, é atribuir um nome a cada uma das instruções. O nome da instrução é designado por mnemónica. Estas mnemónicas devem descrever de alguma forma o que cada instrução representa. Um programa escrito em linguagem assembly não é mais do que um programa escrito utilizando mnemónicas. Pode traduzir-se à mão, mnemónica a mnemónica, sendo sem dúvida uma tarefa mais fácil (e onde se cometem menos erros) do que qualquer uma das técnicas anteriormente apresentadas. Pode-se ainda automatizar esta tradução, encarregando o próprio computador desta tarefa. Para tal existe um programa que designamos por assembler (ou “assemblador”) que se encarrega desta tradução para um determinado conjunto de mnemónicas. Ao programa escrito em assembly designa-se por programa fonte (source program). Ao programa gerado pelo assembler, que vai ser executado pelo , chama-se programa objecto (object program). Deste modo tornam-se evidentes as vantagens da utilização da linguagem assembly em relação a qualquer um dos outros métodos vistos até agora. 22 Contudo, a grande desvantagem dos assemblers reside no facto destes possuı́rem as suas próprias regras, que devem ser muito bem conhecidas por parte do programador e às quais deve obedecer. Adicionalmente, podem-se indicar as vantagens seguintes dos programas escritos em assembly: • Permitem ao utilizador a atribuição de nomes a: – uma dada posição de memória; – dispositivos de entrada/saı́da; – a conjuntos de instruções (macros). • Permitem a conversão de dados ou endereços entre vários sistemas de numeração e binário, e ainda a conversão de caracteres na sua representação em ASCII ou EBCDIC; • Efectuar alguma aritmética como parte do processo de assembling (“assemblagem”); • Indicar ao loader os locais de memória onde os programas ou dados devem ser colocados; • Permitir ao utilizador a atribuição de zonas de memória temporária de dados e colocar dados fixos nas áreas de memória do programa; • Fornecer a informação necessária para incluir programas standard de uma dada biblioteca, ou programas escritos noutra altura, no programa corrente; • Permitir ao utilizador o controlo do formato da listagem do programa bem como os dispositivos de entrada e saı́da a utilizar. Como desvantagens dos programas escritos em assembly pode indicar-se: • O grande fosso que existe entre as instruções assembly (o conjunto de instruções que o micro-processador consegue realizar) e as tarefas que o microcomputador deve desempenhar; • Tem que se conhecer muito bem um conjunto de instruções de um micro-processador particular (aquele que usamos); • Não é portável. As duas primeiras desvantagens não são propriamente inerentes à programação em assembly, sendo comuns a qualquer micro-processador. Estas desvantagens podem ser minimizadas recorrendo a linguagens de programação de alto nı́vel. 23 As linguagens de alto nı́vel (linguagens orientadas ao procedimento ou ao objecto) possibilitam superar algumas das limitações e dificuldades da linguagem assembly, uma vez que permitem descrever tarefas de uma forma orientada ao problema e não ao computador. Normalmente uma instrução (ou mais correctamente, uma declaração [15]) duma linguagem de alto nı́vel corresponde a várias em linguagem assembly. Existem programas especiais para a tradução de programas escritos nestas linguagens para linguagem máquina. Estes programas são designados por compiladores. Como exemplos de linguagens de programação de alto nı́vel indicam-se: • PASCAL; • FORTRAN; • C, C++; • COBOL; • BASIC, VISUAL BASIC; • ALGOL; • APL; • PL/1; • JAVA. Algumas das vantagens da utilização das linguagens de programação de alto nı́vel são: • Descrição mais conveniente da tarefa a desempenhar; • Codificação mais eficiente do programa; • Documentação mais fácil; • Sintaxe standard; • Independente de uma arquitectura particular; • Portabilidade; • Existência de bibliotecas de rotinas (pequenos programas) e mesmo programas completos. Como desvantagens indica-se: • Regras especiais; 24 • Exigem grande suporte de hardware e software; • Programas ineficientes; • Dificuldade na optimização do código para satisfazer aos requisitos de tempo e memória; • Impossibilidade de utilização de determinada potencialidade do computador em causa. Qual o nı́vel que devemos escolher? Obviamente que a resposta a esta questão não é imediata. Contudo, deve ter-se como “regra de ouro” que o nı́vel depende da aplicação que esteja a ser desenvolvida. Assim, a linguagem máquina foi posta totalmente de parte uma vez que o baixo custo dos assemblers e compiladores, associado a todos os outros factores descritos anteriormente, não justificam a sua utilização. A linguagem assembly deve ser utilizada quando pretendemos pequenos programas, aplicações onde o tamanho de memória seja limitado, aplicações em tempo real, capacidade de processamento de dados muito limitada, aplicações que envolvam um elevado volume de chamadas a determinada rotina para desempenhar determinada tarefa, ou em aplicações muito mais viradas para entrada/saı́da ou controlo. Por seu lado, as linguagens de alto nı́vel devem ser usadas em programas longos, aplicações que requerem grande capacidade de memória, aplicações mais vocacionadas para o cálculo do que para entrada/saı́da ou controlo, compatibilidade entre diferentes versões da mesma aplicação para diferentes máquinas sem necessidade de reescrever o código todo. Enfim, onde as vantagens da utilização deste tipo de linguagens, vistas anteriormente, possam ser evidenciadas. Existem actualmente no mercado linguagens que cada vez se voltam mais para a resolução do problema do que para a forma como vai ser implementada. Linguagens visuais e orientadas por objectos ou ao objecto, bem como as ferramentas CASE (Computer Aided Software Engineering), estão a conquistar cada vez mais adeptos. Requerem contudo, muito mais do que as linguagens orientadas ao procedimento, grandes capacidades de memória, espaço em disco, rapidez de cálculo, etc.. Repare-se ainda que são adicionadas, cada vez mais, novas capacidades aos programas, se tornam mais baratos o hardware e software e os programadores mais caros, são lançados para o mercado compiladores mais versáteis, potentes, com melhor aproveitamento dos recursos dos computadores e mais baratos. 2.3 Descrição geral de um micro-processador Neste secção tentaremos dar uma definição de micro-processador. Pode dizer-se que um micro-processador é um dispositivo lógico programável, que lê instruções binárias dum 25 Memória CPU E/S Figura 2.4 — Diagrama de blocos de uma máquina programável tı́pica. dispositivo de armazenamento, chamado memória, aceita dados binários como entrada, e processa esses dados de acordo com as instruções, fornecendo resultados de saı́da. O esquema da figura 2.4 pode ser utilizado para representar uma máquina programável tı́pica. O bloco designado por CPU (Central Processing Unit — Unidade de Processamento Central) é o responsável pelo controlo de tudo o que se passa na máquina. As unidades principais são uma Unidade Aritmética e Lógica (Arithmetic and Logic Unit — ALU), uma unidade de controlo e vários registos para armazenamento de dados temporários. Quando todos os componentes são integrados numa única pastilha, chamamos-lhe micro-processador. Os microcomputadores possuem CPU’s com esta caracterı́stica. O CPU é a unidade inteligente do sistema, detendo o controlo em qualquer momento de tudo o que se passa nos restantes blocos. A memória é também indispensável em qualquer sistema de computador. É aqui que se encontra armazenada toda a informação que o CPU vai processar, onde vão ser colocados os resultados (finais ou parciais) de um processamento e onde se encontram armazenados os programas. A memória pode ser dividida em dois grandes grupos: ROM (Read Only Memory) — é não volátil, não sendo possı́vel alterar o seu conteúdo por programação normal; e RAM (Random Access Memory) — é volátil, podendo ler-se e escrever-se (alterar a informação nela contida) aleatoriamente. O bloco de Entrada/Saı́da (E/S) também conhecido por Input/Output (I/O) é constituı́do por circuitos que fazem a adaptação entre os sinais eléctricos do sistema de computador e os periféricos que fazem a comunicação com o mundo exterior. Estes circuitos são designados por interfaces. Existem periféricos de Entrada, Saı́da e Entrada/Saı́da. Na figura 2.5 pode ser visto um CPU tı́pico com todas as componentes descritas aqui e ainda alguns periféricos ligados ao sistema por meio das interfaces. Nesta figura podemos observar três linhas. Estas linhas representam, cada uma, um conjunto de sinais eléctricos, todos eles com funções distintas no sistema de microcomputador. Cada linha representa um barramento (bus). Os barramentos ou apresentados são: • Dados; • Endereços; 26 Periféricos CPU ALU Registos Ecran Teclado Controlo Saı́da Entrada Memória ROM RAM Dados Endereços Controlo BUS Figura 2.5 — Diagrama de blocos de um sistema de computador tı́pico. • Controlo. O bus de dados é constituı́do por várias linhas. É comum encontrar barramentos de dados com 8, 16, 32 e 64 linhas. Geralmente este número de linhas surge associado à dimensão da palavra do computador. Nele transita toda a informação que é movimentada entre os diferentes blocos. Este barramento tem que ser bidireccional (porquê?). O barramento de endereços é constituı́do por um conjunto de linhas cujo número varia de acordo com o número de localizações distintas que um CPU consegue referenciar. Tal como para o barramento de dados, este número varia de sistema para sistema. Para o micro-processador é um Z80 o bus de endereços é de 16 bits. Consegue pois diferenciar 216 = 65536 = 64K localizações. É este bus que indica onde a informação se encontra e para onde deve ser dirigida, independentemente da informação em si e onde será transportada (bus de dados). O barramento de controlo tem por objectivo controlar a transferência de informação entre os diferentes blocos. É pois responsável por indicar qual o sentido da informação. Vamos ver agora como funciona, do ponto de vista conceptual, a troca de informação entre dois dos blocos referidos. Suponhamos que o CPU leu da memória uma instrução que interpretou e verificou tratar-se duma ordem para transferir informação entre determinada célula de memória e um registo particular interno ao CPU. A execução desta instrução obriga à definição em simultâneo, através da utilização de sinais eléctricos com suporte fı́sico em linhas independentes, de: • Endereço da posição de memória a aceder (bus de endereços); • Sinal de leitura da memória (bus de controlo); • Informação a operar (bus de dados). 27 T1 T2 T3 T4 clk A0-A15 Mreq Rd Wait Wr D0-D7 dados Figura 2.6 — Diagrama temporal associado ao processo de leitura de dados da memória. Para conseguir este objectivo o CPU: • Coloca no bus de endereços o endereço efectivo da posição de memória (fornecido pela instrução em execução); • Gera o sinal na linha especı́fica para o efeito no bus de controlo; • E recebe no bus de dados a informação a manipular. Este procedimento encontra-se exemplificado, na figura 2.6, na forma de um diagrama temporal. Mais adiante serão estudados em detalhe alguns dos principais diagramas temporais presentes num micro-processador tı́pico. 2.4 Estrutura interna de um CPU Um CPU é constituı́do por componentes electrónicos, tais como: • Portas lógicas (gates); • Registos; • Somadores; 28 A P B Figura 2.7 — Processo tı́pico de transferência de informação entre dois registos. • Memórias; • Etc.. Estes componentes encontram-se ligados por barramentos internos ao CPU que transportam a informação no seu interior. Este transporte é controlado por portas. Tomemos como exemplo a figura 2.7. Os registos A e B estão ligados pela porta P que controla o fluxo de informação entre os dois registos. Ao abrir-se esta porta, uma cópia da informação existente no registo A é colocada no registo B, permanecendo o conteúdo do registo de partida (A) inalterado. Durante o perı́odo de tempo em que a porta P se encontra aberta e se está a efectuar a transferência de informação (no caso do Z80, poucas dezenas de nano-segundos), o conteúdo do registo A deve permanecer inalterado (porquê?). Quando se pretende desenvolver uma outra operação que vá alterar o contudo do registo A deve ter-se o cuidado de fechar previamente a porta P (porquê?). Para que estas transferências de informação dentro dos registos do CPU se façam de uma forma controlada e sem interferência de outras operações que pretendam utilizar os mesmos registos, utiliza-se um relógio. Este relógio especifı́ca a duração de cada operação elementar dentro do CPU. Um perı́odo deste relógio designa-se por ciclo de régio (clock cycle). Alguns micro-processadores mais recentes possuem relógios de sincronização interna com uma frequência superior a 3 GHz (3 000 000 000 ciclos por segundo!). Vamos agora considerar que cada um dos componentes vistos faz parte de um bloco lógico, ao qual vamos atribuir um nome de acordo com as funções que desempenha dentro do CPU. Já vimos na figura 2.5 que existem dois blocos fundamentais (para além do array de registos): • Unidade Aritmética e Lógica (ALU); • Unidade de Temporização e Controlo. Associados a estas duas unidades encontra-se um conjunto de registos. Uns são visı́veis ao programador (o programador controla o seu conteúdo); outros servem de apoio ao funcionamento interno das unidades referidas. A figura 2.8 exemplifica a estrutura interna do CPU Z80. 29 +5V GND Descodificador de instruções Registos ALU Controlo do bus de dados Bus de endereços Relógio Bus de dados Pedidos externos Controlo do bus de endereços Flags Reconhecimento de pedidos Temporização e controlo Registo de instruções Bus de controlo Figura 2.8 — Diagrama de blocos do CPU Z80. 2.4.1 Unidade Aritmética e Lógica (ALU) Esta unidade é responsável pela execução de todas as instruções aritméticas e lógicas. A figura 2.9 pretende representar uma parte desta unidade, por intermédio de um diagrama de blocos, que serve para a execução das operações aritméticas básicas. Esta unidade é capaz de executar operações de adição (ADD) na forma ADD val, onde val representa o valor que queremos adicionar a um registo particular que designamos por acumulador. Assim numa ALU com esta estrutura, as operações são efectuadas tendo sempre como um dos operandos o registo acumulador. O resultado destas operações é depositado de novo no acumulador. O outro operando é lido de um dos restantes registos ou de uma posição de memória e colocado no registo interno M. A ALU limita-se a operar os conteúdos do registo M e do registo acumulador. A execução de uma instrução do tipo ADD M, envolve: • A leitura do registo, ou posição de memória endereçada, para o registo M. A transferência de informação só será efectuada depois de aberta a porta P1; • Os conteúdos dos registos M e acumulador são adicionados pelo somador; • O resultado obtido é colocado no registo T, após a abertura da porta P2; • O resultado da operação é colocado no registo acumulador, depois de primeiro fechar a porta P2 e depois abrir a porta P3. O registo T mostra-se essencial para controlar situações de corrida não controlada. 30 P2 Somador T M P3 P1 Acumulador Figura 2.9 — Diagrama de blocos de uma secção da ALU. Uma vez que uma das entradas do somador é o registo acumulador, não podemos depositar o resultado directo no acumulador pois estarı́amos a alterar uma das suas entradas. 2.4.2 Unidade de Temporização e Controlo Como já referimos, esta unidade gera sinais que temporizam a activação das operações elementares dentro do CPU, bem como sinais de controlo para o exterior do CPU que são necessários ao funcionamento de todo o sistema de microcomputador. É também aqui que são descodificadas as instruções, isto é, as instruções codificadas em binário são interpretadas, são gerados os sinais necessários, encaminha-se a informação para os locais correctos e faz-se com que cada circuito dentro do CPU actue no instante preciso. Existem basicamente duas técnicas para a implementação destas unidades: Hardwired; e Micro-programada. Na primeira técnica a unidade de controlo é construı́da por portas lógicas (gates). As gates abrem-se segundo uma sequência bem determinada, dependendo da instrução em execução. Na segunda técnica, utiliza-se basicamente uma memória cujo conteúdo de cada uma das suas posições determina os sinais de controlo internos e externos ao CPU que vão estar activos em cada ciclo de relógio. A figura 2.10 pretende exemplificar uma estrutura do tipo micro-programada. Nesta figura encontram-se representados: • A memória com o micro-programa; • Um registo de endereçamento da memória de micro-programa; 31 Registo de Instrução P Memória de Microprograma “próxima” “busca” Sinais de temporização e controlo Registo de Endereçamento Figura 2.10 — Diagrama de blocos simplificado de uma unidade de temporização e controlo micro-programada. • O registo de instrução, onde é colocada a instrução a ser executada. Como funciona o circuito apresentado? Cada posição de memória determina os sinais de controlo para um ciclo particular de relógio de uma instrução particular. O controlo do desenvolvimento de uma instrução é efectuado à custa de uma sequência de ciclos de relógio micro-programados, i.é., codificados em posições consecutivas da memória do micro-programa. A mudança de estado (novo ciclo de relógio) é efectuada, numa instrução particular em execução, actuando no sinal que permite incrementar o registo de endereçamento da memória do micro-programa. Para que qualquer instrução seja executada, o CPU tem que ir à memória do sistema fazer a busca (fetch) do seu código. Esta operação é comum a todas as instruções, por isso pode ser codificada nas mesmas posições de memória de micro-programa para todas as instruções. Normalmente as posições iniciais da memória de micro-programa contêm informação referente aos ciclos de relógio da fase de busca das instruções. Uma vez feita a busca da instrução, o seu código é colocado no registo de instrução, que ao ser carregado no registo de endereços (por actuação na porta P), determina a zona de memória de micro-programa a ser seleccionada em seguida. Exemplo 1 (Distribuição de micro-instruções) Suponhamos que um microcomputador, com uma unidade de controlo com uma estrutura idêntica à da figura 2.10, possui um conjunto de N instruções. Suponhamos ainda que a fase de busca das instruções é constituı́da por quatro ciclos de relógio, o mesmo acontecendo à fase de execução das mesmas. A figura 2.11 exemplifica, em face destes dados, como se distribuiriam as microinstruções referentes a cada instrução na memória de micro-programa. O número de posições de memória de micro-programa está pois relacionado com o número de instruções que determinado CPU possui e com o número de ciclos de relógio de cada uma delas. 32 0 Busca 4 Instrução 1 8 Instrução 2 12 ... 4N Instrução N Figura 2.11 — Exemplo de micro-programa. O número de bits de cada posição dessa memória é função do número de sinais de controlo necessários aos circuitos internos do CPU e dos sinais de controlo de todo o sistema de microcomputador que saem do CPU. Exemplo 2 (Sinais afectos a um CPU) Como exemplo dos sinais afectos a uma pastilha de CPU, consideremos o caso do Z80, representado na figura 2.12. Este circuito integrado é vendido na forma de uma pastilha de 40 pinos, com a seguinte distribuição: • 8 linhas bus de dados, • 16 linhas bus de endereços; • 13 linhas de controlo do CPU e sistema de microcomputador; • 1 linha de relógio; • 2 linhas para alimentação. Qual a melhor técnica Hard-wired ou Micro-programa? Esta pergunta não possui resposta simples, devendo ter-se em linha de conta, entre outros, os seguintes aspectos: • Rapidez de execução (número de ciclos de relógio por instrução); • Complexidade dos circuitos; • Versatilidade de implementação de novas instruções; • Custos da memória versus custos de desenho de circuitos dedicados; • Etc.. Para informação complementar consulte-se, por exemplo, [12]. 33 Controlo do sistema Controlo do CPU Controlo dos barramentos Alimentação M1 Mreq Iorq Rd Wr Rfsh Z80 CPU A0 A1 .. . Halt Wait Int NMI Reset Busreq Busack A15 D0 D1 .. . Clk +5V GND Bus de endereços Bus de dados D7 Figura 2.12 — Esquema de ligações externas do Z80. 2.5 Arquitectura de um micro-processador Entende-se por arquitectura de um microcomputador, não a forma como um circuito particular, que realiza determinada função, é implementado do ponto de vista de hardware, mas sim o conjunto de estruturas e formas de as manipular que o programador tem ao seu dispor. Assim, fazem parte da arquitectura: • Todos os registos visı́veis ao programador; • O conjunto de todas as instruções. Desta forma, pode dizer-se que, de uma forma simplista, quanto maior for o número de registos internos ao CPU visı́veis ao programador e o número de operações que se possam executar sobre eles, mais versátil e “poderoso” será esse CPU. Nesta fase convém tentar deixar bem esclarecidas as seguintes questões. O que é uma instrução? O que é um programa? Como já vimos, uma instrução é uma combinação binária armazenada em memória que o CPU vai ler para: • Descodificar; • Interpretar; • E gerar uma sequência de operações elementares cuja acção global é a preestabelecida pelo seu fabricante. 34 Código da operação Endereço do opereando 31 8 7 0 Um programa é uma sequência de instruções armazenadas em memória por ordem crescente dos seus endereços. A sequência de execução destas instruções, em princı́pio, será a da ordem de armazenamento em memória. Existem no entanto instruções que permitem alterar a ordem dessa execução, ou seja, permitem alterar o fluxo da sequência “normal” da execução das instruções. Umas incondicionalmente (salto incondicional), outras quando se verificarem determinadas condições (salto condicional) e outras ainda que podem alterar esse fluxo se ocorrerem determinadas situações internas ou externas ao CPU, mas cujo instante não se pode prever (interrupções). Visto isto, torna-se evidente a necessidade de aprofundar um pouco mais os conhecimentos acerca do modo como um CPU executa uma instrução e que ferramentas (registos) necessita ter para processar uma determinada sequência de instruções (programa). As instruções são códigos binários referindo: • O código da própria instrução; • O(s) operando(s) sobre o(s) qual(ais) vai actuar. Uma instrução é constituı́da pelo código de operação e pelo(s) operando(s). O seu formato varia de máquina para máquina. Na sua forma mais simples uma instrução seria codificada numa única palavra de computador, reservando-se um determinado número de bits para o código de operação e os restantes para a referência do operando. A figura 2.5 exemplifica este conceito. Exemplo 3 (Formato das instruções) Uma máquina que utilizasse somente este formato de instrução poderia ter no máximo 256 instruções. Porquê? Dica: quantos bits são usados para o código de operação? O Z80 possui um formato variável para as instruções, existindo códigos de operação de 1 byte a 4 bytes. Como é que o CPU executa determinada instrução? O desenvolvimento do processamento de uma instrução pelo CPU pode ser subdividido em duas fases: 1. Busca (fetch); 2. Execução. Estas fases são explicadas detalhadamente no próximo ponto. 35 T1 T2 T3 T4 clk A0-A15 PC refresh Mreq Rd Wait M1 D0-D7 dados Rfsh Figura 2.13 — Diagrama temporal correspondente à fase de fetch. 2.5.1 Formato e processamento das instruções Na fase de fetch, o CPU coloca no bus de endereços o endereço da posição de memória que contém a próxima instrução a ser executada e gera um sinal de controlo para leitura da memória, figura 2.13. Convém destacar as seguintes fases principais: • É colocada no bus de dados a combinação binária que corresponde à instrução a processar; • O CPU recolhe essa informação e coloca-a no registo de instrução (Instruction Register); • Esta informação vai ser agora descodificada e posteriormente executada. No caso do Z80, esta fase também é conhecida por ciclo M1 ou ciclo de máquina (Machine Cycle ou Machine Cycle One). Esta fase de busca de instrução é igual para todas as instruções, o mesmo não acontece com a fase de execução, uma vez que as instruções são diferentes umas das outras. Existem contudo grupos de instruções idênticas em que o procedimento do CPU na fase de execução é semelhante, diferindo apenas nos operandos a processar. Mais tarde voltaremos a este assunto. Já se viu que um computador para executar (correr) um programa, necessita de processar uma sequência de instruções. Por este motivo é necessário que o CPU tenha um 36 registo interno que tome conta da evolução desta sequência, armazenando em cada momento o endereço da próxima instrução a ser executada. Este registo interno designa-se por contador de programa (Program Counter). É o conteúdo deste registo que é colocado no bus de endereços de cada vez que o CPU faz o fetch de uma instrução. Uma das operações elementares do CPU é a actualização do PC. Porquê? Porque é que o PC do Z80 é um registo de 16 bits? Dica: Por quantas linhas é constituı́do o bus de endereços? Quantos endereços de memória consegue o Z80 distinguir? 2.5.2 Registos internos de uma arquitectura básica Já vimos que o Program Counter é um registo interno ao CPU essencial ao seu funcionamento. Para a definição de uma arquitectura elementar ficar completa, tornam-se necessários mais dois registos, um acumulador e um registo de flags. O acumulador terá como finalidade o armazenamento de um dos operandos e o resultado da instrução a executar. O outro operando, caso exista, poderá encontrar-se numa posição de memória. O registo de flags serve para memorizar um conjunto de condições relevantes que acontecem durante a execução de algumas instruções. Alguns exemplos de condições memorizadas neste tipo de registo são: • O facto de ocorrer um resultado nulo ao ser processada uma instrução qualquer aritmética; • O facto de ocorrer um resultado negativo ao ser processada uma instrução qualquer aritmética; • O facto do número nele armazenado ser positivo ou negativo; • Etc.. O tipo de conhecimento que se pretende memorizar num registo de flags é um conjunto de situações ligado/desligado (on/off). Quantos bits são necessários por situação? Quantas situações distintas se conseguem memorizar com um registo de 8 bits? Podem ser usadas instruções para verificar a ocorrência ou não destas situações, uma vez que pode ser necessário tomar uma decisão acerca de “qual a próxima instrução a executar”. As instruções de salto condicional fazem parte deste grupo. A figura 2.14 representa esquematicamente uma arquitectura do tipo enunciado. Esta arquitectura “mı́nima” seria constituı́da pelos registos: • PC (Program Counter) de 16 bits; • A (Acumulador) de 8 bits; 37 PC A F F P C S Z Figura 2.14 — Diagrama de blocos de um conjunto mı́nimo de registos. Cy Cy 76543210 76543210 Figura 2.15 — Instruções de rotação para a esquerda e para a direita (ver texto). • F (Flags) de 8 bits. Só 4 bits seriam utilizados para sinalizar as situações de: – Z (Zero). Resultado nulo (zero) da última operação; – S (Sinal). Sinal do operando armazenado no registo A; – C (Carry). Houve carry — transporte ou “vai um” — ou borrow — empréstimo ou “falta um” — no resultado da última operação executada; – P (Paridade). O número de bits a 1 do operando armazenado no registo A é par. Nesta arquitectura também seria necessário definir um instruction set básico que permitisse o funcionamento do microcomputador que nele assentasse. A tabela 2.2 mostra o instruction set proposto. Esta arquitectura é bastante simples. As mnemónicas da tabela 2.2 são idênticas (sendo mesmo um subconjunto) às que iremos estudar do Z80. Esta escolha foi feita de forma a não gerar confusão no futuro quando estudarmos as do Z80. Na figura 2.15 pode ver-se a forma como as instruções de rotação são executadas. Por exemplo, na rotação para a direita o bit número 7 passa para a posição do bit 6, o bit 6 passa para o bit 5, e assim sucessivamente até ao bit 1 que passa para o bit 0. O bit 0 passa a ser o bit da flag de carry e o bit da flag de carry passa a ser o bit 7. Deve salientar-se que de fabricante para fabricante, de máquina para máquina, as mnemónicas utilizadas para corresponder a determinado tipo de operação podem variar. Com esta arquitectura poder-se-iam executar programas! Veja-se o seguinte exemplo. Exemplo 4 (Adição de dois valores) Escreva um programa, com base no instruction set apresentado na tabela 2.2, que adicione o conteúdo da posição de memória cujo endereço é END1 ao conteúdo da posição de memória cujo endereço é END2 e coloque o 38 Instruções de Transferência Instrução Operandos Comentários LD A,n ;A ← n LD A,(nn) ;A ← (nn) LD (nn),A ;(nn) ← A Instruções aritméticas e lógicas Instrução Operandos Comentários ADD A,n ;A ← n ADC A,(nn) ;A ← A + n + Carry SBC A,(nn) ;A ← A − n − Carry AND (nn) ;A ← A E (nn) OR (nn) ;A ← A OU (nn) XOR (nn) ;A ← A OU EXCLUSIVO (nn) CP (nn) ;A − (nn) INC A ;A ← A + 1 DEC A ;A ← A − 1 Instruções sobre a flag de carry Instrução Comentários SCF ;Coloca flag de carry a 1 CCF ;Faz o complemento da flag de carry Instruções de rotação Instrução Operandos RLA Comentários ;Rotação para a esquerda do conteúdo do acumulador RRA ;Rotação para a direita do conteúdo do acumulador Instruções de salto Instrução Operandos Comentários JP nn ;PC ← nn JP cc,nn ;PC ← nn, se a condição cc for verdadeira Tabela 2.2 — Conjunto de instruções mı́nimo para a arquitectura proposta (ver texto). 39 INÍCIO A ← (END1) B ← (END2) C←A+B (RES1) ← C FIM Figura 2.16 — Diagrama de fluxo para o algoritmo do exemplo 4. resultado na posição de memória cujo endereço é RES1. A seguinte listagem mostra-nos a definição de variáveis para o problema dado: p a r c e l a 1 ( END1 ) → A p a r c e l a 2 ( END2 ) → B r e s u l t a d o ( RES1 ) → C. Com base nesta listagem e no diagrama de fluxo apresentado na figura 2.16 é imediata a seguinte codificação: LD A, ( END1) ; l ê a p r i m e i r a p a r c e l a SCF ; c o l o c a f l a g de c a r r y a CCF ; zero ADC A, ( END2) ; a d i c i o n a a 2 a p a r c e l a à 1 a LD (RES1 ) ,A ; c o l o c a o r e s u l t a d o na p o s i ç ã o ; de memória p r e t e n d i d a . 2.6 Arquitectura do Z80 O Z80 é um Micro-processador com as seguinte caracterı́sticas: • 8 bits de bus de dados; • 16 bits de bus de endereços. Para além das instruções apresentadas na tabela 2.2, o Z80 possui um conjunto muito mais poderoso que pode ser consultado, por exemplo, em [5, 10] e [6] (este último disponı́vel para download). Existem vários tipos de tabelas com o conjunto completo de 40 A F A’ F’ B C B’ C’ D E D’ E’ H L H’ L’ I R IX IY PC SP Figura 2.17 — Registos do Z80 visı́veis ao programador. instruções do Z80 que incluem uma explicação mais ou menos detalhada do seu funcionamento, incluindo a forma como o registo de flags é afectado. Os registos internos visı́veis são 22 sendo por 18 de 8 bits e 4 de 16 bits, figura 2.17. Os pares de registo AF, BC, DE, HL podem funcionar como registos de 16 bits em determinadas situações. O mesmo acontece com os registos auxiliares A0 F0 , B0 C0 , D0 E0 e H 0 L0 . O registo A (acumulador) é um registo preferencial, pois existe um muito maior número de instruções que operam sobre este registo, nomeadamente as aritméticas e lógicas. Existem arquitecturas onde não há um registo preferencial. O programador é que decide que registo usar, uma vez que qualquer registo pode ser usado em condições em tudo idênticas entre eles. Os registos I e R só podem ser acedidos individualmente. Os registos IX e IY são designados por Index Registers. São idênticos em tudo. A sua finalidade consiste em aceder a dados na memória. A figura 2.18 exemplifica a utilização destes registos para este fim. Também podem ser vistos como contendo um dado de 16 bits, i.é., registos normais de 16 bits, nas operações aritméticas de 16 bits. Só falta falar do registo SP (Stack Pointer) que, tal como o seu nome indica, é um ponteiro para a memória. O seu conteúdo não é mais que um endereço de memória. Este registo controla uma estrutura definida na memória designada por stack, figura 2.19. A stack é uma estrutura de memorização onde a última informação lá armazenada é a primeira a sair. Este tipo de estrutura é do tipo LIFO (Last In First Out). O tipo de memória a utilizar para implementar esta estrutura é a RAM (porquê?). Este tipo de estrutura funciona como uma pilha de livros colocados no interior de uma caixa; quando se 41 IX ou IY Endereço Opcode Deslocamento Memória + E = (IX ou IY)+ deslocamento ‘E’ designa o endereço efectivo calculado Figura 2.18 — Exemplificação da utilização dos index registers. SP Memória Endereço D A T Sentido dos endereços crescentes U Figura 2.19 — Exemplificação da utilização da stack. 42 A0−An 2n × 1 2 n × 1 2 n × 1 2 n × 1 2 n × 1 2 n × 1 2 n × 1 2 n × 1 D0 D1 D2 D3 D4 D5 D6 D7 Figura 2.20 — Exemplo de memória organizada em bits. pretender retirar algum desses livros, o primeiro a sair será o último lá colocado. A função do SP é conter o endereço da posição de memória que contém o último valor colocado nela. As instruções do Z80 que manipulam a stack, actualizam automaticamente o SP. 2.7 Memória A memória é a unidade de armazenamento de informação de um sistema de computador. É nela que o microcomputador armazena os programas, os dados e os resultados do processamento. Desta forma torna-se necessária a existência de grandes capacidades de memória. Torna-se pois necessário distinguir as localizações de memória umas das outras para se poder identificar quais as que contêm uma particular informação. A forma de o fazer é associar a cada localização de memória uma combinação binária a que chamamos de endereço. A informação a memorizar em cada endereço é codificada com um determinado número de bits. Então, qual será o número de bits que cada localização de memória contém? Este número não é fixo, sendo normalmente nos microcomputadores, múltiplo de 8 bits. No caso do Z80, e uma vez que o seu bus de dados é de 8 bits, é natural que a memória esteja organizada em bytes. Existem outros microcomputadores com comprimentos de palavra de 16, 32 e mesmo 64 bits que mantêm a memória organizada em bytes. Consultese, por exemplo, [4, 13] onde se pode ver esta (e outras) diferenças dentro da mesma famı́lia de micro-processadores. A memória é geralmente vendida em circuitos integrados organizados de uma determinada maneira, a qual varia de fabricante para fabricante. É possı́vel encontrar pastilhas de memória organizada em bits, figura 2.20, e em bytes, figura 2.21. As pastilhas de memória utilizadas nos sistemas de microcomputador baseados no Z80 estão normalmente organizadas em 32K × 8bits. Um exemplo de aplicação deste tipo de memórias pode ser visto na figura 2.21. Cada pastilha possui: • 15 linhas para endereçamento interno à própria pastilha; 43 A15 A0−A14 CS CS 32K x 8 bits 32K x 8 bits Dados Figura 2.21 — Exemplo de memória organizada em bytes. • 8 linhas para acesso à informação (bus de dados); • 1 linha para selecção de pastilha; • Linhas para alimentação e controlo (por exemplo, escrita e leitura). Se pretendêssemos utilizar pastilhas deste tipo num sistema de microcomputador com o Z80 podı́amos utilizar um esquema de implementação idêntico ao da figura 2.21. Nas figuras 2.22 e 2.23 podem ver-se os diagramas temporais dos sinais afectos às fases de leitura e escrita de dados na memória. Note-se que a diferença de velocidade de funcionamento do circuito do CPU e das pastilhas de memória pode ser tão elevada que seja necessária a introdução de ciclos de espera dos dados (sinal Wait). 44 T1 T2 T3 T4 clk A0-A15 Mreq Rd Wait Wr D0-D7 dados Figura 2.22 — Diagrama temporal dos sinais afectos ao ciclo de leitura da memória. T1 T2 T3 T4 clk A0-A15 Mreq Rd Wait Wr D0-D7 dados Figura 2.23 — Diagrama temporal dos sinais afectos ao ciclo de escrita na memória. 45 46 Capı́tulo 3 Ferramentas para programação de um micro-processador 3.1 Introdução Até aqui a vimos estrutura básica de um microcomputador e a arquitectura interna básica de um CPU. Agora vamos estudar as ferramentas que permitem a programação do microcomputador e o modo de utilização dessas ferramentas. Só a tı́tulo de exemplo e antes de prosseguirmos: • Porque é que existe a linguagem máquina e quais são os problemas (desvantagens) da sua utilização? • Porque é que surge a linguagem assembly e quais as sua vantagens e desvantagens? • Como é que se chama a um programa escrito em Assembly? • Depois de escrito este programa, porque é que se utiliza um Assembler? Qual o código por ele gerado? 3.2 Formato de programação Um programa escrito em linguagem assembly é composto por uma série de linhas, sendo cada linha dividida em quatro partes ou campos, como se mostra no código seguinte: Label LOOP: Mnemónica Operandos LD A, ( HL) Comentários ; c o l o c a no r e g i s t o A o c o n t e ú d o ; da p o s i ç ã o de memória apontada ; por HL LD C, 0FCH ;H s i g n i f i c a que o v a l o r e s t á ; e x p r e s s o em h e x a d e c i m a l 47 LD ( 2 4 0 0 ) ,HL ; c o l o c a nas p o s i ç õ e s de memória ; 2 4 0 0 e 2 4 0 1 o c o n t e ú d o do par ; de r e g i s t o s de 1 6 b i t s HL. A colocação de etiquetas permite ao programador referir-se a determinadas secções ou instruções particulares de um programa sem se preocupar com os seus endereços de memória, ficando os cálculos a cargo do assembler. 3.3 Pseudo-instruções As pseudo-instruções são directivas para o assembler. Levam a que este desempenhe determinadas tarefas durante o processo de assemblagem. Não são instruções executáveis (ou tão pouco executadas) pelo CPU, como ilustra o seguinte exemplo utilizando a pseudoinstrução ORG: Linha Endereço Cód . o b j e c t o Mnemónica Operandos 1 ORG B000H Comentários 2 B000 3E0F LD A, 0FH ;A ← 0F 3 B002 0165FA LD BC, 0 FA65H ;BC ← FA65 4 B005 C5 PUSH BC Na linguagem assembly do Z80 as pseudo-instruções mais utilizadas são as seguintes: Label VAL Mnemónica Operando Comentários DEFB 0F7H ;DEFB ( DEFine Byte ) . ‘ VAL’ p a s s a a ; v a l e r F7H ADDR1 DEFW 0F507H ;DEFW ( DEFine Word ) . ‘ADDR1’ contém ; o e n d e r e ç o MSG DEFM ”OLA! ” ;DEFM ( DEFine Message ) . ‘MSG’ d e f i n e ; a mensagem BUFFER DEFS 256 ;DEFS ( DEFine S t o r a g e ) . Reserva 2 5 6 ; p o s i ç õ e s de memória PUCOD EQU END 3.4 0A5H ;EQU ( EQUate ) . CODigo de Power Up ; F i n a l do programa . Desenvolvimento de um programa em assembly O desenvolvimento de um programa em assembly passa por várias fases. Na primeira fase, que designamos por conceptual, o programador deve analisar o problema e determinar as tarefas que o micro-processador deverá executar. Deve ser realizado um algoritmo ou um diagrama de fluxo. 48 Na fase seguinte deve passar-se à escrita do programa em linguagem assembly, seleccionando-se da forma mais conveniente a correspondência entre os registos disponı́veis e as variáveis a utilizar pelo programa. Deve editar-se o programa. Terminada a fase de edição do programa fonte (source program), este deverá ser “assemblado”. Este perı́odo designa-se por assembly time. É aqui que são detectados alguns erros como por exemplo: • Instrução ilegal; • Sı́mbolo não definido; • Expressão fora da gama; • Sı́mbolo duplicado; • Etiqueta (label) ilegal; • Falta de apóstrofo; • Falta de constante. Depois de detectados e corrigidos todos os erros identificados durante esta fase, o programa poderá ser então executado e testado. A fase de execução é designada por run time ou execution time. Nesta fase, que também pode ser designada por fase de debugging, o programador poderá executar o programa instrução a instrução e ir verificando os conteúdos dos vários registos ou posições de memória relevantes. Poderá ainda utilizar breakpoints para parar a execução do programa no ponto pretendido. É nesta fase que se podem corrigir erros de concepção do programa. Na figura 3.1 podem ver-se, sob a forma de um diagrama de fluxo, as fases de desenvolvimento de um programa escrito em assembly. 3.5 Assembler O assembler é um programa que aceita por entrada um programa escrito em linguagem assembly e que produz o seu equivalente em linguagem máquina, gerando ainda as informações necessárias para o loader. Num programa as etiquetas (labels) podem aparecer nas instruções antes de terem sido definidas. Isto implica a necessidade de existência de assemblers de duas passagens. Na primeira passagem são identificados os sı́mbolos e/ou labels e na segunda passagem são geradas as instruções e os endereços. Mais concretamente um assembler deverá realizar: • A geração de instruções; 49 Sim Escrita do programa (Editor) Erros de sintaxe? Sim “Assemblagem” (Assembler) Não Execução e teste do programa Erros conceptuais? Não Programa operacional Figura 3.1 — Diagrama de fluxo das fases de desenvolvimento de um programa escrito em assembly. • Gerar o código máquina correspondente às mnemónicas utilizadas; • Determinar o valor de cada sı́mbolo; • Processar as constantes; • Atribuir endereços; • O processamento das pseudo-instruções. Estas tarefas podem ser agrupadas em duas passagens distribuı́das do seguinte modo. Na primeira passagem deve-se: • Definir os sı́mbolos e constantes; • Determinar o comprimento das instruções em código máquina; • Actualizar o contador de referência; • “Lembrar” dos valores dos sı́mbolos até à segunda passagem; • Processar algumas pseudo-instruções; • “Lembrar” das constantes. Durante a segunda passagem deve-se: 50 Memória Programa principal Programa principal Subrotina A Subrotina A Loader Subrotina B Subrotina B Subrotina C Subrotina C Figura 3.2 — Exemplo de funcionamento de um loader (ver texto). • Gerar o programa objecto; • Buscar os valores dos sı́mbolos; • Gerar as instruções; • Gerar dados; • Processar as pseudo-instruções restantes. Após a geração do programa objecto pelo assembler, este deve ser colocado em memória para poder ser executado. Esta tarefa é do loader. 3.6 Loaders O loader é um programa que coloca na memória o programa objecto, prepara-o para ser executado e inicı́a a sua execução transferindo o controlo para ele. Em programas de alguma complexidade, ou em situações de tarefas idênticas que tenham que ser executadas em vários pontos do programa, é conveniente a divisão do programa em vários módulos. Temos desta forma um programa principal que utilizará vários sub-programas ou subrotinas. Veja-se a figura 3.2. Uma sub-rotina não passa de um conjunto de instruções que executa uma tarefa bem definida. Para que seja simples a utilização de subrotinas por parte do programador é necessário que possam ser referenciadas simbolicamente, sem que o programador tenha que se preocupar com os endereços das várias partes do programa. 51 Para que estes objectivos possam ser atingidos é necessário que as subrotinas sejam traduzidas numa forma objecto de modo a que o loader as possa colocar em posições arbitrárias de memória e de tal forma que não exista sobreposição entre o programa principal e as diversas subrotinas. Diz-se que os diferentes módulos do programa deverão estar na forma relocatável. Um assembler que produza código relocatável deverá: • Produzir o programa objecto; • Indicar todos os outros módulos que são referenciados pelo módulo assemblado; • Determinar todos os locais no programa que necessitem de ser alterados no caso deste módulo ser colocado numa posição arbitrária de memória. Depois de todos os módulos terem sido assemblados, competirá ao loader: • Juntá-los; • Ajustar as referências entre eles; • Ajustar os locais dos programas que dependem dos endereços onde eles irão ser colocados; • Carregar a memória com todos os módulos (para que o programa possa ser executado). Um loader relocatável deverá realizar as funções de: • Atribuição de espaço de memória para os programas (allocation); • Resolução das referências simbólicas entre os vários módulos (linking); • Ajuste das posições dependentes dos endereços, tais como endereços de constantes, de tal forma que corresponda ao espaço atribuı́do (relocation); • Colocar fisicamente na memória as instruções em código máquina e os dados (loading) . 3.7 Macros Para que o programador não tenha que repetir partes idênticas do seu código, existe a facilidade de processamento de macros. Esta facilidade permite a definição de uma abreviatura a que corresponde uma parte do programa, permitindo a sua utilização sempre que esse conjunto de instruções seja utilizado. No caso do assembler do Z80, uma macro define-se da seguinte forma: 52 <nome> MACRO [#<P0>, #<P1>, #<P2> , · · ·, #<Pn>] i n s t r u ç ã o 1 i n s t r u ç ã o 2 ··· i n s t r u ç ã o n ENDM Aqui, “<nome>” indica o nome pelo qual a macro é identificada, “[#<P 0>, #<P 1> , #<P 2>, ..., #<P n>]” indicam os parâmetros (que podem existir ou não), “instrução i” indica a instrução que deverá ser executada e “ENDM” indica o final de definição da macro. O código MOV MACRO #P0 , # P1 LD #P0 , #P1 ENDM define uma macro para utilizar a mnemónica MOV (do Intel 8086) em vez da mnemónica LD (do Z80) podemos definir a macro. A grande questão que se levanta neste momento é a seguinte: quando utilizar macros e quando utilizar subrotinas? Antes de optarmos por qualquer uma das duas devemos ter em atenção, entre outros aspectos, os seguintes: • Quando utilizamos uma sub-rotina temos um bloco de código colocado numa determinada área da memória, sendo esse bloco executado sempre que a sub-rotina for chamada; • Quando utilizamos macros uma dada mnemónica (mais precisamente, um identificador) é expandida num dado bloco de instruções, que constituem a definição da macro, sendo esse bloco repetido no programa sempre que seja encontrada a mnemónica que identifica a macro; • Tal como visto no exemplo anterior, a utilização das macros permite ao utilizador redefinir as mnemónicas já existentes atribuindo-lhes um outro nome, ou então construir as novas instruções formadas à custa das instruções já existentes; • Uma sub-rotina envolve um desvio no fluxo “normal” do programa para depois se regressar ao ponto de onde essa sub-rotina foi invocada, logo torna-se necessário guardar o endereço de partida; • As subrotinas poupam espaço em memória uma vez que só existe uma única cópia destas em memória. 53 54 Capı́tulo 4 Estudo do conjunto de instruções 4.1 Introdução Neste capı́tulo estudaremos em detalhe um conjunto genérico de instruções, vendo a forma como a sua execução afecta o registo de flags. Adicionalmente, estudaremos também os diferentes modos de endereçamento, isto é, as diferentes formas de obtenção de operando(s). Na parte final estudaremos com pormenor a área de stack. 4.2 Formato simbólico das instruções Num micro-processador existem vários tipos de instruções. Podemos distinguir entre instruções de transferência de dados, aritméticas, lógicas, controlo do programa, rotação e deslocamento, controlo do CPU e um grupo de propósito geral. O comprimento da palavra do micro-processador vai condicionar o número de bytes do código de operação (operation code — opcode) de cada instrução. Será de toda a conveniência que logo após o acesso à memória para leitura da instrução (fetch) esta seja imediatamente descodificada, isto é, o micro-processador “saiba” de que instrução se trata e tudo aquilo deve fazer com ela. Por exemplo, para um microprocessador de 8 bits o primeiro byte caracterizará completamente a instrução. Consoante o modo como são acedidos os dados a processar, ou seja, o tipo de endereçamento, a instrução poderá ser composta por mais bytes. No caso do Z80 os opcodes podem ter de 1 a 4 bytes de comprimento. 4.3 Flags Consoante o tipo de instrução, o seu resultado pode afectar ou não o registo de flags, permitindo assim ao programador testar os resultados e tomar as decisões convenientes. 55 No caso do Z80 o registo de flags (F e F’) contém 6 bits que são colocados a ‘1’ ou a ‘0’, sendo o seu significado o seguinte: • Flag de carry (C) — é colocada a ‘1’ se ocorrer um carry (um “transporte” ou “vai um”) numa instrução de soma ou um borrow (“falta um”) numa instrução de subtracção; caso contrário é colocada a ‘0’. As instruções de rotação e deslocamento também afectam esta flag de acordo com o bit que aı́ é armazenado como resultado da rotação ou deslocamento. As instruções lógicas colocam esta flag sempre a ‘0’. • Flag de adição/subtracção (N) — é utilizada nas operações que envolvem uma adição ou subtracção de números representados em Binary Coded Decimal (BCD), permitindo distinguir estes dois tipos de operações. Nas adições é colocada a ‘0’, nas subtracções é colocada a ‘1’. • Flag de paridade/overflow (P/V) — o seu significado varia de acordo com o tipo de operação que se está a realizar. Se foi realizada uma operação aritmética, detecta a ocorrência de uma situação de overflow (V=1). Nas operações lógicas ela indica se o número de bits a ‘1’ do resultado é par (P=1) ou ı́mpar (P=0). • Flag de half carry ou auxiliary carry (H ou Ac) — numa instrução aritmética, esta flag será colocada a ‘1’ caso exista carry ou borrow entre os bits 3 e 4. • Flag de zero (Z) — esta flag é colocada a ‘1’ se o resultado da operação for zero. As instruções aritméticas, lógicas e de comparação são exemplos de instruções que afectam esta flag. • Flag de sinal (S) — a flag de sinal armazena o estado do bit mais significativo do acumulador (bit 7). 4.4 Tipos de endereçamento Os diferentes tipos de endereçamento permitem-nos obter o endereço efectivo da instrução. Numa instrução de manipulação de informação será o endereço do dado que irá ser processado. Numa instrução de salto será o endereço da próxima instrução ao ser processada. Podemos dividir os modos de endereçamento em dois tipos: modo directo; e modo indirecto. No modo directo o endereço é retirado directamente da instrução ou calculado combinando um valor existente na instrução com o conteúdo de um registo. No modo indirecto o endereço calculado é o endereço de uma posição de memória que contém o endereço efectivo (final). Existem duas formas de calcular o endereço efectivo: o seu valor é indicado na própria instrução; ou na instrução é indicado o registo que o contém. 56 Registos Opcode R 0 1 .. . Operando R .. . n Figura 4.1 — Modo de endereçamento tipo registo. Dependendo da flexibilidade do conjunto de instruções, poderão existir as diversas variantes ou combinações destes modos de endereçamento. 4.4.1 Modo registo Neste modo um dos operandos está contido num dos registos do CPU, sendo especificado no opcode o registo onde ele se encontra, figura 4.1. O código LD A, B ;A f i c a com o c o n t e ú d o de B mostra-nos um exemplo de utilização deste modo de endereçamento em assembly do Z80. 4.4.2 Absoluto O endereço da posição de memória a atingir é indicado na própria instrução, figura 4.2. Se for completo, 16 bits, designa-se por absoluto longo, figura 4.2(b). Se for indicado apenas 1 byte, designa-se por absoluto curto, não se encontrando implementado no Z80. Veja-se a figura 4.2(a). Pode ainda ser indicado de uma forma indirecta, figura 4.2(c) (não implementado no Z80). O código LD A, ( 0 FA80H) mostra-nos um exemplo de utilização do modo absoluto longo em assembly do Z80. 4.4.3 Imediato Neste modo uma constante é especificada como fazendo parte da instrução, i.é., um dos operandos é uma constante. O código 57 Memória Opcode Endereço .. . 0 Endereço Operando .. . (a) Absoluto curto Memória Opcode Endereço .. . Operando .. . (b) Absoluto longo Memória .. . Opcode Endereço Endereço Indirecto .. . Operando .. . (c) Absoluto indirecto Figura 4.2 — Modo de endereçamento tipo absoluto. 58 Registos Opcode R End. indirecto R Memória Operando Figura 4.3 — Modo de endereçamento tipo registo indirecto. LD B, 2 1 0 LD HL, 0 E642H mostra-nos exemplos de utilização deste modo em assembly do Z80. 4.4.4 Registo indirecto O endereço efectivo do operando está contido num registo ou par de registos, figura 4.3. O código LD LD B , ( HL) (DE) , A mostra-nos exemplos de utilização deste modo em assembly do Z80. 4.4.5 Auto-incremento e auto-decremento Estes modos de endereçamento utilizam-se geralmente para manipular tabelas e/ou listas. O modo auto-incremento é idêntico ao modo registo indirecto, só que depois de acedida a memória, o registo que contém o endereço efectivo é incrementado, do comprimento do operando, automaticamente, figura 4.4(a). No modo de auto-decremento é efectuada uma subtracção ao registo que contém o endereço efectivo, no valor igual ao comprimento do operando. A subtracção é efectuada antes do acesso à memória, figura 4.4(b). No Z80 as instruções que utilizam este modo são a LDI, LDD, CPI, CPD, LDIR, LDDR, CPIR, CPDR, onde I se refere a Incremento e D a Decremento. R designa as Repetitivas. Estes modos podem ser implementados, para qualquer instrução, da seguinte forma: LD A, ( HL) ; auto−i n c r e m e n t o INC HL ; tamanho operando 1 b y t e DEC DE ; auto−decremento LD (DE) ,A ; tamanho operando 1 b y t e . 59 Registos R Opcode R End. indirecto + Memória Operando (a) Auto-incremento Registos R Opcode R End. indirecto − Memória Operando (b) Auto-decremento Figura 4.4 — Modos de endereçamento tipo auto-incremento e auto-decremento. Registos Opcode R Deslocamento R End. de base Memória + Operando Figura 4.5 — Modo de endereçamento tipo indexado. 4.4.6 Indexado Neste modo o endereço é calculado adicionando um endereço de base e um deslocamento. O endereço de base é indicado na própria instrução. O deslocamento encontra-se armazenado num registo. Este modo de endereçamento é geralmente utilizado para aceder a tabelas e matrizes. O endereço de base da matriz é especificado na instrução e o valor do registo corresponde ao ı́ndice, figura 4.5. O Z80 não implementa este tipo de endereçamento. Contudo pode ser obtido da seguinte forma: LD DE, BASE ; l ê o e n d e r e ç o b a s e LD L ,A ; e s t e n d e o d e s l o c a m e n t o para LD H, 0 ; 16 b i t s ADD HL,DE ; c a l c u l a e n d e r e ç o i n d e x a d o LD B , ( HL) ; t r a n s f . da memória para o r e g . B 60 Registos Opcode Ext. sinal R End. base R Deslocamento Memória Ext. sinal + Deslocamento Operando Figura 4.6 — Modo de endereçamento tipo base. Registos Opcode RI RB End. base RB Deslocamento RI Ext. sinal Memória Ext. sinal Deslocamento + Operando Figura 4.7 — Modo de endereçamento tipo base-indexado. Como poderia ser implementado se o deslocamento fosse de 16 bits e estivesse guardado nas posições de memória INDEX e INDEX+1? 4.4.7 Base Neste modo o endereço é formado de modo semelhante ao do indexado, figura 4.6. O endereço de base está armazenado num registo e o deslocamento (offset), que pode ser longo ou curto, faz parte da instrução. O Z80 suporta este tipo de endereçamento, devendo para o efeito ser utilizado um dos registos de 16 bits IX ou IY. É possı́vel indicar na instrução um offset, com um comprimento de 1 byte. O código seguinte ilustra esta utilização: LD 4.4.8 ( IX +3) ,E Base-indexado Neste modo o endereço de base e o deslocamento encontram-se armazenados em registos, figura 4.7. O Z80 não suporta este modo. 61 Ext. sinal Opcode Memória Deslocamento Operando + PC (a) Relativo deslocamento curto. Memória Opcode Operando Deslocamento + PC (b) Relativo deslocamento longo. Figura 4.8 — Modo de endereçamento tipo relativo. 4.4.9 Relativo O endereço é calculado somando um deslocamento, indicado na instrução, ao valor actual do Program Counter, figura 4.8. O programador referencia a posição de memória através da colocação de uma etiqueta, a qual é depois utilizada na instrução de referência à memória. Durante a fase de assembling é calculado o deslocamento necessário a partir do valor actual do contador de referência, sendo gerada uma mensagem de erro se o endereço absoluto estiver fora do alcance do endereçamento relativo. No Z80 este tipo de endereçamento existe unicamente nas instruções de salto, sendo o deslocamento limitado a 1 byte (deslocamento curto, figura 4.8(a)). O deslocamento calculado é um valor com sinal representado em complemento para dois, podendo variar entre -128 e +127. O seguinte excerto de um programa em assembly do Z80 ilustra a utilização deste modo de endereçamento: JR LAB ; s a l t a para ‘LAB’ ... LAB LD A, B 62 4.5 Instruções Nesta secção iremos estudar um conjunto de instruções genérico tendo por base o Z80. Serão dados exemplos elucidativos dos diferentes tipos de instruções. A notação que vai sendo introduzida é válida para todas as instruções seguintes. 4.5.1 Transferência de informação As instruções de transferência de informação permitem a movimentação de informação do tipo: registo–registo; registo–memória; e memória–registo. A mnemónica utilizada, tal como visto nos exemplos apresentados até ao momento, é LD (LoaD), sendo em seguida indicados os operandos. Em primeiro lugar a origem e em segundo o destino. São permitidas transferências de 8 bits e 16 bits. O registo de flags só pode ser transferido de e para a memória associado ao registo acumulador (A), utilizando para o efeito as instruções de manipulação da stack (secção 4.6. No Z80 existem cinco modos de endereçamento nas instruções de transferência entre registos e memória e vice-versa. 4.5.1.1 Modo absoluto Este modo permite a transferência de 8 bits e 16 bits. O formato para a transferência de 8 bits é LD A , ( nn ) LD ( nn ) , A e para 16 bits LD dd , ( nn ) LD ( nn ) , dd onde ‘nn’ representa um número de 16 bits (neste caso é um endereço), ‘dd’ um par de registos, BC, DE, HL, ou um dos registos SP ou ‘xy’, sendo ‘xy’ um dos registos IX ou IY. Exemplo 5 (Transferência de informação — modo absoluto) Transferir o conteúdo da posição de memória com endereço 2345H para o registo A e o conteúdo do par de registos HL para os endereços 65FDH (L) e 65FEH (H). LD A, ( 2 3 4 5H) ;A ← ( 2 3 4 5H) LD ( 6 5FDH) , HL ; ( 6 5FDH) ← L ; ( 6 5FEH ) ← H 63 4.5.1.2 Modo imediato No modo imediato também podem ser transferidos 8 ou 16 bits. Para 8 bits LD r, n LD ( ss ) , n onde ‘r’ representa um registo A, B, C, D, E, H ou L, ‘n’ um dado de 8 bits, ‘ss’ o par de registos HL ou ‘xy’+offset e ‘offset’ é um número de 8 bits, ou seja, ‘n’. Para 16 bits LD dd , nn Exemplo 6 (Transferência de informação — modo imediato) Transferido valor 6 para o registo C, o valor FA02H para IY e FE0FH para SP. 4.5.1.3 LD C, 6 ;C ← 6 LD IY , 0 FA02H ; IY ← FA02H LD SP , 0 FE0FH ; SP ← FE0FH Modo base No modo base apenas podem ser transferidos 8 bits LD r , ( xy + o f f s e t ) LD ( xy + o f f s e t ) , r Exemplo 7 (Transferência de informação — modo base) Transferir o conteúdo para posição de memória indicada por IX+3 para o registo E e o conteúdo do registo A para a posição de memória indicada por IY+50. 4.5.1.4 LD E , ( IX+3) ; E ← ( IX+3) LD ( IY + 5 0 ) , A ; ( IY +50) ← A Modo registo indirecto Neste modo também só podem ser transferidos 8 bits LD r , ( ss ) LD ( ss ) , r LD A , ( qq ) LD ( qq ) , A onde ‘qq’ é um dos pares de registos BC ou DE. 64 Exemplo 8 (Transferência de informação — modo registo indirecto) Transferir para o registo D o conteúdo da posição de memória apontada por HL e para a posição de memória apontada por DE o conteúdo do registo A. 4.5.1.5 LD D, ( HL) ;D ← ( HL) LD (DE) , A ; (DE) ← A Outros exemplos de instruções de transferência LD HL , ADDRESS LD r , ( HL) LD A, ( ADDRESS) LD r ,A LD HL , ( INDIR ) LD (HL ) , r LD HL , nn LD (ADDRESS) , HL As transferências entre registos podem envolver 8 ou 16 bits, podendo ser efectuadas, no caso de 8 bits, através das instruções LD r , r0 LD A, x LD x, A onde ‘r0 ’ pode tomar os mesmos valores de ‘r’ e ‘x’ é um dos registos I ou R. No caso da transferência de 16 bits LD SP , xx onde ‘xx’ é um dos registos IX, IY ou o par de registos HL. 4.5.2 Manipulação de blocos Vamos apresentar aqui não só as instruções de manipulação de blocos, mas também as instruções de troca de registos. O Z80 possui um conjunto muito poderoso de instruções para manipulação de blocos de informação. Estas instruções possibilitam ao programador o controlo automático do número de bytes transferidos, bem como a actualização automática dos ponteiros envolvidos. 65 As instruções LDI e LDD movem um byte de dados do endereço referenciado por HL para o endereço referenciado por DE, decrementam BC e Incrementa (LDI) ou Decrementa (LDD) DE e HL. As instruções LDIR e LDDR repetem LDI ou LDD até BC−1 = 0. Por seu lado, as instruções CPI e CPD comparam o conteúdo do acumulador com o conteúdo da posição de memória apontada por HL, decrementam BC e Incrementa (CPI) ou Decrementa (CPD) HL. Ambas colocam a flag de zero a ‘1’ se os operandos forem iguais, ficando a ‘0’ nas outras situações. A flag de parity/overflow fica a ‘0’ se BC−1 = 0 ou a ‘1’ se BC−1 6= 0. CPIR e CPDR repetem CPI e CPD até BC−1 = 0. Exemplo 9 (Movimentação de dados) Mover um byte da posição de memória com endereço ADR1 para a posição de memória com endereço ADR2: LD BC, 1 ; número de b y t e s a mover LD HL , ADR1 ; i n i c i a l i z a origem LD DE, ADR2 ; in ici ali za destino ; ou LDD LDI Mover 10 bytes de dados da posição de memória com endereço ADR1 para a posição de memória com endereço ADR2: LD BC, 1 0 ; número de b y t e s a mover LD HL , ADR1 ; i n i c i a l i z a origem LD DE, ADR2 ; in ici ali za destino ; transfere 10 bytes LDIR ou LD BC, 1 0 ; número de b y t e s a mover LD HL , ADR1+9 ; i n i c i a l i z a origem LD DE, ADR2+9 ; in ici ali za destino ; transfere 10 bytes LDDR Examinar as posições de memória, começando pelo endereço ADR, até ser encontrada uma contendo 0 ou terem sido examinadas 256: LD BC, 1 0 0H ; 2 5 6 p o s i ç õ e s a t e s t a r LD HL , ADR ; i nı́ c i o do b l o c o SUB A ;A=0 ; Z=1 s e e x i s t i r um z e r o CPIR ; Z=0 s e BC=0 As instruções de troca de registos são EX DE, HL 66 que troca os conteúdos de DE e HL, EX AF,AF0 que troca os conteúdos de A com A0 e F com F0 , EXX que troca os conteúdos dos pares de registos BC com B0 C0 , DE com D0 E0 e HL com H0 L0 e EX (SP ) , xx que troca o conteúdo dos dois bytes no topo da stack com um dos registos IX ou IY, ou o par de registos HL. 4.5.3 Instruções aritméticas No caso do Z80, estas instruções permitem a realização de adições e subtracções, com ou sem carry. 4.5.3.1 Aritmética de 8 bits As instruções aritméticas de 8 bits podem ser efectuadas entre o acumulador e um registo de 8 bits ou entre o acumulador e um byte numa posição de memória endereçado num modo imediato, através do par de registos HL (registo indirecto), ou através dos registos IX ou IY (base+offset). A sua forma geral é ADD r Esta instrução adiciona o conteúdo do acumulador ao conteúdo do registo indicado por ‘r’, depositando o resultado no acumulador. ADD n ;A ← A + n ADD ( ss ) ;A ← A + ( s s ) ADC s ;A ← A + s + c a r r y A última instrução adiciona o conteúdo do acumulador, o conteúdo do registo indicado por ‘s’ e o bit da flag de carry, depositando o resultado no acumulador. Esta instrução deve ser usada quando se pretende utilizar aritmética de precisão múltipla. A instrução SUB s ;A ← A − s subtrai o conteúdo do acumulador ao conteúdo do registo indicado por ‘s’, depositando o resultado no acumulador. A instrução SBC s ;A ← A − s − c a r r y 67 subtrai o conteúdo do acumulador, o conteúdo do registo indicado por ‘s’ e o bit da flag de borrow (carry), depositando o resultado no acumulador. Esta instrução deve ser usada quando se pretende utilizar aritmética de precisão múltipla. Na instrução INC d ;d ← d + 1 é adicionado o valor um ao número guardado em ‘d’. A instrução DEC d ;d ← d − 1 subtrai um ao número guardado em ‘d’. Nestas instruções, ‘s’ é um dos registos de ‘r’, ‘n’, ou ‘(ss)’; e ‘d’ é um dos registos de ‘r’ ou ‘(ss)’. 4.5.3.2 Aritmética de 16 bits No Z80, a aritmética de 16 bits só é possı́vel entre registos, não sendo possı́vel endereçar dados em memória. A instrução seguinte adiciona os 16 bits de HL com os 16 bits do registo (ou par de registos) ‘ww’ ADD HL , ww A instrução seguinte adiciona os 16 bits de HL, os 16 bits do registo (ou par de registos) ‘ww’ e o bit da flag de carry ADC HL , ww A instrução seguinte subtrai os 16 bits de HL, os 16 bits do registo (ou par de registos) ‘ww’ e o bit da flag de borrow (carry). SBC HL , ww A seguinte instrução adiciona os 16 bits de ‘xy’ com os 16 bits do registo (ou par de registos) ‘ee’. Não se pode adicionar IX a IX ou IY a IY. ADD xy , e e A instrução INC dd adiciona um ao registo (ou par de registos) ‘dd’. A instrução DEC dd 68 subtrai um ao registo (ou par de registos) ‘dd’. Nestas instruções ‘ww’ representa um dos pares de registos BC, DE, HL, ou o registo SP, e ‘ee’ um dos pares de registos BC ou DE, um dos registos ‘xy’ ou SP. Exemplo 10 (Aritmética de precisão múltipla) Efectuar a adição de dois números de 64 bits ADD8: LD B, 8 ; 64 b i t s = 8 b y t e s SUB A ; c a r r y =0 LD HL , NUM1 ; i nı́ c i o d o s números LD DE, NUM2 LD A , ( DE) ; l ê 1 b y t e de um o p e r a n d o ADC A , ( HL) ; soma um b y t e do o u t r o o p e r a n d o LD (HL ) , A ; armazena a soma de 8 b i t s INC DE ; incrementa os p o n t e i r o s INC HL DJNZ ADD8 ; c o n t a o número de a d i ç õ e s ; efectuadas 4.5.4 Instruções lógicas Estas instruções permitem a realização de operações lógicas elementares a conjunção (AND), disjunção (OR) e disjunção exclusiva (XOR) (eXclusive OR). Podem ser efectuadas entre o acumulador e um registo de 8 bits, um byte endereçado no modo imediato, um byte endereçado através do par HL ou utilizando os registos IX ou IY com um deslocamento. Estas instruções não permitem o endereçamento absoluto. A instrução AND s faz o AND do acumulador com o byte indicado por ‘s’. A instrução OR s faz o OR do acumulador com o byte indicado por ‘s’. Finalmente, a instrução XOR s faz o XOR do acumulador com o byte indicado por ‘s’. Exemplo 11 (Instruções lógicas) Alguns exemplos de utilização das instruções lógicas são os seguintes. 69 4.5.5 AND BIC ;AND de A com a e t i q u e t a BIC OR (HL) ;OR de A com o s d a d o s a p o n t a d o s p o r HL XOR C ;XOR de A com C Manipulação de bits O Z80 possui um conjunto de instruções que permitem a manipulação de bits, fazendo o seu teste e inicialização (set ou reset), i.é., podemos testar o valor, colocar a ‘1’ ou a ‘0’ um dado bit de um registo ou de uma posição de memória. A instrução seguinte testa o bit número ‘b’ do byte indicado por ‘d’, colocando a flag de zero a ‘1’ se o bit testado é ‘0’ e vice-versa BIT b, d A instrução seguinte coloca a ‘1’ o bit número ‘b’ do byte indicado por ‘d’ SET b, d A instrução RES b, d coloca a ‘0’ o bit número ‘b’ do byte indicado por ‘d’. Podemos utilizar instruções lógicas para atingirmos os mesmos objectivos. Assim, utilizando “máscaras”, podemos: • Colocar bits a ‘1’ fazendo OR com ‘1’s nas posições desejadas; • Colocar bits a ‘0’ fazendo AND com ‘0’s nas posições desejadas; • Complementar bits fazendo XOR com ‘1’s nas posições desejadas; • Testar bits a ‘0’ fazendo AND com ‘1’s nas posições desejadas. Exemplo 12 (Manipulação de bits) Colocar a 1 o bit 6 do acumulador SET 6 ,A OR 40H ou Colocar a 0 o bit 3 do acumulador RES 3 ,A AND 0F7H ou Complementar o bit 2 do acumulador 70 XOR 04 Testar o bit 5 dois do acumulador BIT 5, A AND 20H ou 4.5.6 Controlo de programa As instruções de controlo de programa permitem a alteração do fluxo sequencial do programa de uma forma condicional ou não. Pode ser efectuado um salto para uma dada instrução, no caso de uma dada condição ser verdadeira, um salto para um dado endereço de memória, no caso de uma dada condição ser verdadeira, Podemos basear os saltos condicionais num dos seguintes tipos de decisões: • Teste do valor de 1 bit (‘0’ ou ‘1’); • Igualdade ou desigualdade de dois valores; • Comparação de dois valores. O modo de endereçamento utilizado num salto pode ser relativo — limitado ao intervalo fechado −126 a +129 posições de memória (instruções com mnemónica JR) —, ou absoluto (instruções com mnemónica JP). Utilizando endereçamento relativo JR e JR cc0 , e Utilizando endereçamento absoluto: JP nn JP cc , nn onde ‘e’ representa um número na gama −128 a +127, ‘cc0 ’ uma das condições C, NC, Z, NZ, ‘cc’ uma das condições C, NC, Z, NZ, PO, PE, P, M e ‘nn’ um número de 16 bits. Exemplo 13 (Saltos condicionais) Saltar para DEST se o bit 5 do acumulador for 1 (podemos usar JP em vez de JR) BIT 5, A JR NZ , DEST Saltar para DEST se o bit 6 do registo C for 0 71 BIT 2, C JR Z , DEST Saltar para DEST se o bit 5 da posição de memória com endereço ADR for 1 LD HL , ADR BIT 5 , (HL) JR NZ , DEST Saltar para DEST se o bit 7 do acumulador for 1 AND A JP M, DEST Saltar para DEST se o acumulador contém o número VALOR CP VALOR JR Z , DEST Saltar para DEST se o conteúdo do acumulador é diferente do conteúdo da posição de memória com endereço ADR LD HL , ADR CP (HL) JR NZ , DEST Saltar para DEST se o conteúdo do acumulador for 0 AND A JR Z , DEST Saltar para DEST se o conteúdo do acumulador não for FFh INC A JR NZ , DEST Saltar para DEST se o conteúdo do acumulador for 1 DEC A JR Z , DEST Saltar para DEST se a posição de memória com endereço ADR contém 0 LD HL , ADR INC (HL) DEC (HL) JR Z , DEST Saltar para DEST se o conteúdo da posição de memória com endereço OPER1 for menor do que o conteúdo da posição de memória com endereço OPER2 72 LD A , ( OPER1) LD HL , OPER2 CP (HL) JR C , DEST Saltar para DEST se o conteúdo da posição de memória com endereço OPER1 for menor ou igual ao conteúdo da posição de memória com endereço OPER2 LD A , ( OPER2) LD HL , OPER1 CP (HL) JR NC, DEST Saltar para DEST se o acumulador contém um número com sinal maior ou igual ao número VALOR CP VALOR ; e f e c t u a a comparação JP PE , FNEG ; houve o v e r f l o w ? JP P , DEST ; não , s a l t a s e r e s . p o s i t i v o JR DONE FNEG: JP M, DEST DONE: NOP ; sim , s a l t a s e r e s . n e g a t i v o Designamos por ciclo (loop) a repetição de uma sequência de instruções. O Z80 possui uma instrução que nos permite implementar esta estrutura de uma forma muito simples. Trata-se da instrução DJNZ e onde ‘e’ especifı́ca um número na gama −128 a +127. Se pretendermos utilizar esta instrução teremos que: 1. Carregar o registo B com o número de vezes que a sequência vai ser executada; 2. Executar a sequência; 3. Usar a instrução DJNZ que decrementa o registo B e volta ao passo 2 no caso do resultado ser diferente de 0. Esta instrução tem duas limitações: 1. O contador é de 8 bits; • Qual o número máximo de vezes que uma sequência pode ser executada? • Com que valor se deve carregar o registo B? 73 2. Utiliza endereçamento relativo • Qual é a consequência? Limita o número de instruções dentro do ciclo. Exemplo 14 (Ciclos ou loops) Ciclo simples LD LOOP: B , NVEZES ··· ··· DJNZ LOOP Dois loops, um dentro de outro LD C , NVEZESE LOOPE: LD B , NVEZESI LOOPI : ··· ··· DJNZ LOOPI DEC C JR NZ , LOOPE Loop executado mais de 256 vezes: LD LOOP: BC, NVEZES ··· ··· 4.5.7 DEC BC LD A, B OR C JR NZ,LOOP Deslocamento e rotação As instruções de deslocamento (shift) permitem operar em qualquer registo ou posição de memória. Podem ser de dois tipos: shift aritmético — preservam o bit de sinal; e shift lógico — introduzem um ‘0’. Shift lógico, do byte especificado em ‘d’, para a direita: SRA d Shift aritmético, do byte especificado em ‘d’, para a esquerda com introdução de ‘0’: SLA d Shift aritmético, do byte especificado em ‘d’, para a direita com introdução de ‘0’: 74 SRL d As instruções de rotação (rotate) permitem a rotação de um registo ou posição de memória, podendo esta ser efectuada através da flag de carry ou não. Rotação para a esquerda do conteúdo do acumulador; o bit mais significativo é reintroduzido como bit menos significativo, ficando a flag de carry também afectada com este bit: RLCA Rotação para a direita do conteúdo do acumulador; o bit menos significativo é reintroduzido como bit mais significativo, ficando a flag de carry também afectada com este bit: RRCA Rotação para a esquerda do conteúdo do acumulador: o bit mais significativo é colocado na flag de carry, entrando como bit menos significativo o bit que estava nesta flag: RLA Rotação para a direita do conteúdo do acumulador; o bit menos significativo é colocado na flag de carry, entrando como bit mais significativo o bit que estava nesta flag: RRA RLC d idêntica a RLCA só que roda o byte indicado por ‘d’. RRC d idêntica a RRCA só que roda o byte indicado por ‘d’. RL d idêntica a RLA só que roda o byte indicado por ‘d’. RR d idêntica a RRA só que roda o byte indicado por ‘d’. 4.5.8 Grupo aritmético de propósito geral Este grupo contém as instruções para ajuda nos cálculos com números representados em BCD, bem como para complementar e negar o conteúdo do acumulador e ainda para inicialização da flag de carry. DAA ; Decimal A d j u s t Accumulator 75 Esta instrução converte o conteúdo do acumulador para um número válido BCD compactado, após a soma ou subtracção de operandos compactados (dois dı́gitos BCD num único byte, ou seja, 4 bits por dı́gito). CPL ; Complement Accumulator complementa para um o conteúdo do acumulador. NEG ; Negate Accumulator nega o conteúdo do acumulador. Efectua a subtracção 0−A e coloca o resultado em A. CCF ; Complement Carry F l a g complementa a flag de carry. SCF ; S e t Carry F l a g cola a ‘1’ a flag de carry. 4.5.9 Grupo de controlo do CPU Com as instruções pertencentes a este grupo podemos controlar o modo de funcionamento do CPU, inclusive indicar-lhe para não fazer nada! NOP ; No OPeration ! A instrução HALT pára o CPU. DI ; Disable Interrupts Com esta instrução o flip-flop de interrupções fica com o valor ’0’, i.e., IFF ← 0. EI ; Enable I n t e r r u p t s Com esta instrução o flip-flop de interrupções fica com o valor ’1’, i.e., IFF ← 1. A instrução IM z coloca o CPU no modo de interrupções 0, 1 ou 2. 4.6 Stack Durante a execução de um programa temos muitas vezes necessidade de armazenar temporariamente a informação que irá ser processada. Para facilitar este processo é implementada, na maioria dos sistemas de computador, uma estrutura destinada a esse fim. 76 É usada uma zona de memória de leitura/escrita (read/write) do processador, designada por stack, existindo um registo que serve de ponteiro para o topo da stack designado por Stack Pointer. Esta estrutura encontra-se organizada como LIFO (Last In First Out), i.é., o último elemento a ser lá colocado será o primeiro a ser lido. A stack ao ser preenchida vai crescendo para os endereços mais baixos de memória. Como o stack pointer aponta para o topo da stack, ele deve ser decrementado sempre que introduzamos um novo valor na stack e incrementado quando retiramos algum valor da stack. Assim para colocarmos na stack vários registos do CPU terı́amos que efectuar as seguintes operações. Armazenar na stack: LD SP , 0 FEA0H DEC SP LD (SP ) , H DEC SP LD (SP ) , L DEC SP LD (SP ) , A DEC SP LD (SP ) , F ; i n i c i a l i z a r SP Retirar da stack: LD C , ( SP ) INC SP LD B , ( SP ) INC SP LD E , ( SP ) INC SP LD D, ( SP ) INC SP Para facilitar esta tarefa de manipulação da stack existem habitualmente instruções dedicadas, efectuando automaticamente o incremento ou decremento do stack pointer. No Z80 estas instruções são a PUSH e a POP que permitem o armazenamento e a retirada, respectivamente, da stack de um par de registos ou registo de 16 bits. Utilizando estas instruções, os exemplos acima apresentados reduzem-se significativamente. Armazenar na stack: 77 LD SP , 0 FEA0H PUSH HL PUSH AF Retirar da stack: POP BC POP DE Num programa onde utilizemos a stack para armazenamento temporário teremos que ter o cuidado de efectuar igual número de PUSH’s e de POP’s. O Z80 permite colocar e retirar da stack os registos IX ou IY, ou os pares de registos AF, BC, DE, HL, através da utilização das instruções PUSH e POP. 4.7 Subrotinas Muitas vezes temos num programa um conjunto de instruções, desempenhando uma tarefa bem definida, que é repetida por diversas vezes. Nesta situação será mais conveniente considerar esse conjunto de instruções como um bloco autónomo, o qual é chamado sempre que essa tarefa necessita de ser executada. A este bloco autónomo damos o nome de subrotina. A utilização de subrotinas tem diversas vantagens já que conduz a programas: • Com menos código; • Mais legı́veis; • De mais fácil debugging; • Etc.. Para a chamada das subrotinas temos instruções próprias, existindo no Z80 dois formatos de instrução: CALL nn ; chamada i n c o n d i c i o n a l CALL cc , nn ; chamada c o n d i c i o n a l . A instrução CALL, para além de saltar para o local onde se encontra a sub-rotina, armazena previamente o endereço de retorno, de tal forma que após a execução da subrotina o controlo do programa passa para a instrução imediatamente a seguir ao CALL. No final da sub-rotina deve ser colocada a instrução RET (return), a qual vai à stack buscar o endereço de retorno (armazenado no topo pela instrução CALL), colocando-o no program counter. Podemos dizer que: 78 PUSH PC JP nn teriam o mesmo efeito que CALL nn e POP PC teria o mesmo efeito que RET. Normalmente são passados à sub-rotina um conjunto de dados que irão ser processados por ela. Estes dados são geralmente designados por parâmetros de entrada. A sub-rotina origina um conjunto de resultados, os parâmetros de saı́da, que deverão ser passados de volta ao programa que chama a sub-rotina. Diz-se que uma sub-rotina é reentrante se ela pode ser interrompida por um programa e chamada por esse outro. Nestas condições quando a chamada termina, a sub-rotina interrompida reinicia-se onde tinha sido suspensa. Para que não exista destruição de informação é necessário armazenar todos os registos antes da sub-rotina reiniciar a execução. Uma sub-rotina recursiva é aquela que se chama a si própria. Numa estrutura de programação com este tipo de subrotinas é necessário armazenar na stack: • O endereço de retorno; • Os parâmetros de entrada e de saı́da; • As variáveis locais. 4.8 Passagem de parâmetros A passagem de parâmetros entre uma sub-rotina e o programa que a chama pode ser realizada de várias formas. Iremos referir as técnicas mais comuns, dando exemplos de aplicação com base no conjunto de instruções do Z80. 4.8.1 Registos Os parâmetros podem ser passados nos registos do CPU, sejam eles o dado ou o endereço de memória onde se encontra o dado. O programa, antes de efectuar a chamada da sub-rotina, deve colocar nos registos escolhidos, os parâmetros de entrada. Antes da instrução RET a sub-rotina colocará nos registos escolhidos, os parâmetros de saı́da. No caso do Z80 podemos utilizar: 79 • O acumulador para passar parâmetros de 8 bits; • O par HL para endereços (16 bits); • O par DE para um segundo endereço devido à existência da instrução EX DE,HL; • IX ou IY como locais óbvios para a colocação de um endereço base de uma estrutura onde são conhecidos os deslocamentos dos vários elementos. 4.8.2 Área de memória Neste caso utiliza-se uma área de memória read/write para o armazenamento dos parâmetros. No Z80 pode implementar-se esta técnica utilizando: • Um index register com o endereço base dessa área, utilizando depois vários offsets para endereçar os diferentes parâmetros; • O par de registos HL, para guardar o endereço base, uma vez que o processo do ponto anterior é muito lento. Através da manipulação de HL temos acesso aos diferentes parâmetros. O programa que chama a sub-rotina deve: 1. Colocar os diferentes parâmetros na memória; 2. Guardar num index register ou no par de registos HL o endereço de base; 3. Chamar a sub-rotina. No caso de ser utilizada uma área de memória comum deixa de ser possı́vel a reentrância e a recursividade, já que os parâmetros anteriores serão destruı́dos em cada chamada da sub-rotina. 4.8.3 Stack Esta é a forma mais geral de passagem de parâmetros, possibilitando a utilização de estruturas recursivas e reentrantes. O programa que chama a sub-rotina deve: • Antes da sua chamada, reservar espaço para os parâmetros de saı́da e colocar os parâmetros de entrada na stack; • Depois do retorno, deve remover os parâmetros da stack (limpar a stack). Este processo é simples se os parâmetros de entrada tiverem sido colocados acima da área vazia destinada aos resultados. 80 A implementação no caso do Z80, que vamos ver em seguida, é em tudo semelhante à forma como é feita num PC baseado num micro-processador da famı́lia Intel 80x86. Consulte-se [8], por exemplo, para mais pormenores. Programa principal: ; r e s e r v a e s p a ç o para p a r â m e t r o s de s aı́ d a XOR A ; c o l o c a 0 em A PUSH AF ; parâmetro de s aı́ d a de 8 b i t s LD HL, 0 ; c o l o c a 0 em HL PUSH HL ; parâmetro de s aı́ d a de 1 6 b i t s ··· ; c o l o c a p a r â m e t r o s de e n t r a d a LD A , ( PAR8 ) ; l ê parâmetro de 8 b i t s PUSH AF ; c o l o c a −o na s t a c k LD HL , ( PAR16 ) ; l ê parâmetro de 1 6 b i t s PUSH HL ; c o l o c a −o na s t a c k ··· CALL sub−r o t i n a ; l i m p a a s t a c k r e t i r a n d o os p a r â m e t r o s de s aı́ d a POP HL ; r e t i r a parâmetro de s aı́ d a de 1 6 b i t s POP AF ; r e t i r a parâmetro de s aı́ d a de 8 b i t s PUSH IX ; IX é o LD IX , 0 ; IX ← SP ADD IX , SP ··· Sub-rotina: frame pointer ··· LD r , ( IX+5+2×NPAR) ; em ’ r ’ f i c a um ; parâmetro de 8 b i t s LD rpL , ( IX+4+2×NPAR) ; LSB do parâmetro ; de 1 6 b i t s LD rpH , ( IX+5+2×NPAR) ;MSB do parâmetro de ; 16 b i t s ··· LD SP , IX ; r e p õ e s t a c k p o i n t e r limpando v a r i á v e i s ; locais POP RET IX ; r e p õ e frame p o i n t e r ; fim da sub−r o t i n a De notar que IX está a ser utilizado como um segundo ponteiro para a stack. A este 81 tipo de ponteiro auxiliar dá-se o nome de frame pointer. Esta designação deve-se ao facto deste ponteiro criar um outro ponto na stack por onde se pode “ver” o que ela contém. Qual é o outro ponto? Uma vez colocados os parâmetros pelo programa chamador, podemos retirá-los se tivermos em conta que o parâmetro NPAR se encontra em IX+4 + 2×NPAR. NPAR varia de 0 (último parâmetro colocado na stack pelo programa que chama a sub-rotina) até N (primeiro parâmetro colocado na stack). NPAR é multiplicado por 2 porque colocamos sempre 2 bytes na stack, mesmo quando só queremos guardar um byte (caso do registo A que se junta sempre ao registo F). O algarismo 4 surge da adição dos 2 bytes que são o endereço de retorno da sub-rotina (colocados pela instrução CALL) e dos 2 bytes da instrução PUSH IX logo no inı́cio da sub-rotina. Quando queremos aceder a um byte apenas, em vez de somar 4 somamos 5, pois este byte é sempre o mais significativo (foi colocado na stack usando PUSH AF). Podemos usar esta técnica para chamar subrotina(s) a partir de uma sub-rotina. 82 Capı́tulo 5 Entrada e Saı́da 5.1 Introdução Até ao presente capı́tulo foi estudado o funcionamento de um sistema computacional, sem se atender à comunicação com o mundo exterior. Foram estudadas: • A arquitectura básica de um CPU; • A forma como a memória se integra no sistema; • As estruturas que se definem na memória; • E a forma como o CPU comunica com a memória. Neste capı́tulo vamos estudar a forma de integração de interfaces nos barramentos do sistema computacional e as técnicas fundamentais de ligação entre as interfaces e os periféricos (impressoras, terminais alfa-numéricos, etc.). Consulte-se [7], por exemplo, para mais pormenores. 5.2 Mapas de endereçamento As estruturas fı́sicas que fazem parte de um sistema computacional (registos, memória e interfaces), necessitam de ser identificadas com um código único. Ao pretender-se aceder a uma em particular, o seu código deve ser lançado no local e tempo convenientes, por forma a que a estrutura em causa fique disponı́vel. Este código de identificação designa-se por endereço da estrutura. Tal como estudado anteriormente, o CPU tem um conjunto de linhas de endereço acessı́veis do exterior que lhe permitem distinguir um número mais ou menos elevado desses elementos. Assim, conseguimos distinguir 2n elementos com n linhas de endereço. Podemos traduzir esta ideia de uma outra forma; as n linhas que saem do CPU possibilitam um mapa de endereçamento com N elementos, sendo N = 2n . 83 A15 Mapa de endereçamento A0−A14 0000 Memória Memória Interface 7FFF 8000 Periférico 32K x 8 bits Interface Registos FFFF Dados Controlo Figura 5.1 — Exemplo de mapa de endereçamento. Neste caso, a interface é acedida por qualquer endereço dos 32K superiores (ver texto). Este mapa pode ser ou não preenchido, consoante se coloquem estruturas fı́sicas com que o CPU possa comunicar. Por exemplo, no caso da memória, se tivéssemos um sistema computacional com 16 linhas de endereços, o mapa de endereçamento por elas criado teria 216 = 65536 = 64K células. Estando este mapa totalmente disponı́vel, poder-se-ia utilizar a sua primeira metade com uma pastilha de memória de 32K elementos. Assim, este mapa de endereçamento teria a sua metade superior livre para se utilizar, sendo possı́vel colocarmos mais memória ou outro tipo qualquer de estrutura. Podemos, através da utilização deste método, colocar na parte do mapa de endereçamento disponı́vel, circuitos de interface, figura 5.1. De notar que a selecção da interface é efectuada unicamente pela linha A15 o que implica que qualquer endereço da metade superior do mapa de endereçamento selecciona o circuito de interface. Apesar de se gastar metade do mapa de endereçamento com apenas um circuito de interface, tal facto não se torna relevante se não precisarmos de introduzir mais nenhuma estrutura no sistema. Se pretendermos introduzir mais estruturas no sistema temos que descodificar mais linhas de endereço, reduzindo assim o número de combinações que levam a escolher a dita estrutura. Compare-se a figura 5.1 com a figura 5.2. O aumento da lógica introduzida liberta posições do mapa de endereçamento, o que permite a introdução de estruturas adicionais. Existe um compromisso entre a quantidade de lógica utilizada e o número de posições do mapa de endereçamento deixadas livres. O grande inconveniente da utilização desta 84 A15 A14 Mapa de endereçamento 0000 A0−A13 Memória Interface Memória Periférico 32K x 8 bits Registos Interface CFFF D000 FFFF Dados Controlo Figura 5.2 — Outro exemplo de mapa de endereçamento onde a interface é acedida por qualquer endereço dos 16K superiores (ver texto). técnica (Input/Output memory mapped ou simplesmente memory mapped) é a necessidade de introdução de uma quantidade apreciável de lógica exterior para descodificar interfaces colocadas no mapa de endereçamento de memória. O micro-processador 68000 da Motorola implementa este tipo de mapa de endereçamento [9, 16]. Alguns sistemas computacionais, têm para além do mapa de endereçamento de memória, um mapa especı́fico para as transacções de entrada/saı́da (I/O mapped). Para tal, basta que o CPU tenha mais uma linha acessı́vel do exterior que permite distinguir os dois mapas de endereçamento. O Z80 tem os dois mapas de endereçamento: de memória; e de I/O. Este CPU possui uma linha designada por ‘IO/M’ cujo estado distingue o mapa a endereçar. Quando está “em baixo” o mapa a endereçar será o mapa de memória. Quando está “em cima” o mapa a endereçar será o mapa de I/O. Como é que o CPU, estando a executar um programa, pode aceder a um ou a outro mapa de endereçamento? Muito simples: existem dois grupos de instruções para aceder a cada um dos mapas! Todas as instruções estudadas até aqui acedem ao mapa de endereçamento de memória. As instruções que se referem ao mapa de endereçamento de entrada/saı́da são para entrada IN A, ( n ) ;A ← ( n ) IN r , ( C) ;A ← (C) INI ; (HL) ← (C) ;HL ← HL+1 ;B ← B−1 85 INIR ; (HL) ← (C) ;HL ← HL+1 ;B ← B−1 a t é B= 0 IND ; (HL) ← (C) ;HL ← HL−1 ;B ← B−1 INDR ; (HL) ← (C) ;HL ← HL−1 ;B ← B−1 a t é B= 0 e para saı́da OUT (n ) , A ; (n ) ← A OUT (C ) , r ; (C) ← r OUTI ; (C) ← (HL) ;HL ← HL+1 ;B ← B−1 OTIR ; (C) ←(HL) ;HL ← HL+1 ;B ← B−1 a t é B= 0 OUTD ; (C) ← (HL) ;HL ← HL−1 ;B ← B−1 OTDR ; (C) ← (HL) ;HL ← HL−1 ;B ← B−1 a t é B= 0 O grande inconveniente desta técnica é que estas instruções, como vimos, são em número muito mais reduzido do que as que acedem ao mapa de endereçamento de memória. Esta situação conduz a uma menor versatilidade nas condições de programação. É da responsabilidade do programador a utilização destas instruções. Na figura 5.3 pode ser visto um exemplo. A interface da esquerda encontra-se no mapa de endereçamento da memória, sendo seleccionada pelo endereço 8000H. A interface da direita encontra-se no mapa de endereçamento de I/O, sendo seleccionada pelo endereço 40H. 5.3 Interfaces As interfaces são circuitos que se encontram ligados aos barramentos do sistema e a um periférico particular. Têm por objectivo fazer a adaptação entre os sinais eléctricos do sistema computacional e os sinais eléctricos dos respectivos periféricos. 86 A15 A6 IO/M IO/M CS CS Dados Controlo Dados Interface Controlo Interface Figura 5.3 — A interface é acedida pelo mapa de endereçamento da memória. A interface da direita é acedida pelo mapa de endereçamento de I/O (ver texto). Geralmente, internamente são constituı́das: • Pela lógica que permite fazer a adaptação dos sinais referidos; • Por registos de memorização para armazenamento temporário da informação a trocar entre o periférico e o sistema computacional. Normalmente existe uma diferença muito grande de velocidades na troca de informação entre o sistema computacional e a interface e entre a interface e o periférico. Esta é uma das razões que faz com seja necessária a existência dos registos de memorização. A utilização destes registos é necessária por causa da informação a transferir e ainda pelo controlo dessa transferência. Assim, existem geralmente dois tipos de registos: registos de memorização temporária da informação; e registos que guardam o estado da interface em cada instante. Os registos responsáveis por guardar o estado em que a interface se encontra em cada instante são os registos de controlo e estado ou status. Os registos de controlo devem conter informação que lhes permitam saber se os registos de armazenamento temporário de informação (internos à interface, também designados por registos de dados ou data registers), se encontram ou não livres para nova transacção. O conteúdo destes registos é actualizado automaticamente pela lógica interna à interface de acordo com o seu estado em cada instante. Estes registos podem ser lidos pelo CPU sob o controlo de programa. Desta forma podemos saber o estado da interface nesse instante. Na figura 5.4 pode ser visto um esquema genérico de ligação de uma interface a um sistema computacional. 87 Barramento do sistema computacional Interface Periférico Figura 5.4 — Esquema geral de interligação de uma interface. 5.3.1 Protocolo de programação para periféricos de saı́da Suponhamos que a um sistema computacional está ligado um periférico de saı́da, através de um circuito de interface que contém internamente dois registos cujas funções obedecem ao anteriormente descrito: • Registo de dados (data register); • Registo de estado (status register). Quando o sistema computacional pretende transferir informação para este periférico vê-se confrontado com as diferenças de tempo de transferência e a necessidade de assegurar uma transferência correcta da informação. Para assegurar esta última necessidade o sistema deve consultar o registo de estado e quando verificar que o registo de dados se encontra livre, procede à transferência da informação. Os circuitos internos à interface são responsáveis por colocar o registo de estado na situação de “interface ocupada” e proceder à transferência da informação residente no registo de dados para o periférico. Finda esta operação devem colocar o registo de estado no estado “interface livre”, permitindo ao sistema escrever nova informação no registo de dados. O fluxo-grama da figura 5.5 ilustra o processo aqui descrito. Exemplo 15 (Ligação de uma interface de saı́da) A figura 5.6 esquematiza a integração de um periférico num sistema computacional cujo CPU é um Z80. Pretende-se enviar para este periférico o conteúdo de uma zona de memória cujo endereço inicial é BLOCO e cujo número de bytes se encontra armazenado na posição de memória com endereço NUM. Observando-se a figura 5.6 conclui-se que a interface se encontra ligada no mapa de endereçamento de entrada/saı́da, uma vez que é seleccionada quando a linha IO/M estiver no nı́vel lógico ‘1’. Tanto no Z80, como em qualquer outro sistema computacional com mapa especı́fico para I/O, esta situação só ocorre quando se utilizam instruções de entrada/saı́da. 88 Não Leitura do registo de estado Interface livre? Sim Escrita no registo de dados Figura 5.5 — Diagrama de fluxo do protocolo de programação para saı́da de dados. IO/M A7 A6 CS Interface C/D A0 Dados Controlo Periférico Estado Dados Figura 5.6 — Esquema de interligação da interface do exemplo 15. 89 Por outro lado, para seleccionarmos a interface (entrada ‘CS’ activa “baixa”) necessita ter também, em simultâneo, as linhas A6 e A7 nos nı́veis lógicos ‘0’ e ‘1’, respectivamente. Os endereços que seleccionam esta interface obedecem ao padrão: A7 A6 A5 A4 A3 A2 A1 A0 1 0 X X X X X l onde ‘X’ representa um estado qualquer (‘0’ ou ‘1’). O estado da linha A0 (‘l’) ao assumir o valor lógico ‘1’ selecciona o registo de estado e ao assumir o valor lógico ‘0’ selecciona o registo de dados. Supondo que escolhemos (pura opção) o valor lógico ‘0’ para as linhas A1 a A5, os endereços que seleccionam os registos de estado e dados são, respectivamente, 81H e 80H. Na figura 5.7 mostra-se o algoritmo proposto. As variáveis utilizadas são: • PONT — ponteiro para a posição de memória cujo conteúdo vai ser transferido; • CONT — contador do número de bytes que falta transferir; • STATUS — endereço do registo de estado da interface. Supomos que se o registo contém o valor ‘1’ a interface está ocupada e se for ‘0’ está livre; • DATA — endereço do registo de dados da interface. Na listagem seguinte apresenta-se uma implementação em assembly do Z80. DATA EQU 80H STATUS EQU 81H BLOCO EQU 1000H NUM EQU 0FFH LD HL , BLOCO ; v a l o r i n i c i a l do p o n t e i r o LD A , (NUM) ; v a l o r i n i c i a l do c o n t a d o r LD B, A LD C , DATA IN A , ( STATUS ) ; l e i t u r a do r e g i s t o de e s t a d o AND A ; a f e c t a a f l a g de z e r o JR NZ , L0 ; i n t e r f a c e ocupada ? L0 : ; s aı́ d a da i n f o r m a ç ã o e a c t u a l i z a OUTI ; ponteiro e contador JR NZ , L0 ; c o n t i n u a a t é t r a n s f e r i r t o d o s ; os b y t e s END 90 INÍCIO PONT = BLOCO CONT = (NUM) Sim (STATUS) = 1? Não (DATA) = (PONT) Não PONT = PONT + 1 CONT = CONT -1 CONT = 0? Sim FIM Figura 5.7 — Diagrama de fluxo para o protocolo de saı́da do exemplo 15. 5.3.2 Protocolo de programação para periféricos de entrada De um modo idêntico aos periféricos de saı́da, as interfaces que estão ligadas a periféricos de entrada têm também, para além dos registos de dados, um registo de estado. O protocolo a estabelecer com este tipo de interfaces é análogo ao estudado para os periféricos de saı́da figura 5.8. Exemplo 16 (Ligação de uma interface de entrada) A figura 5.9 esquematiza a integração de um periférico de entrada num sistema de microcomputador controlado por um Z80. Pretende-se ler para uma zona de memória a informação a receber do periférico de entrada. Esta informação deve ser armazenada sequencialmente na memória do sistema e a partir do endereço BLOCO, até que seja detectado um byte (código) com o valor 0DH. A interface encontra-se integrada no mapa de endereçamento de I/O, podendo os seus registos de dados e de estado ser acedidos respectivamente pelos endereços C0H e C1H. O raciocı́nio efectuado para chegar a estas conclusões é análogo ao do exemplo 15, pelo que se dispensa a sua apresentação. As variáveis utilizadas no algoritmo são (figura 5.10): • PONT — ponteiro para a posição de memória cujo conteúdo vai ser transferido; • COD — código da informação que faz terminar a leitura do periférico; 91 Leitura do registo de estado Não Interface com informação válida? Sim Leitura do registo de dados Figura 5.8 — Diagrama de fluxo do protocolo de programação para entrada de dados. IO/M A7 A6 CS Interface C/D A0 Dados Controlo Periférico Estado Dados Figura 5.9 — Esquema de interligação da interface do exemplo 16 92 INÍCIO PONT = BLOCO COD = 0DH Não (STATUS) = 0? Sim Não (PONT) = (DATA) PONT = PONT + 1 (DATA) = COD? Sim FIM Figura 5.10 — Diagrama de fluxo para o protocolo de entrada de dados do exemplo 16. • STATUS — endereço do registo de estado da interface; • DATA — endereço do registo de dados da interface. Na listagem seguinte apresenta-se uma implementação em assembly do Z80. DATA EQU C0H STATUS EQU C1H BLOCO EQU 1000H COD EQU 0DH LD HL , BLOCO ; v a l o r i n i c i a l do p o n t e i r o IN A , ( STATUS ) ; l e i t u r a do r e g i s t o de s t a t u s AND A ; a f e c t a a f l a g de z e r o JR NZ , L0 ; i n t e r f a c e com i n f o r m a ç ã o v á l i d a ? IN A , (DATA) ; l e i t u r a da i n f o r m a ç ã o LD (HL ) , A ; armazena i n f o r m a ç ã o na memória INC HL ; actualiza ponteiro CP COD ; c ó d i g o = 0DH? JR NZ , L0 ; c o n t i n u a a t é t r a n s f e r i r t o d o s L0 : ; os b y t e s END 93 Barramento do sistema computacional Periférico 1 Interface 1 Periférico 2 Interface 2 .. . Periférico n Interface n Figura 5.11 — Diagrama de blocos dum sistema computacional com periféricos de entrada e periféricos de saı́da. 5.3.3 Protocolo de programação num sistema computacional com periféricos de entrada e de saı́da Nos dois últimos pontos analisámos como é que um CPU, sob controlo de um programa, consegue transaccionar correctamente informação com periféricos de saı́da e de entrada. Para o caso da figura 5.11, supondo que os periféricos recebem e enviam informação mais lentamente do que o CPU a pode processar, qual será o protocolo de programação a estabelecer, de modo a que o CPU possa efectuar transacções com todos os periféricos simultaneamente? Mantendo o raciocı́nio já efectuado para cada tipo de interface, o processador deve consultar o registo de estado de cada interface e se: a interface estiver livre, então efectuar a transacção correspondente; se a interface estiver ocupada, então consultar o registo de estado da próxima interface. Este raciocı́nio pode ser mais facilmente especificado na forma do fluxo-grama da figura 5.12. 5.4 Tipos de interfaces Até agora estudámos a ligação das interfaces aos barramentos do sistema computacional. Mas..., quais os tipos de ligações dos circuitos de interface aos periféricos? Existem fundamentalmente duas técnicas: ligação série; e ligação paralela. Consoante o tipo de ligação assim se designam por interfaces série ou interfaces para94 INÍCIO (Status 1) = Livre? Transacção com interface 1 Sim Transacção com Não Sim (Status 2) = Livre? interface 2 Não Sim Sim (Status n) = Livre? Transacção com interface n Não Mais transacções? Não FIM Figura 5.12 — Diagrama de fluxo do protocolo para entrada e saı́da de dados. 95 Barramento de endereços Descodificador Barramento de dados Interface .. . dados Periférico Paralela handshak Figura 5.13 — Diagrama de blocos de interligação de uma interface paralela. lelas. Os protocolos estudados para a comunicação entre as interfaces e o sistema computacional são mantidos válidos, independentemente do tipo de ligação destas aos periféricos. 5.4.1 Interface paralela Estes circuitos ligam-se aos periféricos por um conjunto de linhas que transportam os dados. A transferência de informação entre o bus de dados do sistema computacional e os periféricos é feita usando as linhas do bus de dados, figura 5.13. Existem ainda do lado do periférico, para além das linhas de dados, linhas de controlo (handshake) que controlam a transferência e validam a informação presente nas suas linhas de dados. Este tipo de interface utiliza-se para ligar periféricos que: se encontrem fisicamente junto do sistema computacional; e/ou que necessitem de velocidades de transferência relativamente elevadas. 5.4.1.1 Estudo da interface paralela Z80 PIO O circuito de interface Z80 PIO (Parallel Input/Output), é um dispositivo com duas portas, programável que fornece uma interface compatı́vel TTL. O CPU pode configurar a PIO para interface com outros periféricos, sem necessidade de lógica adicional, tais como: • Teclados; • Leitoras de papel e cartões; • Impressoras; • Programadores de ROMs; • Etc.. 96 É distribuı́do numa caixa com 40 pinos e entre outras facilidades apresentam-se as seguintes: • Duas portas de interface de 8 bits independentes bidireccionais, com handshake para controlo da transmissão de dados; • Handshake para interrupções para uma resposta mais rápida; • Quatro modos distintos de funcionamento (todos eles com handshake para controlo de interrupções): • Saı́da de dados (byte); • Entrada de dados (byte); • Bus bidireccional (porta A apenas); • Controlo (8 linhas); • É incluı́da lógica para vectorização automática de interrupções, sem necessidade de lógica externa, baseada no esquema Daisy Chain; • 8 saı́das capazes de alimentar um par Darlington de transı́stores; • Todas as entradas e saı́das são compatı́veis com os nı́veis TTL; • Apenas uma fonte de alimentação de 5 volts e um relógio com apenas uma fase. Um diagrama de blocos da Z80 PIO pode ser visto na figura 5.14. A estrutura interna consiste em: • Interface com o CPU; • Lógica para controlo interno; • Lógica da porta A; • Lógica da porta B; • Lógica para controlo de interrupções. O diagrama de blocos de cada uma das portas, A e B, pode ser visto na figura 5.15. Dele fazem parte: • 6 registos com handshake incluı́do; • Registo de 8 bits para entrada; • Registo de 8 bits para saı́da; 97 Interface com o CPU +5V GND CLK Interface com Lógica para controlo interno Barramento de dados 8 Barramento de controlo I/O com o barramento do CPU os periféricos 8 barramento interno 6 Controlo de interrupções Dados ou controlo handshake Porta A I/O 8 Porta B I/O Dados ou controlo handshake 3 Linhas de controlo das interrupções Figura 5.14 — Diagrama de blocos do circuito Z80 PIO. • Registo de 2 bits para controlo do modo de funcionamento; • Registo de 8 bits para máscara (mask) de I/O (modo controlo); • Registo de 2 bits para controlo do registo de máscara. A figura 5.16, mostra a distribuição das linhas no chip. Deste conjunto de linhas, fazem parte os grupos de: • Controlo da PIO; • Controlo de interrupções; • Alimentação; • Controlo da porta A; • Controlo da porta B; • Bus de dados. A PIO entra no estado de reset a partir do momento que é alimentada. Neste estado são efectuadas as operações de: • Reset aos registo de máscara de ambas as portas para desabilitar todos os bits das portas de dados; • As linhas do bus de dados são colocadas num estado de alta impedância e os sinais de READY são postos a ‘0’; 98 Registo de selecção de I/O (8 bits) Registo de controlo de modo (2 bits) Habilitação da saı́da Registo de saı́da de dados (8 bits) barramento interno 8 bits para o periférico (dados ou controlo) Registo de controlo de mask (2 bits) Registo de mask (8 bits) dados de ent. Registo de entrada de dados (8 bits) Ready Lógica para controlo de handshake Pedidos de interrupção Strobe Linhas de handshake Figura 5.15 — Diagrama de blocos das portas A e B do circuito Z80 PIO. D0 D1 D2 D3 D4 D5 D6 D7 Selecciona porta (B/A) Selecciona controlo/dados Habilita chip M1 IORQ RD +5V GND CLK 19 20 1 40 39 38 3 2 6 5 4 37 36 35 26 11 25 8 Linhas 7 a 15 A7−A0 18 16 A RDY A STB 8 Z80 PIO Linhas 27 a 34 B7−B0 21 17 B RDY B STB 23 24 22 INT Habilita INT IN Habilita INT OUT Figura 5.16 — Esquema de ligações do circuito Z80 PIO. 99 Modo 3 apenas INT AND/ HIGH/ Mask Enable OR LOW follows 0 1 1 1 MB MB MB MB MB MB MB MB MB = 0 Bit monitorizado MB = 1 Bit não monitorizado Figura 5.17 — Programação das interrupções do circuito Z80 PIO. • Selecção do modo 1 de funcionamento; • Aos vectores de endereçamento não é feito reset; • Reset aos flip-flops de interrupção das portas; • É feito um reset aos registos de saı́da das portas. A escolha do modo de funcionamento faz-se com base na palavra: M1 M0 X X 1 1 1 1 , onde M1 e M0 podem ter os valores: 00 − s aı́ d a 01 − e n t r a d a 11 − b i d i r e c c i o n a l 11 − c o n t r o l o . No caso de escolha do modo de controlo, a próxima palavra indica quais as linhas de entra/saı́da com base na seguinte máscara: I /O I /O I /O I /O I /O I /O I /O I /O, onde I/O pode ter o valor ‘1’ para entra e ‘0’ para saı́da. A palavra para leitura do vector de interrupções é a seguinte: V7 V6 V5 V4 V3 V2 V1 0 Note-se que o bit menos significativo é zero. As interrupções são controladas com base na palavra apresentada na figura 5.17. É possı́vel fazer o set ou reset do flip-flop de habilitação de interrupções sem modificar o estado do resto da palavra de controlo de interrupções, usando a palavra IE X X E 0 0 1 1 100 onde, IE=0 − d e s a b i l i t a i n t e r r u p ç õ e s ; IE=1 − h a b i l i t a i n t e r r u p ç õ e s ; E =0 − não limpa i n t e r r u p ç õ e s p e n d e n t e s ; E =1 − limpa i n t e r r u p ç õ e s p e n d e n t e s ; X − tanto faz . Exemplo 17 (Exemplos de programação da PIO) Programar a porta B da PIO para saı́da: LD A, 0 FH OUT (PIOBP ) , A Programar a porta A da PIO para entrada: LD A, 4 FH OUT (PIOAP ) , A Programar a PIO para o modo bidireccional: LD A, 8 FH OUT (PIOAP ) , A Programar a porta A da PIO para controlo com as linhas 1, 5 e 6 para entrada e as linhas 0, 2, 3, 4 e 7 para saı́da: 5.4.2 LD A, 0CFH OUT (PIOAP ) , A LD A, 0 1 1 0 0 0 1 0B OUT (PIOAP ) , A Interface série Quando se pretendem ligar ao sistema computacional periféricos que se encontrem fisicamente afastados usam-se as interfaces série. A razão principal é o reduzido número de linhas necessárias para esta ligação, contrariamente à ligação paralela, figura 5.18. Quantas linhas de dados eram necessárias numa interface paralela para transmitir 8 bits de informação? Uma outra razão para a existência da comunicação série é que normalmente os periféricos nas condições descritas atrás não necessitam de velocidades de transferência elevadas. Não se deve confundir necessidade com desejo de taxas de transferência mais elevadas! A ideia base assenta em transmitir por uma única linha a informação existente num registo paralelo da interface. O tipo de funcionamento que se pretende é o de um shift 101 Barramento de endereços Descodificador Barramento de dados Interface dados Periférico Série handshak Figura 5.18 — Exemplo de interligação de uma interface série. register, onde a informação lhe é fornecida em paralelo pelo sistema computacional e sai para o exterior pela sua linha série à velocidade imposta por um sinal de relógio conveniente. A transmissão pode ser half duplex ou full duplex. Quando se pretende receber informação série, esta ideia continua válida. O sinal que contém a informação entra pela linha de serial input do shift register e vai sendo deslocado nos flip-flops internos do registo com o auxı́lio de um sinal de relógio cuja frequência é a mesma que a do relógio de transmissão do sinal. Uma vez que os dados não passam de um sinal que vai mudando o seu estado assincronamente, existem dois problemas que é necessário resolver: • Amostrar o sinal em intervalos de tempo que correspondam aos bits individuais; • Existência de um mecanismo que transforme os bits recebidos em caracteres válidos. Para resolver o primeiro problema basta que exista um relógio de sincronismo que tenha a mesma frequência no receptor e no emissor. Para garantir a integridade dos caracteres torna-se necessário indicar quando é que um caracter começa e quando é que ele acaba. Se soubermos por quantos bits é formado um caracter basta, sabermos quando é que ele começa para sabermos quando é que acaba. Na transmissão série é usual encontrar caracteres com 5, 6, 7 e 8 bits de comprimento. Para indicar quando é que começa (quando deve começar a contar) geralmente são usadas duas soluções distintas: transmissão sı́ncrona e transmissão assı́ncrona. Na transmissão série sı́ncrona é necessário existir um bit de dados válido em cada transição activa do sinal de relógio. Uma vez garantido que as frequências dos sinais de relógio, a menos de um certo erro, são iguais de um e do outro lado da linha, os dados transmitidos irão ser reconhecidos adequadamente na estação receptora. Os limites de dados válidos são indicados pela introdução de uma sequência de bits conhecida por ambas as estações. Esta sequência de bits é designada por caracter de sincronismo (SYNC character). Tipicamente este caracter pode ter o valor 01101001. Podem existir 1 ou 2 102 start registo 01110011 1 1 0 0 1 1 1 0 pari. stop relógio Figura 5.19 — Transmissão série assı́ncrona. caracteres de sincronismo antes de cada bloco de dados. A estação que recebe pode estar à espera destes caracteres de sincronismo afim de reconhecer o inı́cio de um conjunto de dados válido. O primeiro bit a seguir a um SYNC é o primeiro bit válido do primeiro caracter. Este tipo de transmissão série é pouco utilizado. (Porquê?) Na transmissão série assı́ncrona os caracteres só são transmitidos quando existirem dados válidos. Entre as transmissões de dados válidos, o sinal de saı́da é mantido “alto”, designando-se este estado por mark. Cada caracter a transmitir é “encapsulado” por um bit de inicio de transmissão, designado por start bit, e 1, 1.5 ou 2 bits de fim, designados por stop bits. O start bit é sempre um ‘0’ (nı́vel baixo), enquanto que o(s) stop bit(s) é (são) ‘1’ (nı́vel alto). Entre este bits é que são transmitidos os bits que constituem o caracter. Existe ainda a possibilidade de transmissão de um bit de paridade, para controlo de erro, antes do(s) stop bit(s). Veja-se a figura 5.19. Qual a máxima e mı́nimas percentagens de tempo útil numa transmissão deste tipo? Resposta: assistam às aulas teóricas! 5.4.2.1 Estudo da interface série Am8251 O chip Am8251 foi desenhado para transmissão de dados série. É uma USART (Universal Synchronous/Asynchronous Receiver/Transmitter) programável. Oferece as capacidades de: • Transferência de dados sı́ncrona ou assı́ncrona; • Sinalização half ou full duplex; • Os dados são transmitidos em forma de caracteres com 5, 6, 7 ou 8 bits/caracter; • Bit de paridade par, ı́mpar ou nenhum; • Controlo dos sinais modem efectuados pelo micro-processador; • O caracter de SYNC programável; • Registos separados para códigos de controlo e para escrita de dados para a lógica de transmissão (apenas no Am9551). 103 TxRDY TxE RD WR Reset CLK CS C/D RxRDY SYNDET Bus de dados D7−D0 Buffers do Lógica de controlo de bus de dados Read/Write Interface com o microprocessador Secção de controlo de registos Bus interno Secção receptora RxD RxC Controlo de modem DTR RTS DSR CTS Secção transmissora Conjunto da interface de dados TxC TxD Figura 5.20 — Diagrama de blocos do circuito Am8251. Como pode ser visto na figura 5.20, este circuito é constituı́do pelos seguintes blocos: • Control register; • Transmitter; • Receiver; • Modem control; • Read/write control. O control register (receptor e transmissor) recebe palavras de controlo de 8 bits enviados pelo CPU, sendo estas palavras usadas para estabelecer e/ou alterar o modo de funcionamento e controlar os nı́veis dos sinais. Por seu lado, o transmissor (transmitter) recebe dados de 8 bits do bus de dados do CPU e adiciona-lhes bits de formatação de acordo com o modo de operação estabelecido pelo control register. Depois transmite a informação por uma linha série; são também gerados sinais apropriados que indicam se os transmit registers estão vazios, por forma a que o CPU possa enviar outro dado de 8 bits. A secção de transmissão contém dois registos (buffers): output register — os dados são transmitidos a partir deste registo; transmitter buffer register — onde o CPU escreve os dados. Internamente, o Am8251, detecta quando o output register está vazio, carregando-o em seguida com os dados do transmitter buffer register. Desta forma o CPU pode escrever 104 RD WR C/D CS Comentários 0 1 0 0 receiver register → data bus 1 0 0 0 data bus → transmitter register 0 1 1 0 status register → data bus 1 0 1 0 data bus → control register X X X 1 data bus → high impedance Tabela 5.1 — Operação funcional do Am8251 (USART). um byte para ser transmitido (no transmitter buffer register) enquanto outro está a ser transmitido. O receptor (receiver) aceita dados chegados via série, convertendo-os em caracteres, retirando-lhes os bits colocados a mais pelo transmissor do outro lado da linha, de acordo com as condições impostas pelo seu control register, envia dados para o bus de dados do CPU quando este o desejar e desenvolve a sinalização necessária para se saber quando existe um dado acabado de chegar ou a não existência de qualquer dado. É constituı́do por dois registos: receiver input register — onde vão sendo recebidos os vários bits que constituem um caracter; e receiver buffer register — onde, depois de ter sido recebido um caracter completo pelo receiver input register, vão ser colocados os bits automaticamente para posterior leitura por parte do CPU. O modem control contém sinais de controlo standard para coordenar a operação de interface de comunicações e conjunto de dados. O bloco read/write fornece a interface com o micro-processador. No que se refere ao endereçamento, existem quatro linhas responsáveis pela selecção da operação que pretendemos efectuar: • Read (RD); • Write (W R); • Control/data (C/D); • Chip select (CS). Na tabela 5.1, mostra-se a operação funcional durante um read ou write. A USART deve ser inicializada a seguir a um reset do sistema, antes que um dado possa ser transmitido. Para se proceder à sua inicialização deve enviar-se duas, três ou quatro palavras, dependendo do modo de funcionamento pretendido. Existem duas formas de provocar o retorno da lógica de controlo ao mo-de de controlo (mode control): através de um comando especı́fico; ou a seguir a um reset. O Am8251 pode funcionar no modo sı́ncrono ou assı́ncrono, dependendo dos códigos de modo de controlo. Estes códigos podem ser vistos na figura 5.21. 105 7 6 5 4 3 2 1 0 00 01 10 11 — — — — Modo Modo Modo Modo sı́ncrono assı́ncrono baude rate factor 1 assı́ncrono baude rate factor 16 assı́ncrono baude rate factor 64 00 01 10 11 — — — — 5 6 7 8 por por por por bits bits bits bits caracter caracter caracter caracter 1 — habilita controlo de erro por paridade 0 — paridade ı́mpar; 1 — paridade par Modo sı́ncrono 00 — 2 caracteres SYNC, SYNDET output 01 — 2 caracteres SYNC, SYNDET input 10 — 1 caracter SYNC, SYNDET output 11 — 1 caracter SYNC, SYNDET input Modo assı́ncrono 00 — inválido 01 — 1 stop bit 10 — 1,5 stop bits 11 — 2 stop bits Figura 5.21 — Códigos de programação do Am8251 nos modos sı́ncrono ou assı́ncrono. A palavra de sincronismo (sync word), apesar de fazer parte dos códigos de controlo, na realidade não controla nada na USART. É a palavra a ser transmitida como palavra de sincronismo. A primeira palavra a ser enviada para a USART é a palavra de modo, se esta por sua vez especificar que o modo de operação desta é sı́ncrono, então a(s) próxima(s) palavra(s) será(ão) a(s) palavra(s) de sincronismo. As palavras de comando (command words) são usadas para inicializar certas funções, como por exemplo: • Reset de todas as flags de erro; • Iniciar a pesquisa de um SYNC. Estas palavras podem ser enviadas pelo micro-processador em qualquer instante. Durante o processo de inicialização a última palavra é de comando. Veja-se a figura 5.22. O registo de estado (status register) contém toda a informação acerca do estado actual do Am8251. É por consulta a este registo que o programa responsável pela transmissão sabe se existem ou não erros. Veja-se a figura 5.23. Exemplo 18 (Exemplo de programação do Am8251) Programar a USART para transmitir assincronamente um conjunto de 50 bytes, colocados nas posições de memória a partir do endereço INIT, com as seguintes caracterı́sticas: • factor de baud rate 16; 106 7 6 5 4 3 2 1 0 Txe — ‘1’ habilita a transmissão DTR — ‘1’ a linha de saı́da DTR é forçada a ‘0’ RxE — ‘1’ habilita a recepção SBRK — Send BreaK ER — reset das flags de erro RTS — ‘1’ a linha RTS é forçada a ‘0’ IR — entrada no modo “ocupado” EH — procura por um caracter de SYNC Figura 5.22 — Códigos de programação do Am8251: palavras de comando. 7 6 5 4 3 2 1 0 TxRDY RxRDY TxE Parity error Overrun error Framing error (modo assı́ncrono apenas) SYNDET DST Figura 5.23 — Códigos de programação do Am8251: consulta do registo de estado. 107 • 8 bits por caracter; • controlo de erros por paridade desabilitada; • 2 stop bits. O código assembly apresentado em seguida mostra-nos uma solução para este problema. L0 : LD A, 1 1 0 0 1 1 1 0B OUT (URTC) , A LD A, 3 OUT (URTC) , A LD HL , INIT LD B, 5 0 IN A , ( URTC) BIT 2, A JR Z , L0 LD A , ( HL) OUT (URTD) , A DJNZ L0 HALT 108 ; mode c o n t r o l word ; command word ; status register ; t r a n s m i t e um c a r a c t e r Capı́tulo 6 Interrupções 6.1 Introdução No capı́tulo anterior estudámos os protocolos de comunicação entre o sistema computacional e as interfaces. Se analisarmos os referidos protocolos verificamos que o processador gasta grande parte do seu tempo a ler o registo de estado das interfaces para conhecer o seu estado e poder aceder-lhes no momento certo. Daqui resulta uma grande perda de versatilidade, pois enquanto o CPU está a fazer uma coisa não pode fazer outra! Seria muito mais interessante que quando as interfaces estivessem prontas para receber ou enviar informação (de ou para o sistema computacional) o sinalizassem, ou seja, o interrompessem. Neste capı́tulo vamos estudar os problemas levantados por este tipo de abordagem, isto é, pelas interrupções. 6.2 Considerações gerais Num sistema de entrada/saı́da simples, a única maneira que o processador tem de verificar o estado de determinada interface é a de amostrar continuamente o seu registo de estado. Ora é de esperar que um sistema computacional mantenha em funcionamento um conjunto de periféricos, tais como: • Terminais; • Discos; • Impressoras; • Etc.. Para que estes funcionem com um elevado grau de eficiência o processador não pode perder o seu tempo a amostrar os registos de estado das diferentes interfaces. 109 Um sistema com interrupções resolve este problema ao permitir que o processador seja sinalizado sempre que determinada interface se encontre livre. O processador inicia em seguida um processo de entrada/saı́da com essa interface, ficando livre para outras tarefas imediatamente de seguida (enquanto o periférico troca a informação com a interface). Usando um sistema de interrupções: • Cada periférico envia um pedido de interrupção; • O processador aceita o pedido de interrupção, suspendendo momentaneamente a execução do programa em curso; • Executa a rotina de atendimento da interrupção, associada à interface em questão; • Depois de terminada a execução da rotina de atendimento da interrupção, o processador regressa à execução do programa que tinha interrompido. Do ponto de vista do programa interrompido, este facto é-lhe perfeitamente transparente, uma vez que a forma como é executado se mantém inalterada, havendo apenas um aumento de tempo. (Porquê?) Após a execução de uma instrução, o processador consulta sempre uma flag interna de modo a saber se foi ou não feito um pedido de interrupção. O CPU passa pelas seguintes fases no processamento de uma instrução: • Busca (fetch); • Execução; • Teste de interrupções. Note-se que a fase de teste de pedido de interrupção só acontece depois da instrução ter sido executada! Que é que isto quer dizer? Se, por exemplo, o CPU se encontrava a executar a instrução ADD 55 e houvesse um pedido de interrupção, este só seria visto, e possivelmente atendido, depois de toda a instrução ter sido executada, ou seja, depois de ter sido lido da memória o operando 55, ter sido adicionado ao conteúdo do acumulador e o resultado depositado no acumulador. Contudo, existem processadores que permitem ou aceitam interrupções durante a fase de execução de uma instrução. Como iremos ver, esta possibilidade complica muito fortemente a arquitectura de um CPU. O processador tem meios de inibir completamente as interrupções actuando num bit interno de estado através de uma instrução especı́fica do seu conjunto de instruções. Estando as interrupções habilitadas e sendo detectada a ocorrência de uma, o processador toma, genericamente, as seguintes acções: 110 Inı́cio Salvaguarda do estado do CPU Tarefas de transferência de informação Restaura o estado do CPU Habilita as interrupções Fim Figura 6.1 — Diagrama de fluxo genérico de uma rotina de serviço à interrupção. 1. Inibe as interrupções. Permite ao CPU garantir que a próxima instrução a ser executada pertença à rotina de serviço à interrupção detectada, cabendo ao programador decidir se dentro desta rotina serão permitidas ou não mais interrupções. 2. Guarda total ou parcialmente o seu estado. O CPU deve guardar o conteúdo do PC para que lhe seja possı́vel regressar ao programa interrompido após ter servido a interrupção. Devem ser salvaguardados todos os registos utilizados dentro desta rotina, nomeadamente o registo de flags, de modo a que a ocorrência duma interrupção não altere a execução do programa interrompido. 3. Detecta a interface que pediu a interrupção. A identificação da interface que pediu a interrupção pode ser feita por vários processos, como iremos ver mais à frente. 4. Identifica a razão do pedido e atende-o. Traduz-se na utilização de instruções para permitir a transferência de informação entre o sistema computacional e a interface (instruções de entrada/saı́da de dados). 5. Restaura o seu estado e regressa ao programa interrompido. É aqui que se faz a restauração dos registos internos do CPU (estado do processador), a habilitação do uso de interrupções e o regresso ao programa interrompido. Na figura 6.1 pode ser visto o diagrama de fluxo genérico de uma rotina de serviço à interrupção. 6.3 Interrupções múltiplas e prioridades Quando existem vários periféricos no sistema, como é que se sabe qual o periférico que pretende interromper? E se ocorrerem dois pedidos de interrupção simultâneos, qual o 111 Z80 ADC 0801 74LS366 D1 Dispositivo 1 D0 IORD STATUS A7 A6 A5 A4 A3 A2 A1 A0 Do bus de endereços ADC 0801 Para o bus de dados INT Dispositivo 2 Figura 6.2 — Exemplo de atendimento de interrupções por polling (ver texto). primeiro a ser atendido? Para a resolução destes problemas existem essencialmente duas técnicas: método de polling; método de vector de interrupção. 6.3.1 Polling Neste método, o micro-processador interroga cada um dos dispositivos usando o conjunto de instruções (software), identificando o dispositivo que pretende ser atendido. Depois transfere a execução do programa para a rotina de serviço apropriada. O software determina a prioridade entre os dispositivos que pedem interrupção e serveos de acordo com as prioridades estabelecidas durante o desenvolvimento do programa. Exemplo 19 (Atendimento de interrupções por polling) A figura 6.2 mostra um exemplo onde dois conversores A/D estão ligados por uma interface ao Z80, utilizando o modo 1 de interrupção. (Vamos estudar este modo em pormenor mais à frente.) A seguinte rotina de serviço à interrupção estabelece o dispositivo 1 como o que possui mais alta prioridade, isto é, prioridade superior ao dispositivo 2. 112 MDO1: PUSH AF ; g u a r d a o c o n t e ú d o d o s r e g i s t o s IN A , ( STATUS) ; l ê a p o r t a i n v e r s o r a t r i −s t a t e AND 00000011B ; i g n o r a o e s t a d o d a s l i n h a s D7 ; a D2 ; c o l o c a D0 na f l a g de c a r r y RRA CALL C , DVC1 ; s e D0=1 v a i p a r a o DVC1 ; para l e r os dados ; c o l o c a D1 na f l a g de c a r r y RRA CALL C , DVC2 ; s e D1=1 v a i p a r a o DVC2 ; para l e r os dados POP AF ; r e p õ e o c o n t e ú d o d o s r e g i s t o s ; h a b i l i t a i n t e r r u p ç õ e s EI RETI DVC1: PUSH AF ; g u a r d a o c o n t e ú d o d o s r e g i s t o s IN A , ( ADC1) ; l ê d a d o s do d i s p o s i t i v o 1 LD (HL ) , A ; g u a r d a o s d a d o s na memória OUT (ADC1) , A ; i n i c i a a próxima c o n v e r s ã o POP AF ; r e p õ e o c o n t e ú d o d o s r e g i s t o s PUSH AF ; g u a r d a o c o n t e ú d o d o s r e g i s t o s IN A , ( ADC2) ; l ê d a d o s do d i s p o s i t i v o 2 LD (HL ) , A ; g u a r d a o s d a d o s na memória OUT (ADC2) , A ; i n i c i a a próxima c o n v e r s ã o POP AF ; r e p õ e o c o n t e ú d o d o s r e g i s t o s RET DVC2: RET Como facilmente se pode concluir por este exemplo, bastaria trocar a ordem do teste à flag de carry (e consequente chamada à sub-rotina de atendimento) que alterarı́amos a ordem de prioridade de atendimento dos dispositivos. 6.3.2 Vector de interrupção Neste método, o dispositivo que interrompe indentifica-se automaticamente, fornecendo para o efeito uma instrução ou um endereço (tudo feito por hardware). Quando surgem dois dispositivos a pedir interrupção ao mesmo tempo, a prioridade de atendimento é determinada também pelo hardware, utilizando, por exemplo, o método de Daisy Chain. Exemplo 20 (Vector de interrupção) A figura 6.3 mostra um esquema para a implementação de interrupções com vários dispositivos usando o codificador 8-para-3 74LS148. Este circuito: 113 Codificador de prioridades 8 para 3 buffer tri−state +5V +5V 16 I7 I6 I5 I4 I3 I2 I1 I0 4 3 2 1 10 11 12 13 Vcc GND 8 74LS366 6 A2 2 3 4 A1 4 5 2 A0 6 7 10 9 0 GS 74LS148 E1 10K +5V +5V 1 15 INT INTA (para o Z80) (vindo do Z80) +5V 10K bus de dados D7 D6 D5 D4 D3 D2 D1 D0 Figura 6.3 — Exemplo de atendimento de interrupções por vector de interrupção (ver texto). 114 • Tem 8 linhas de entrada e 3 de saı́da; • A saı́da compreende os valores de 000 a 111; • As saı́das estão invertidas. Se, por exemplo, o dispositivo ligado à entrada 7 estiver activo, a saı́da terá o valor 000; • Determina automaticamente a mais alta prioridade. Se, por exemplo, as entradas 6 e 4 estiverem activas ao mesmo tempo, ignora a entrada 4 e coloca na saı́da o código correspondente à entrada 6; • Fornece as combinações apropriadas nas suas linhas de saı́da A0, A1 e A2. Estas devem estar ligadas às linhas de dados D1, D2 e D3 do CPU. A linha D0 deve ser forçada a ‘0’ por causa dos requisitos do modo 2 de interrupção do Z80. Com base no esquema da figura 6.3, se o dispositivo colocado na entrada I0 pedisse interrupção, a saı́da do codificador seria 111. Esta saı́da seria invertida pelo buffer tri-state 74LS366 e colocada no bus de dados. O valor lido pelo Z80, supondo que este estava a funcionar no modo 2 de interrupções, seria F0H. A figura 6.4 mostra uma possı́vel implementação do controlador utilizando um esquema Daisy Chain. 6.4 Interrupções no Z80 O Z80 tem duas linhas de pedido de interrupção, que correspondem a outros tantos tipos de interrupção. São elas: • Linha INT (Interrupt); • Linha NMI (Non Maskable Interrupt). O Z80 possui ainda dois flip-flops internos para inibição/activação de interrupções (IFF1 - flag de permissão de interrupção e IFF2 - flag auxiliar para salvaguarda de IFF1), um registo de dois bits que guarda o modo de interrupção actual (Interrupt Mode — IM) e o registo I (Interrupt vector), vector de interrupção. Na figura 6.5 podemos ver o sistema de interrupção do Z80. 6.4.1 Interrupções mascaráveis Quando a linha de entrada INT “vem a baixo”, posta neste estado por qualquer interface, esta sinaliza o Z80 de um pedido de interrupção. Esta linha pode ser mascarada 115 5 3.ST. EN Prioridade decrescente Registo de estado Periférico 3 6 1 1 0 3.ST. EN Registo de estado Periférico 2 7 1 1 1 3.ST. EN Bus de dados (código do periférico) 1 0 1 Registo de estado Periférico 1 INT INTA Controlo por daisy chain Figura 6.4 — Estabelecimento de prioridades no atendimento de interrupções com recurso ao esquema Daisy Chain. Z80 INT Flags NMI IFF1 IFF2 Registos IM I M1 IORQ INTA Figura 6.5 — Esquema resumido do sistema de interrupções do Z80. 116 activando um flip-flop interno ao CPU através de uma instrução apropriada de inibição de interrupções — DI (Disable Interrupts). Uma vez permitidas as interrupções, com a utilização da instrução EI (Enable Interrupts), o Z80 tem três modos de funcionamento possı́veis, sendo estes modos seleccionáveis pelo programador através das instruções: • IM 0 — Interrupt Mode 0; • IM 1 — Interrupt Mode 1; • IM 2 — Interrupt Mode 2. Em qualquer um destes modos o retorno ao programa interrompido é feito pela instrução RETI (RETurn from Interrupt) que restaura o valor do PC a partir da stack. 6.4.1.1 Modo 0 (interrupção vectorizada) O CPU, após verificar e aceitar o pedido de interrupção da interface, salvaguarda o conteúdo do PC na stack e gera um ciclo de interrupt acknowledge, activando os sinais IORQ e M1, através do qual a interface que faz o pedido coloca no bus de dados o código da instrução, normalmente de reinicio (RST — Restart) que o CPU deve executar. O código da instrução é então colocado no bus de dados (pode ser RST 0, RST 8, RST 10H, ..., RST 38H) e esta instrução direcciona o controlo do sistema para a rotina de serviço à interrupção da interface, figura 6.6. 6.4.1.2 Modo 1 (interrupção por pesquisa) Este modo é idêntico em tudo ao modo 0, sendo o endereço de restart, sempre, 38H (RST 38H). Veja-se a figura 6.7. 6.4.1.3 Modo 2 (interrupção vectorizada) Este modo é o mais poderoso. O endereço de inı́cio da rotina de atendimento de interrupção é calculado da seguinte forma: 1. Em primeiro lugar é calculado o endereço de entrada numa tabela de interrupções guardada na memória: • A parte mais significativa deste endereço (8 bits) é fornecida pelo registo I; • A parte menos significativa (designada por vector de interrupção) fornecida, pelo periférico que interrompe, na fase de interrupt acknowledge, figura 6.8 5.3.1. - 3; 117 Inı́cio do ciclo de interrupção IFF1 e INT activadas? Não Continuação do programa Sim Automaticamente: Desactivação de IFF1 e IFF2 Salto para a rotina de serviço do periférico (normalmente RST n para salvaguardar PC na stack) Por software: Salvaguarda do estado do CPU Serviço do periférico Restauro do estado do CPU Reactivação de IFF1 e IFF2 Regresso ao programa interrompido Figura 6.6 — Diagrama de fluxo de atendimento das interrupções para o modo 0 do Z80. Inı́cio do ciclo de interrupção IFF1 e INT activadas? Não Continuação do programa Sim Automaticamente: Desactivação de IFF1 e IFF2 Salto para o endereço 0038H com salvaguarda do PC na stack Por software: Salvaguarda do estado do CPU Pesquisa do periférico Serviço do periférico Restauro do estado do CPU Reactivação de IFF1 e IFF2 Regresso ao programa interrompido Figura 6.7 — Diagrama de fluxo de atendimento das interrupções para o modo 1 do Z80. 118 Registo I ENDL Vector ENDH .. . Endereços crescentes Memória .. . Endereço da tabela Figura 6.8 — Exemplo de tabela de interrupções para o modo 2 do Z80. 2. Os dois bytes lidos da posição de memória com endereço calculado no ponto anterior, constituem o endereço efectivo da rotina de atendimento da interrupção figura 6.9 5.3.1- 4. Este endereço deve ser sempre um número par. Como vimos anteriormente, a memória encontra-se organizada em bytes, sendo necessários dois bytes para formar um endereço. Estes bytes devem estar agrupados dois a dois na memória por forma a facilitar a sua manipulação. Assim, convencionou-se que o byte menos significativo deste endereço estaria guardado num endereço par. Daı́ resulta que o bit menos significativo do vector de interrupção seja sempre ‘0’. 6.4.2 Interrupções não mascaráveis Existe uma linha de entrada no Z80, a linha NMI, que permite interromper o seu funcionamento independentemente do estado do flip-flop de interrupt disable. Diz-se então que este tipo de interrupção é não mascarável. Estas interrupções estão previstas, devendo ser utilizadas em situações extremas, como quando se detecta a falta de energia. Existem sistemas que ao detectar esta situação, interrompem o seu funcionamento normal, guardando numa memória de suporte não volátil todo o estado da máquina por forma a poder recuperar a situação existente mal a energia regresse. O Z80 ao detectar uma interrupção deste tipo salta para a posição de memória 66H (após ter guardado na stack o conteúdo do PC) onde inicia a execução da rotina de serviço à interrupção. O retorno ao programa é feito pela instrução RETN (RETurn from Non-maskable interrupt) que restaura o valor de IFF1 a partir de IFF2 e o valor do PC a partir da stack, figura 6.10. 119 Inı́cio do ciclo de interrupção IFF1 e INT activadas? Não Continuação do programa Sim Automaticamente: Desactivação de IFF1 e IFF2 Salvaguarda do PC na stack Salto para a rotina de serviço do periférico cujo endereço se encontra na posição de memória dada pelo registo de interrupção I e pelo vector de interrupção colocado no bus de dados pelo periférico que interrompe Por software: Salvaguarda do estado do CPU Serviço do periférico Restauro do estado do CPU Reactivação de IFF1 e IFF2 Regresso ao programa interrompido Figura 6.9 — Diagrama de fluxo de atendimento das interrupções para o modo 2 do Z80. 120 Inı́cio do ciclo de interrupção Não NMI activada? Continuação do programa Sim Automaticamente: Salvaguarda IFF1 em IFF2 Desactivação de IFF1 Salto para endereço 0066H com salvaguarda do PC na stack Por software: Salvaguarda do estado do CPU Pesquisa do periférico (se necessário) Serviço do periférico Restauro do estado do CPU Regresso ao programa interrompido Figura 6.10 — Diagrama de fluxo de atendimento das interrupções não mascaráveis do Z80. 6.4.3 Programa de entrada/saı́da usando interrupções Nesta ponto vamos abordar um programa para entrada/saı́da de dados, mas que recorre à utilização de interrupções para efectuar as transacções. Deve-se comparar este programa com os apresentados na secção 5.3. Exemplo 21 (Entrada/saı́da com recurso a interrupções) Suponhamos que pretendı́amos implementar o algoritmo proposto no exemplo apresentado no ponto 5.3.1. Suponhamos ainda que o registo de estado da interface tem um bit onde o CPU pode escrever e cujo estado determina a possibilidade da interface pedir uma interrupção ao CPU. Assim, se o bit 7 do registo de estado estiver a: • ‘0’ — interface com interrupção inibida; • ‘1’ — interface com interrupção permitida. A interface gera um pedido de interrupção ao CPU quando o seu bit 0 indicar estado livre e o bit 7 se encontra a ‘1’. As posições de memória P e P+1 contêm o ponteiro para o bloco de memória cujo conteúdo se pretende enviar para o periférico. Na figura 6.11 pode ver-se o diagrama de fluxo correspondente. Na listagem seguinte apresenta-se uma possı́vel implementação em assembly do Z80. 121 P EQU 0F8FDH INIB EQU 80H DATA EQU 80H STATUS EQU 81H NUM EQU 0FFH SAIDA : PUSH AF ; salvaguarda PUSH HL ; registos PUSH BC ; LD HL , ( P) ; l ê p o n t e i r o LD A , (NUM) ; l ê n . de b y t e s LD B, A LD C , DATA ; t r a n s f e r e dado OUTI ; act . ponteiro , ; act . n . bytes ;a transferir LD (P ) , HL ; memoriza p o n t e i r o ; e contador LD A, B LD (NUM) , A JR NZ , NAO ; se t r a n s f e r i u ; t o d o s p á r a LD A , INIB ; i n i b e i n t e r r u p ç õ e s ; no p e r i f é r i c o NAO: OUT (STATUS ) , A POP BC POP HL POP AF ; restaura estado EI ; p e r m i t e i n t e r r u p ç õ e s RETI ; r e t o r n a da r o t i n a de ; s e r v i ç o à i n t e r r u p ç ã o END Na tabela 6.1 pode ver-se um sumário do processo de interrupções do micro-processador Z80. 122 Inı́cio atendimento de interrupção Salvaguarda registos internos Lê ponteiro para a próxima posição de memória a transferir Lê número de bytes Sim Tudo transferido? Inibe interrupções da interface Não Transfere byte Actualiza ponteiro Actualiza contador Guarda ponteiro Guarda contador Restaura registos Habilita interrupções Retorna ao programa interrompido Figura 6.11 — Diagrama de fluxo do exemplo 21. 123 Interrupção Condições para Instrução Hardware externo Posições de restart BUSRQ inactivo EI ou DI não pro- Não requerido 0066H NMI activa baixa duzem efeito Instrução RST Salto para um de aceitar o pedido de interrupção Nonmaskable interrupt (NMI) pino 17 Maskable interrupt BUSRQ inactivo Necessita estar pino 16 NMI inactiva habilitada com INT activa baixa EI, podendo ser desabilidata Modo 0 oito endereços (00, 08, · · ·, 38H) Modo 1 Modo 2 Utiliza o registo I Não requerido 0038H Vector (LSB) Endereço par (MSB) para cal- duma posição de cular o endereço memória Tabela 6.1 — Sumário do processo de interrupções do Z80. 124 Referências bibliográficas [1] Acer Incorporated. IOM-MPF-IP Experiment Manual (Software/Hardware), 1988. [2] Acer Incorporated. IOM-MPF-IP Operation Manual, 1988. [3] Acer Incorporated. IOM-MPF-IP User’s Manual, 1988. [4] Barry B. Brey. The Intel Microprocessors: 8086/8088, 80186/80188, 80286, 80386, 80486, Pentium, Pentium Pro Processor, Pentium II, Pentium III, Pentium 4 — Architecture, Programming and Interfacing. Prentice Hall, sexta edição, 2002. [5] Ramesh Gaonkar. The Z80 Microcomputer: Architecture, Interfacing, Programming and Design. Macmillan Publishing Company, segunda edição, 2000. [6] Patai Gergely. Complete Z80 instruction set, Agosto 2001. URL: http://www.ticalc.org/archives/files/fileinfo/195/19571.html. [7] Douglas V. Hall. Microprocessors and Digital Systems. McGraw-Hill International Editions, second edição, 1983. [8] Douglas V. Hall. Microprocessors and Interfacing — Programming and Hardware. McGraw-Hill International Editions, second edição, 1986. [9] Thomas L. Harman e David T. Hein. The Motorola MC68000 Microprocessor Family: Assembly Language, Interface Design and System Design. Pearson Education POD, segunda edição, 1995. [10] Lance A. Leventhal. Z80 Assembly Language Programming. Osborne/McGraw-Hill, 1979. [11] Craig Marven e Gillian Ewers. A simple approach to Digital Signal Processing. Texas Instruments, 1993. [12] David Patterson e John Hennessy. Computer architecture: a quantitative approach. Morgan Kaufmann Publishers, Inc., 1990. [13] John B. Peatman. Microcomputer-based Design. McGraw-Hill International Editions, 1981. 125 [14] Manuel C. Reis, António J. Gouveia, e Francisco S. Pereira. Introdução à Progrmação. Universidade de Trás-os-Montes e Alto Douro, ISBN 972-669-547-3, Julho 2003. [15] Jean-Paul Tremblay, John M. DeDourek, e Richard B. Bunt. Introduction to Computer Science: An Algorithmic Approach. McGraw-Hill International Editions, 1989. [16] John F. Wakerly. Microcomputer Architecture and Programming: The 68000 Family. John Wiley and Sons, Inc., 1989. 126