Conhecendo o Processador Z80


  O principal componente de um computador é a CPU. No MSX, a CPU é o processador Z80-A, da Zilog, com um clock de 3,58 MHz. O Z80-A é um processador de 8 bits, ou seja, é capaz de processar 8 bits de cada vez. O endereçamento de memória, por sua vez, é feito com 16 bits, resultando em 65536 posições de memória diferentes[1].

  A imagem a seguir, apresenta a arquitetura do processador Z80 do MSX.


  Registradores

  Os registradores são as memórias internas do processador Z80, que são extremamente rápidas e armazenam pequena quantidade de informações. Elas são utilizadas pelo processador para auxiliar na execução de programas e cálculos. Eles podem ser de 8 ou 16 bits.
  A memória principal é uma memória localizada fora do processador, conectada através de um barramento de dados de 8 bits e outro de endereçamento de 16 bits. Ela possui no MSX o tamanho de 64 Kbytes (65536 bytes), variando de 0 a 65535 (0 a FFFF, em hexadecimal). O objetivo da memoria principal é armazenar os programas e os dados que serão processados pelo Z80. Por exemplo, no endereço de memória &H3CD2 do esquema abaixo, encontramos o valor &H2C.


  O registrador possui velocidade de escrita/leitura maior que a memória principal. Dessa forma, os registradores deverão ser os preferidos para a realização de cálculos, sempre que possível. Para uma troca de valores de registrador para outro registrador são necessários 4 ciclos de clock, enquanto que de um registrador para a memória são necessários de 7 a 13 ciclos. Quanto menos ciclos de clock necessitar uma instrução, mais rápida será sua execução.
  Repare no lado direito do esquema do processador acima, que há três setas grandes: a de dados (DATA BUS), de endereço (ADR BUS) e de controle (CON BUS). Elas representam as vias de comunicação entre o processador, memória principal e periféricos.

  Os registradores do Z80:

  A - O acumulador é o registrador responsável pelos resultados aritméricos, lógicos e comunicação para fora, através de INs e OUTs.
  B - Registrador comum. Utilizado também como contador de laços de repetição, como na instrução DJNZ.
  C - Registrador comum. Pode ser usado em par com o registrador B.
  D - Registrador comum.
  E - Registrador comum. Pode ser utilizado em par com o registrador D.
  HL - Registrador de 16 bits.
  IX - Registrador de índice. Permite a utilização de deslocamentos fixos para uma variável, como o valor "1" na expressão "A + 1". Ex: IX+1.
  IY - Registrador de índice. Permite a utilização de deslocamentos fixos para uma variável, como o valor "1" na expressão "A + 1". Ex: IY+2.
  F - Flag. São bits que indicam certas propriedades resultantes, após uma operação aritmética.
  PC - Esse registrador é um ponteiro (memória principal) para a próxima instrução a ser executada.
  SP - Esse registrador é um ponteiro (memória principal) para a o topo de uma pilha.
  Registradores alternativos: A', B', C', D', E', F', H' e L'. São acionados através das instruções EX e EXX.


  Flags

  Os flags indicam algumas propriedades resultantes de operações aritiméticas. Esses resultados são representados pelos 8 bits do registrador F.
  A configuração de bits desse registrador, bem como a descrição de cada proriedade é mostrada a seguir.
 Bit   |  7   6   5   4   3   2   1   0
 ------+---------------------------------   ← Registrador F
 Flag  |  S   Z   -   H   -   P   N   C
  S - Sign - O resultado da operação é negativo (1) ou positivo (0).
  Z - Zero - O resultado da operação é zero (1) ou não (0).
  H - Half-Carry - Se 1, houve estouro no 4o. bit.
  P - Parity/Overflow - Indica se número de bits 1 é par ou ímpar.
  N - Subtraction - A última operação foi subtração (1) ou adição (0).
  C - Carry - Se 1, houve estouro no 4o. bit.

  Exemplos de operações que afetam o flag:

  Primeiro caso:
  00000000		Teste em Assembly:
- 00000001		
----------		10 ORG &HC000
  11111111		20 LD A,0
			30 LD B,1
 7 6 5 4 3 2 1 0	40 SUB B
 S Z - H - P N C	50 RET
 1 0 1 1 1 0 1 1 	
  Segundo caso:
  00001111		Teste em Assembly:
+ 00000001		
----------		10 ORG &HC000
  00010000		20 LD A,&HF
			30 LD B,1
 7 6 5 4 3 2 1 0	40 ADD A,B
 S Z - H - P N C	50 RET
 0 0 0 1 0 0 0 0 	
  Terceiro caso:
  11111111		Teste em Assembly:
+ 00000001		
----------		10 ORG &HC000
  00000000		20 LD A,&HFF
			30 LD B,1
 7 6 5 4 3 2 1 0	40 ADD A,B
 S Z - H - P N C	50 RET
 0 1 0 1 0 0 0 1 	

  Program Counter - PC e Stack Pointer - SP

  Para a execução de uma instrução, o processador precisa saber em que lugar da memória principal está localizada a próxima instrução. O registrador PC contém esse endereço.
  Cada instrução do Z80 possui um comprimento em bytes que varia de 1 a 4. A instrução de tamanho "n" executada consome todos bytes pertencentes à ela. Dessa forma, toda vez que uma instrução de comprimento de n bytes é executada, o registrador PC avança n bytes, em busca da próxima instrução.
  Por exemplo, se uma dada instrução do Z80 possuir 2 bytes e o registrador PC apontar para o endereço de memória &HC000, após a execução desta instrução, o registrador PC irá apontar para a posição de memória &HC002.
Instrução: LD A,4 
Código de máquina equivalente: &H3E &H04

 End	Código de máquina   
 C000	3E
 C001	04
 C002	XX  ← Código da próxima instrução

  Uma pilha de dados na memória principal é semelhante a uma pilha de livros sobre uma mesa. No caso dessa, um primeiro livro é colocado diretamente sobre a mesa, sendo esse o topo da pilha. O segundo é posto acima do primeiro livro e esse passa a ser o topo da pilha. O terceiro é posto sobre o segundo e esse passa a ser o topo da pilha. Quando retiramos um livro da pilha, retiramos o livro do topo, que no caso é o terceiro livro. Assim, o segundo livro passa a ser o topo da pilha. Por aí vai, retirarmos o último livro da pilha, que no caso é o primeiro que foi colocado na pilha, tornando-a uma pilha vazia.
  A seqüência de eventos de empilhamento e desempilhamento, narrada acima, é ilustrada a seguir.
                                L3
                        L2      L2      L2
                L1      L1      L1      L1      L1
 Mesa  ----    ----    ----    ----    ----    ----    ----
 Legenda:
 L1: primeiro livro colocado
 L2: segundo livro colocado
 L3: terceiro livro colocado
   : Topo da pilha
  A convenção de listagem de memória em papel ou em tela é feita de forma crescente, de cima para baixo. No caso do Z80, que é capaz de endereçar 65536 posições, tem-se:
 Endereço
 --------
 0000
 0001
 0002
 0003
 ...
 FFFE
 FFFF
  O empilhamento é feito no sentido decrescente da memória. A partir da convenção apresentada, temos que o empilhamento na memória é feito no sentido de baixo para cima. Entretanto, quando o valor for "estourado", isto é, uma posição de memória menor que 0000, retorna-se ao valor &HFFFF.

  De forma a auxiliar o programador a armazenar dados na memória, o registrador SP contém um ponteiro para a posição de memória que indica o topo de uma pilha.
  A instrução PUSH reg, empilha dados contidos no registrador reg na próxima posição de memória acima do topo. Já a instrução POP reg, recupera o dado contido no topo da pilha para o registrador reg.
  Obs: reg é necessariamente um registrador de 16 bits (AF, BC, DE, HL).

  Exemplo prático: suponha que temos a seqüência de bytes 01 02 03 04 05 06. Se o SP estiver apontando para a posição &H1000, ao empilharmos byte a byte, os dados ficariam assim:
 End.	Dado
 -----------
 0FFA	06
 0FFB	05
 0FFC	04
 0FFD	03
 0FFE	02
 0FFF	01
 1000	xx
  Obs: xx significa "lixo" da memória.
  Ao final da operação, o registrador SP irá apontar para a posição de memória &HFFA, que é o valor do topo da pilha. Assim, quando houver desempilhamento, o valor 06 é retornado.


  Código em linguagem de máquina e Mnemônicos

  As instruções passadas ao processador são códigos numéricos, no qual é extremamente difiícil para um programador entendê-los ou memorizá-los. Por exemplo, o seguinte trecho é um código em linguagem de máquina (valores em hexadecimal):

  3E FF 06 01 80 C9

  De modo a facilitar a vida do programador de Assembly, foram criadas certas palavras que representassem os códigos em linguagem de máquina, tornando-se mais compreensíveis para o Homem. A essas palavras que representam os códigos de máquina, chamamos de Mnemônicos.
  O programa anterior, ao ser reescrito utilizando os Mnemônicos, torna-se agora legível para nós:
 LD A,&HFF
 LD B,1
 ADD A,B
 RET
  Observe que fica muito mais fácil de entender o que o programa faz. Ele carrega (LD = load) o valor &HFF no registrador A, depois carrega o valor 1 no registrador B e soma (ADD) o conteúdo do registrador B com o registrador A. Por fim, devolve o controle à rotina que chamou o nosso programa (RET = return).
  É necessário converter o código em Mnemônicos para o Assembly, pois o processador só entende aqueles códigos numéricos. O utilitário que realiza essa operação é chamado de Assembler.


  Assembler / Disassembler

  Assembler (ou Assemblador) é um programa que converte os mnemônicos para código de máquina. Por exemplo, o programa fonte (Mnemônicos) a seguir será convertido para linguagem de máquina no Macro-Assemblador RSCII:
 10 ORG &HC000
 20 LD A,B
  Obs: o comando ORG mais o endereço indica onde o local de memória onde o programa será colocado.
  No RSCII, digitar EN e enter, para assemblar:
 EN
 Número de Opción: 3
 Modo (H/D): H
  Resultado:
 End    Cód.Maq.	Linha	Mnemônico
 -------------------------------------------
 C000			10	ORG &HC000
 C000	78		20	LD A,B
  Assim, o código de máquina para a instrução LD A,B é &H78.

  O sentido inverso do assembler, ou seja, converter código de máquina para mnemônicos, é chamado de disassembler.
  No RSCII, o comando DE seguido do endereço inicial (ou endereço inicial mais endereço final) desassembla o código.
 DE &HC000
 Modo (H/D): H
 DEFB:

 C000  78       LD A,B
  Ao adicionar a opção "G" no final do comando DE, o código mnemônico é salvo em disco. Para isso, o programa pedirá um nome.
 DE &HC000,G
 Modo (H/D): H
 Nombre: "prog1.asm"
  O arquivo será salvo no formato de listagem do RSCII e poderá ser carregado através do comando LT.
 10 ORG &HC000
 20 LD A,B
  O disassembler para uma faixa de endereços é feito da seguinte maneira: suponha que nosso programa em Assembly esteja locado da posição de memória &HC000 até &HC00A. O comando DE para salvar nosso código será:
  DE &HC000-&HC00A
  ou
  DE &HC000-&HC00A,G
  Obs: a listagem numérica de um programa com mnemônicos, similar ao Basic, é convenção do Macro-Assemblador RSCII.


  Fluxo de execução dos programas

  Conforme dito anteriormente, o registrador PC contém o endereço da próxima instrução a ser executada. Uma instrução é executada por vez, onde cada instrução pode ter de um a quatro bytes.
  O fluxo de execução é sempre progressivo na memória, executando instruções em seqüência, salvo quando encontrados desvios.
  Quando uma instrução é executada, o registrador PC avança N bytes, de acordo com o tamanho de cada instrução.
  A seguir, será apresentado um programa onde o fluxo de leitura de instruções é ilustrado.
 End    Cód.Maq.	Linha	Mnemônico
 -------------------------------------------
 C000			10	ORG &HC000
 C000	78		20	LD A,B
 C001	3E04		30	LD A,4
 C003	DD340A		40	INC (IX+10)
 C006	DDCB0A1E	50	RR (IX+10)
  O fluxo de execução, instrução a instrução, é o seguinte:
 Ordem	Antes		Instrução Executada	Depois
 1	PC=C000		LD A,B			PC=C001
 2	PC=C001		LD A,4			PC=C003
 3	PC=C003		INC (IX+10)		PC=C006
 4	PC=C006		RR (IX+10)		PC=C00A
  O fluxo segue sempre adiante, salvo quando encontrar desvios. Os desvios serão abordados no próximo capítulo.

  Para simular esse programa no RSCII, use o comando SI (simular).
 SI &HC000
 Modo (H/D): H

 Teclar E para executar cada instrução.
 Teclar M, caso deseje alterar um dos registradores:
 A, B, C, D, E, F, H, L, IX ou IY.
 Tecle "enter" para validar as modificações nos registradores, em cada linha (a primeira linha são os registradores normais, a segunda os alternativos).



  A linguagem Assembly é bem mais complexa do que as linguagens de alto nível. Portanto, para começar a enterdê-la bem, é necessário:
  1. Se ambientar bem com os simuladores (Z80-Dt ou RSCII).
  2. Começar pelos programas de exemplo fornecidos nesse curso.
  3. Executar o passo a passo e observar o comportamento dos registradores, flags e memória.
  4. Testar pequenas mudanças, seja elas pela alteração do valor dos registradores ou memória, bem como pela alteração do código fonte.
  5. Procurar por entender as instruções do Z80, através de pequenos programas de teste.
  No capítulo seguinte, serão apresentadas as principais instruções do Z80, divididas por tipo de operação.
  Conforme dito anteriormente, pequenos programas podem ser feitos para testar e comparar os efeitos das instruções do Z80.



  Referências:
  [1] - Aprofundando-se no MSX; Piazzi, Maldonado, Oliveira; Ed. Aleph, 1987.


CURSOS/ASSEMBLY/AULA5