terça-feira, fevereiro 01, 2022

Tutorial Linguagem Assembly para MSX: Parte 1 - 2 Aritimética Z80

Registradores como variáveis, a memória e o Z80, adição ao modo Z80, subtração ao modo Z80, números negativos no Z80, bytes no Z80, são os tópicos abordados neste capítulo.

Sabendo algo sobre a aritmética hexa do Super-X e da aritmética binária do Z80, podemos começar a aprender como o Z80 realiza sua matemática.


Registradores como Variáveis

Artigo Original: Wilson Pilon

O Super-X, nosso guia e intérprete, sabe muito sobre o Z80 que está dentro do seu MSX. Iremos usá-lo para entrarmos no mundo interior deo Z80, e começaremos a pedir ao Super-X para apresentar o que puder sobre pequenas partes de memória chamadas registradores, em que podemos armazenar dados. Os registradores são como variáveis em BASIC, mas não são exatamente os mesmos. Diferente da linguagem BASIC, o microprocessador Z80 contém um número fixo de registradores, e eles não fazem parte da memória do seu MSX.

Pediremos ao Super-X para apresentar os registradores do Z80 com o comando RG.

(Você provavelmente verá números diferentes na segunda e quarta linhas de sua listagem, estes números refletem o estado atual do computador. Você continuará vendo tais diferenças, e depois aprenderemos mais sobre elas.)

Por enquanto, o Super-X certamente nos deu muita informação. Vamos nos concentrar nos primeiros quatro registradores, AF, BC, DE e HL, todos os quais o Super-X nos diz serem iguais a 0000, tanto aqui quanto em sua tela. Estes registradores são os registradores de uso geral. Os outros registradores, IX, IY, SP, PC, AF', BC', DE', HL', BP, são registradores de propósito especial que veremos em outros capítulos.

O número de quatro dígitos que vem após o nome do registrador está em notação hexa. No capítulo anterior, aprendemos que uma palavra é descrita exatamente em quatro dígitos hexa. Aqui, você pode ver que cada um dos registradores do Z80 possui uma palavra, ou 16 bits de tamanho.

Porém, os registros AF, BC, DE e HL podem ser manipulados em bytes, o que é mais padrão, formando os registradores A, B, C, D, E, H e L (o F é usado como flag, ou status e será visto mais adiante). O Z80, na verdade é um processador de oito bits, que pode eventualmente manipular 16 bits quando a combinação de registros acima é usada. Ele também trabalha com 16 bits para acessar endereços de memória. As combinações válidas de registro são AF (porém, o A normalmente é manipulado sozinho), BC, DE e HL, outras combinações envolvendo este conjunto de registro não é valida.

Já dissemos que os registradores são como variáveis BASIC. Isto deve significar que podemos alterá-las, e realmente podemos. O comando RG do Super-X faz mais do que apresentar registradores. Seguido pelo nome de um registro (ou par) e um valor, o comando diz ao Super-X que queremos alterar o registrador, pelo valor especificado. Por exemplo, podemos mudar o registro A assim:

Quando informamos RG BC,0102, o comando altera o registro B com a primeira metade do número e C com a segunda parte. Mas o que vem a ser a metade do número? Como o Z80 é um processador de 8 bits, que pode manipular em alguns casos 16, é usada uma forma um pouco quanto estranha para se armazenar valores de 16 bits (word). A word é dividida em dois bytes, sendo que o byte mais a esquerda recebe o nome de mais significativo (more significant byte, MSB), e o mais a direita menos significativo (less significant byte, LSB). Para o tratamento de registradores, podemos escrever os bytes diretamente como no exemplo, porém, mais para frente, veremos que para referenciar a memória, os bytes devem ser invertidos.

Daqui por diante, usaremos o comando RG sempre que precisarmos colocar números nos registradores do Z80.

Você deve se lembrar que no capítulo anterior usamos o comando CL para efetuar cálculos de soma hexaritimética. Lá o Super-X fez o trabalho para nós. Desta vez, usaremos o Super-X simplesmente como um interpretador para que possamos trabalhar diretamente com o Z80. Daremos ao Z80 as instruções para somar dois registradores: colocaremos um número no registrador B e depois instruiremos o Z80 para somar o número em B ao número em A e colocar a resposta de volta em A. Primeiro, precisamos de um número no registrador B. Desta vez, vamos somar 0A7H com 02AH. Use o comando RG para armazenar os valores.


(NT) Embora no tutorial de Super-X você tenha um detalhamento melhor, é importante saber que o comando RG pode ser usado como "RG *" o que simplesmente zera todos os registradores.

A Memória e o Z80

Os registradores A e B devem, respectivamente, conter 0A7H e 02AH, como podemos verificar com o comando RG.


Agora que temos nossos dois números nos registradores A e B, como podemos dizer ao Z80 para somar B a A? Colocamos alguns números na memória do computador.

Seu MSX provavelmente possui pelo menos 64Kb de memória - muito mais que o necessário aqui. Colocaremos dois bytes em código de máquina em algum lugar nesta imensidão de memória. Neste caso, o código de máquina serão dois números binários que dirão ao Z80 para somar o registrador B a A. Depois, para que possamos ver o que acontece, executaremos esta instrução com a ajuda do Super-X.

Agora, em que lugar da memória iremos colocar nossa instrução de dois bytes, e como dizer ao Z80 onde encontrá-la? Por acaso, o Z80 divide sua memória em partes de 64K chamadas de segmentos (estou falando aqui do MSX 1, usando memória expandida. A maior parte dos MSX tem, pelo menos, 64 Kb de RAM em 4 segmentos de 16 Kb e 32 Kb de ROM em 2 segmentos de 16 Kb, porém o Z80, só acessa 64 Kb por vez. Alguns MSX possuem mais memória deste tipo, via expansão ou cartuchos. Existe ainda dois outros tipos de memória, a mapeada, com segmentos de 16Kb, e a megaram, com segmentos de 8Kb, em um capítulo futuro vou explicar com um pouco mais de detalhes os tipos de memória, porém, até lá, consideremos apenas o metodo padrão de 64Kb do MSX 1). Na maior parte do tempo, veremos a memória dentro de um dos 4 segmentos de 16Kb ativos por vez. Podemos fazer isto pelo modo que o Z80 rotula a memória.

Todos os bytes na memória são rotulados com números começando de 0 e subindo. Mas você se lembra da limitação de 4 dígitos para os números hexa? Isto significa que o maior número que o Z80 pode usar é o equivalente a 65535, o que significa que o número máximo de rótulos que ele pode usar é 64 K. Mesmo assim, sabemos por experiência que o MSX pode fazer referência a mais de 64K de memória. Como pode ser isso? Com um pequeno truque: existe um sistema de 4 slots primários, cada um com capacidade de 16Kb, porém cada um destes slots pode ser expandido para 4 sub-slots, sendo assim, temos 16x16Kb de memória que podemos acessar. Obiviamente, o Z80 pode acessar apenas 64K por vez, então, o que fazemos é usar uma tabela especial na área de trabalho (além de um porto do Z80) para selecionar 4 slots ativos, dentre os 16 possíveis. Mudando a configuração, podemos ir chaveando os slots, e assim, acessar mais do que 64K de memória, porém, 64K por vez.

Inicialmente o Super-X escolhe os slots e segmentos para nós, a saber, dois segmentos do slot 0 (ROM) e dois do slot 1 (RAM), para que possamos trabalhar dentro de um segmento sem termos que prestar atenção na sua configuração. E, por enquanto, iremos nos referir aos endereços somente pelos 64K ativos na RAM. Cada um destes endereços refere-se a um byte, e os endereços são sequenciais, de forma que 101H é o byte seguinte a 100H na memória. Para indicarmos de qual segmento devemos usar a RAM, usa-se a sintaxe <endereço>#<slot primário>.<slot secundário>. Você deve ler a documentação do seu MSX para saber qual a configuração dos slots (por hora, pelo menos, mais adiante veremos como descobrir isso), e deve selecionar os mesmos com qualquer comando que faça referência a endereços de memória do Super-X.

Uma outra forma fácil de identificar, é observar a tela de status que aparece logo na entrada do Super-X, ela mostra a disposição da RAM e ROM, como mostra a figura abaixo:


Veja que ele mostra o SLOT P0=#0:P1=#3-2:P2=#3-2:P3=#3-2. P0, P1, P2 e P3 são os quatro segmentos de 16 Kb que estão ativos no momento, veja que P0 está no slot 0, e os outros no slot 3.2. Mais embaixo veja que ele aponta onde se encontra a RAM (RAM:P0=#3-2:P1=#3-2:P2=#3-2:P3=#3-2), e a ROM (MAIN-ROM(P0-1):#0), ou seja, slot 0 (ele mostra outras informações que não são relevantes neste momento em particular). Então, para trabalharmos, devemos usar os segmentos de RAM (3.2 no meu exemplo) visto que a ROM (segmento 0) não pode ser escrita. Durante o texto, estarei usando as minhas referências para RAM (3.2), porém você deve trocar para a configuração apontada para o seu MSX. Para informações mais detalhadas, procure ler o Aprofundando-se no MSX, principalmente o capítulo PPI - Interface de Periféricos.

Após a primeira seleção, geralmente o Super-X mantém a mesma para os próximos comandos, mas convém verificar a linha de endereçamento (veja o tutorial do Super-X para maiores detalhes).

Escritas por inteiro, nossas duas instruções para somar B a A se parece assim:

ADD A,B

Colocaremos estas instruções nas posições 100H e 101H da RAM (no meu caso no slot 3.2).

O comando do Super-X para examinar e alterar a memória é chamado D<endereço>-<slot primário>.<slot secundário>, de DUMP. Use este comando para entrar os doi bytes da instrução ADD, como se segue:

D 100#3-2



Você deve adaptar para sua configuração, por exemplo, usuários de Expert, devem usar D 100#2-0, e os de Hotbit, D 100#3-0. Perceba que ao selecionar o endereço, o mesmo se torna ativo, como demonstra a última linha, que mostra o comando corrente e o endereço ativo (DUMP:0100) e o slot selecionado (SLOT:3-2).

Com o cursor em 0100H, entre: 80.

O número 80 é o comando em linguagem de máquina para nossa instrução ADD na posição de memória 0100. Perceba que podem existir números previamente gravados nestas posições de memória (eu limpei antes), que são restos de outro programa ou dado que já se encontravam na RAM.

Adição ao Modo Z80

Para verificarmos se a instrução está corretamente inserida no endereço 100H, basta usarmos o comando

I 100

no prompt do Super-X e com o comando

RG

verificamos que os registradores A e B estão com os valores corretos.


Nossa instrução ADD está corretamente colocada na memória, justamente onde queremos que ela esteja. O primeiro número da listagem obtida pelo comando I, nos dão o endereço (100H) de nossa instrução ADD. Como entramos nossa instrução em linguagem de máquina - números que não têm significado para nós, mas que o Z80 interpretará como uma instrução de soma -, a mensagem ADD A,B confirma que entramos a instrução corretamente.

O Z80 encontra o endereço no registrador PC, que você pode ver na listagem acima, e podemos dizer a ele onde encontrar a próxima instrução a ser executada definindo um valor para PC, com o comando:

RG PC,100H

O endereço 100H foi escolhido por conta de uma particulariedade do MSX-DOS, explicaremos sobre isso mais adiante, mas por hora, devemos ter em mente que um programa assembly para ser executado no MSX-DOS, precisa começar em 100H, porém, o Super-X não consegue trabalhar de maneira adequada neste endereço, por necessitar da BIOS, então até efetuarmos nosso primeiro programa sem o uso do Super-X, utilizaremos o endereço 0C100H.

Então, basta repetir os passos anteriores, trocando o endereço 100H por C100H.

Quando iniciamos o Super-X, o PC está em 0000H (ou outro valor dependendo se algo já foi executado no MSX ou não).

Agora com nossa instrução no lugar e os registradores com os valores corretos, diremos ao Super-X para executar nossa única instrução. Usaremos o comando

TR C100

Se você usou a instrução RG para mudar o PC para 0C100, não precisa colocar o endereço na instrução TR.

O programa lista os registradores e para na primeira instrução a ser executada (ADD A,B) e aguarda um comando. O comando TR vem de TRACE, e serve para executar as instruções do programa uma a uma, mostrando os resultados nos registradores, neste ponto o programa aguarda para executar nossa instrução, basta dar um [RETURN] para que ele a execute. Veja que após o [RETURN], o programa passa para a próxima instrução (que pode ser qualquer coisa, já que só colocamos uma única instrução), e mostra como ficaram os registradores após a operação. Pressione [ESC] para sair do modo TRACE.


E é isso aí. O registrador A agora contém 0D1H, que é a soma de 0A7H e 02AH. E o registrador PC aponta para o endereço 0C101.

Já disse antes que o ponteiro da instrução, sempre aponta para a próxima instrução para o Z80. Se digitássemos TR novamente, executaríamos a instrução seguinte, mas não faça isso ainda - seu Z80 pode perder a cabeça.


Ao invés disso, que tal se executássemos a instrução de soma novamente, somando 2AH a 0D1H e armazenando a nova resposta em A? Para isso, precisamos dizer ao Z80 onde encontrar sua nova instrução, e queremos que ela seja nossa instrução ADD em 0C100H. Podemos simplesmente trocar o registrador PC para 0C100H? Vamos tentar, use o comando RG para colocar 0C100H em PC, e olhe como ficaram os registradores. Em seguida informe TR apenas e veja que a próxima instrução a ser executada é ADD A,B.

Conseguiu. Pressione [RETURN] para que o comando TR execute a instrução e veja se o registrador A contém 0FBH.

Perfeito! Como você pode ver, você sempre deve checar o registrador PC e a instrução na linha final da listagem do comando TR, antes de executar a mesma. Dessa forma, você estará seguro de que o Z80 irá executar a instrução que você quer.

Agora, coloque 0C100H no registrador PC, e assegure-se de que os registradores contêm A=FB, B=2A, e vamos tentar a subtração.

Subtração ao Modo Z80

Vamos escrever uma instrução para subtrair B de A de modo que, após a subtração, teremos 0A7 em A, o ponto em que começamos antes das duas somas.

Em 0C100H, coloque o valor 90H. A listagem com I ou com TR C100, deve agora mostrar a instrução

SUB A,B

que instrui o Z80 a subtrair o conteúdo de B do conteúdo de A, e deixar o resultado em A (Na verdade, o Super-X mostra a instrução SUB A,x por apenas SUB x, visto que todas elas tem o A como alvo das subtrações de 8 bits). A ordem de A e B pode parecer invertida, mas a instrução é como o comando em BASIC

A = A - B

exceto que no Z80, diferente do BASIC, sempre o resultado é colocado na primeira variável (registrador).


Lembre de colocar o registro PC em 0C100H novamente

RG PC,0C100H

O comando TR agora deve mostrar a instrução SUB B

Executando a instrução com [RETURN] teremos o resultado em A (0D1H)
Mude o PC novamente para 0C100H e execute novamente a instrução, agora teremos 0A7H em A.

Números Negativos no Z80

Anteriormente descobrimos que o Z80 usa o complemento de dois para representar números negativos, agora vamos trabalhar diretamente com a instrução SUB para calcular números negativos, vamos fazer um pequeno teste no Z80, para ver se conseguimos 0FFH para -1. Iremos subtrair 1 de 0, e se estivermos corretos, o resultado que será colocado no registrador A será FF. Coloque 0 no registro A, 1 no registro B, e coloque a função de subtração em 100H, execute e veja o resultado.

Aproveite que está com a mão na massa e execute esta instrução mais algumas vezes e veja diferentes representações para números negativos (lembre-se de ir mudando o PC).

Words no Z80

Toda a aritmética vista até o momento se baseou em bytes (8 bits), mas o Z80 sabe como fazer cálculos em words (16 bits)? Sim, ele consegue.

Sabemos que um word nada mais é do que dois bytes agrupados, e os registros gerais do Z80 (A, B, C, D, E, L e H) podem ser agrupados (AF, BC, DE, LH), num formato conhecido como byte alto e byte baixo, por exemplo, o registro agrupado DE tem o byte mais significativo em D e o menos significativo em E (se carregar 512D em DE, E recebe 1H e D recebe 2 (2*256+1=513).

Vamos agora realizar uma instrução em word. Colocaremos 09h em 0C100H (instrução ADD HL,BC), no registrador HL inserimos 0102H, e em BC 0101H e mudamos o PC para 0C100

e comandamos TR e [RETURN] para ver o resultado (0203H em HL).

Como vimos, o Super-X nos permite trabalhar tanto com os registros individuais quanto com os agrupados.


Multiplicação e Divisão no Z80

Artigo Original: Wilson Pilon

Fizemos a soma e a subtração, então é de se esperar a multiplicação e a divisão, correto? Errado, infelizmente o Z80 não contém instruções para estas operações (Quem usa o Turbo-R/R800 está em vantagem, pois este processador tem algumas funções nesta linha).

Como fazemos então? Existem várias formas para efetuar este processo, para decidir qual é a melhor, você deve conhecer bem os dados que o programa vai aceitar.

Um dos métodos e fazer o rotacionamento dos bits (será explicado com mais detalhes adiante), onde você desloca todos os bits para a esquerda ou para a direita, e com isso consegue multiplicações ou divisões para esquerda ou direita em múltiplos de 2.

Por exemplo, para multiplicarmos 3 por 4, usamos as seguintes instruções (não se preocupe em entender por hora)

ld b,3
sla b
sla b

Isto rotaciona os bits 2 vezes o que faz a multiplicação por 4 funcionar, mas e para outros valores que não são múltiplos de 2? Uma ideia é usar a instrução ADD e SUB algumas vezes (o que pode ser enfadonho em determinado ponto). Por exemplo para multiplicar 3 por 15:

ld a,3
ld b,a
add a,a
add a,a
add a,a
add a,a
sub b

Outra técnica usa tabelas de multiplicações, o que ocupa um bom espaço em memória para tarefas mais complexas.

Se quizer ter uma ideia, existe um bom artigo no MSX Assembly Pages (de onde eu tirei os exemplos).


Resumo

Está quase na hora de escrevermos um programa real, por exemplo um que imprima um caracter na tela.

Aprendemos sobre os registradores, fazendo um paralelo com o BASIC (embora com muito menos opções). Concentramos nos registros de propósito geral e demos uma olhada rápida no registrador PC. Após aprender como carregar os registros virmos como efetuar algumas operações básicas e aprendemos sobre uma pequena limitação nas operações de multiplicação e divisão. Aprendemos a trabalhar com os registradores separados (byte) ou agrupados (word) e usamos intensamente o Super-X.

Claro, assim que nossos programas começarem a crescer de tamanho, teremos que parar de usar o Super-X e migrar para um compilador mais apropriado, porém iremos usar muito o Super-X para testes e depuração.

Nenhum comentário:

Postar um comentário