Curso Fudeba de ASM |
Bem, chegou a hora do desapego. Esta aula é apenas uma parte do que originalmente havia sido previsto para a Aula 4, pois achei que estava muito pesada. Nesta aula vamos começar a tomar algum contato com uma forma um pouco mais "rudimentar" de raciocínio: vamos brincar de pensar igual ao computador. Pode parecer bobeira, mas tudo que parece muito difícil de programar pensando como "gente" fica mais simples quando pensado como se fossemos o computador. Nesta aula é que os programadores de BASIC começaram a sentir alguma diferença mais fundamental com o que estão habituados, mas hey, continua não sendo um bicho nem de duas cabeças.
Primeiramente veremos como trabalhar com repetição de instruções e nas próximas aulas usaremos isso para imprimir varias mensagens diferentes usando apenas um loop. Nesta aula vamos aprender também a usar o FuDebug para ajudar a resolver os paus de nossos programas.
Você deve ter reparado que nos programas anteriores escrevemos uma seqüência de frases, não é? Bem, mas e se ao invés de imprimirmos varias frases, quiséssemos repetir varias vezes, digamos, 5 vezes, a mesma frase? Bem, nosso programa ficaria mais ou menos assim...
--- Cortar Aqui --- BDOS EQU 5 STROUT EQU 9 START: ; Mostra informações do programa e faz pergunta LD DE,NOMEDOPRG ; Indica texto do nome do programa CALL MOSTRATXT ; Mostra texto LD DE,AUTOR ; Indica texto do nome do autor CALL MOSTRATXT ; Mostra texto LD DE,FUDEBA ; Indica texto fudeba CALL MOSTRATXT ; Mostra texto LD DE,FUDEBA ; Indica texto fudeba CALL MOSTRATXT ; Mostra texto LD DE,FUDEBA ; Indica texto fudeba CALL MOSTRATXT ; Mostra texto LD DE,FUDEBA ; Indica texto fudeba CALL MOSTRATXT ; Mostra texto LD DE,FUDEBA ; Indica texto fudeba CALL MOSTRATXT ; Mostra texto JP 0 ; Volta ao MSX-DOS ;------ ; MOSTRATXT - Função que mostra um texto cuja seqüência é terminada por '$'. ; Entrada: DE - Aponta para seqüência a ser mostrada, terminada por '$' ;------ MOSTRATXT: LD C,STROUT ; Indica função de mostrar texto do BDOS CALL BDOS ; Manda o BDOS executar. RET ; Retorna NOMEDOPRG: DB 'Programa 4 - Brincando com loops',13,10,'$' AUTOR: DB ' Por Daniel Caetano',13,10,10,'$' FUDEBA: DB 'O DalPoz é um fudeba!',13,10,'$' END --- Cortar Aqui ---
Simples, melzinho na chupeta agora que estamos todos craques em ASM. Mas vocês concordam que isso ficou com uma aparência meio "burra"? Estamos repetindo 5 vezes a mesma coisa! Uma alternativa seria fazer o seguinte:
--- Cortar Aqui --- BDOS EQU 5 STROUT EQU 9 START: ; Mostra informações do programa e faz pergunta LD DE,NOMEDOPRG ; Indica texto do nome do programa CALL MOSTRATXT ; Mostra texto LD DE,AUTOR ; Indica texto do nome do autor CALL MOSTRATXT ; Mostra texto CALL TXTFUDEBA ; Imprime texto fudeba CALL TXTFUDEBA ; Imprime texto fudeba CALL TXTFUDEBA ; Imprime texto fudeba CALL TXTFUDEBA ; Imprime texto fudeba CALL TXTFUDEBA ; Imprime texto fudeba JP 0 ; Volta ao MSX-DOS ;------ ; TXTFUDEBA - Imprime texto fudeba na tela ;------ MOSTRATXT: LD DE,FUDEBA ; Indica texto fudeba CALL MOSTRATXT ; Mostra texto RET ;------ ; MOSTRATXT - Função que mostra um texto cuja seqüência é terminada por '$'. ; Entrada: DE - Aponta para seqüência a ser mostrada, terminada por '$' ;------ MOSTRATXT: LD C,STROUT ; Indica função de mostrar texto do BDOS CALL BDOS ; Manda o BDOS executar. RET ; Retorna NOMEDOPRG: DB 'Programa 4 - Brincando com loops',13,10,'$' AUTOR: DB ' Por Daniel Caetano',13,10,10,'$' FUDEBA: DB 'O DalPoz é um fudeba!',13,10,'$' END --- Cortar Aqui ---
Já ficou melhor, não? De fato, essa segunda solução é melhor em termos de tamanho, mas na verdade, se você pensar com carinho, verá que ela ficou mais lenta, porque agora o processador tem que pular de um lado para outro muito mais vezes do que antes. Assim, aqui aparece um conceito importante: se você precisa de velocidade em um determinado trecho do programa, só o subdivida em funções se:
Fora essas situações, evite simplesmente sair dividindo tudo. E o caso de nosso programa é um desses em que ficar enchendo de função não tá melhorando muito. Tá diminuindo o tamanho de uma quantia ínfima e tá deixando ele mais lento, alem de deixando ele mais difícil de ler, pois ele tá cada vez mais espalhado, com funções que fazem tarefas cada vez menores e mais especializadas.
Alem disso, *existe* um jeito diferente de fazer isso. Um jeito que nos dá mais maleabilidade como veremos posteriormente, alem de trazer um ganho de desempenho e economizar ainda MAIS espaço. COMO?!? É simples! Dizendo pro Z80 repetir os comandos! Ele sabe fazer isso muito bem!
O comando que faz isso é o DJNZ, um tipo especial de Jump. O que ele significa eu entro em mais detalhes depois. Mas a idéia é mais ou menos essa:
LOOP: ASM1 ASM2 ASM3 ... DJNZ LOOP
Ou seja, o DJNZ vai fazer ele repetir tudo que está entre o DJNZ e a definição LOOP. Epa, mas então o LOOP é uma função também? Afinal, eu defini ele com os 2 pontinhos na frente! Bem, digamos que não. Eu menti pra vocês. Colocar esses dois pontinhos na frente não significa que é uma função, mas sim significa que isso é uma ETIQUETA (Label, em inglês). Ora, se você pensar que uma etiqueta, na vida real, serve para a gente identificar as coisas, pro assemblador é a mesma coisa. Ou seja, essa "etiqueta" indica pro assemblador onde estão as coisas. Assim, quando você usa:
CALL FUNCAO
Ele vai procurar pela etiqueta
FUNCAO:
E vai executar a partir de então. E é por isso, então, que toda a função começa com uma etiqueta (pra que seja possível você mandar o Z80 pular pra lá!). No entanto, você pode colocar etiquetas onde quiser, mesmo no meio de funções. Isso é útil em diversos casos, como neste do DJNZ. O DJNZ diz mais ou menos assim pro processador: "Repete tudo que estiver entre esta etiqueta e eu". E o Z80, como é muito obediente, vai lá e faz, sem questionar.
Assim, como ficaria nosso programa? Bem, o que queremos repetir é o seguinte:
LD DE,FUDEBA ; Indica texto fudeba CALL MOSTRATXT ; Mostra texto
Que havíamos até separado em uma função (mas vamos voltar atras). Então, se queremos repetir isso, devemos usar um trecho mais ou menos assim:
LOOP: LD DE,FUDEBA ; Indica texto fudeba CALL MOSTRATXT ; Mostra texto DJNZ LOOP ; Repete os comandos LD DE,FUDEBA e CALL MOSTRATXT
Não é? Sim! É! Vamos inserir isso no programa, que fica agora assim:
--- Cortar Aqui --- BDOS EQU 5 STROUT EQU 9 START: ; Mostra informações do programa e faz pergunta LD DE,NOMEDOPRG ; Indica texto do nome do programa CALL MOSTRATXT ; Mostra texto LD DE,AUTOR ; Indica texto do nome do autor CALL MOSTRATXT ; Mostra texto LOOP: LD DE,FUDEBA ; Indica texto fudeba CALL MOSTRATXT ; Mostra texto DJNZ LOOP ; Repete os comandos LD DE,FUDEBA e CALL MOSTRATXT JP 0 ; Volta ao MSX-DOS ;------ ; MOSTRATXT - Função que mostra um texto cuja seqüência é terminada por '$'. ; Entrada: DE - Aponta para seqüência a ser mostrada, terminada por '$' ;------ MOSTRATXT: LD C,STROUT ; Indica função de mostrar texto do BDOS CALL BDOS ; Manda o BDOS executar. RET ; Retorna NOMEDOPRG: DB 'Programa 4 - Brincando com loops',13,10,'$' AUTOR: DB ' Por Daniel Caetano',13,10,10,'$' FUDEBA: DB 'O DalPoz é um fudeba!',13,10,'$' END --- Cortar Aqui ---
E vejam como ficou mais limpo! Notem que continua existindo um pulo, como no caso da função. No entanto, quando transformamos em função tivemos que gastar espaço com 5 chamadas à função (agora esse espaço foi economizado) e, ainda no caso da função, havia um pulo adicional: se fomos, tivemos que voltar (com o RET). Ou seja, para cada chamada à função tínhamos DOIS pulos, que agora virou apenas 1 (ele pula da posição do DJNZ para a posição da etiqueta LOOP).
Assemblem isso com o M80/L80, usando o CL80 e rodem no BrMSX. E o que vocês viram? Creio que funcionou, em parte. (^= Vocês devem ter notado que, ao invés de repetir 5 vezes a frase, esta ficou se repetindo milhares de vezes... infinitas, na verdade. Bem, o que está faltando? Dissemos ao Z80 para REPETIR um trecho de código, mas não dissemos para ele QUANTAS vezes repetir.
O comando DJNZ significa "Decrement and Jump if Not Zero" ou, em português, subtraia 1 e pule pra etiqueta indicada se o resultado não for zero. Até ai', tudo bem.... Mas subtrai 1 de ONDE? Eu fui maldoso e não contei pra vocês que essa informação precisa ser colocada no registrador B. Quando chegar no DJNZ, ele vai fazer o seguinte:
B = B - 1
(subtraiu 1 de B)
B = 0?
Se NÃO, ele pula para a etiqueta (Decrement and Jump if Not Zero). Se B era igual a zero, no entanto, a instrução é simplesmente ignorada e o programa segue adiante. Assim, devemos colocar no registrador B o numero de vezes que queremos que a coisa se repita. Vamos, então, mudar o programa, acrescentando um
LD B,5
Pra indicar isso. O programa fica então assim:
--- Cortar Aqui --- BDOS EQU 5 STROUT EQU 9 START: ; Mostra informações do programa e faz pergunta LD DE,NOMEDOPRG ; Indica texto do nome do programa CALL MOSTRATXT ; Mostra texto LD DE,AUTOR ; Indica texto do nome do autor CALL MOSTRATXT ; Mostra texto LOOP: LD B,5 ; Indica repetição de 5 vezes. LD DE,FUDEBA ; Indica texto fudeba CALL MOSTRATXT ; Mostra texto DJNZ LOOP ; Repete os comandos LD DE,FUDEBA e CALL MOSTRATXT JP 0 ; Volta ao MSX-DOS ;------ ; MOSTRATXT - Função que mostra um texto cuja seqüência é terminada por '$'. ; Entrada: DE - Aponta para seqüência a ser mostrada, terminada por '$' ;------ MOSTRATXT: LD C,STROUT ; Indica função de mostrar texto do BDOS CALL BDOS ; Manda o BDOS executar. RET ; Retorna NOMEDOPRG: DB 'Programa 4 - Brincando com loops',13,10,'$' AUTOR: DB ' Por Daniel Caetano',13,10,10,'$' FUDEBA: DB 'O DalPoz é um fudeba!',13,10,'$' END --- Cortar Aqui ---
Assemble novamente e rode.
O QUE?!? Continuou dando problema? Bem, vamos passo a passo ver o que está acontecendo. Assim que você executa o programa ele vai executar:
LD DE,NOMEDOPRG ; Indica texto do nome do programa CALL MOSTRATXT ; Mostra texto LD DE,AUTOR ; Indica texto do nome do autor CALL MOSTRATXT ; Mostra texto
Até aqui nada de anormal. Em seguida, ele executa:
LOOP: LD B,5 ; Indica repetição de 5 vezes.
Na linha acima indica numero de vezes igual a 5. Portanto, B indica 5 neste momento.
LD DE,FUDEBA ; Indica texto fudeba CALL MOSTRATXT ; Mostra texto
Aqui ele aponta o texto e mostra o texto. B ainda indica 5.
DJNZ LOOP ; Repete os comandos LD DE,FUDEBA e CALL MOSTRATXT
No DJNZ, o valor de B é decrementado (subtraído de uma unidade). Ou seja, com a execução deste comando, B passa a conter o valor 4. Ainda no DJNZ é verificado se 4 = 0? Como não, ele pula para o label LOOP, e temos em seguida:
LOOP: LD B,5 ; Indica repetição de 5 vezes.
Que faz B = 5 de novo! Epa, isso está errado! Assim o B nunca vai chegar a zero! E porque isso tá ocorrendo? Porque colocamos o LD B,5 dentro do loop, e isso está, logicamente, errado! A solução é mudar o loop para:
LD B,5 ; Indica repetição de 5 vezes. LOOP: LD DE,FUDEBA ; Indica texto fudeba CALL MOSTRATXT ; Mostra texto DJNZ LOOP ; Repete os comandos LD DE,FUDEBA e CALL MOSTRATXT
Ou seja, com o B FORA do loop. Se você fizer a "emulação de cabeça" que eu fiz há pouco verá que agora sim o valor de B vai decrescendo... O programa deve estar assim agora:
--- Cortar Aqui --- BDOS EQU 5 STROUT EQU 9 START: ; Mostra informações do programa e faz pergunta LD DE,NOMEDOPRG ; Indica texto do nome do programa CALL MOSTRATXT ; Mostra texto LD DE,AUTOR ; Indica texto do nome do autor CALL MOSTRATXT ; Mostra texto LD B,5 ; Indica repetição de 5 vezes. LOOP: LD DE,FUDEBA ; Indica texto fudeba CALL MOSTRATXT ; Mostra texto DJNZ LOOP ; Repete os comandos LD DE,FUDEBA e CALL MOSTRATXT JP 0 ; Volta ao MSX-DOS ;------ ; MOSTRATXT - Função que mostra um texto cuja seqüência é terminada por '$'. ; Entrada: DE - Aponta para seqüência a ser mostrada, terminada por '$' ;------ MOSTRATXT: LD C,STROUT ; Indica função de mostrar texto do BDOS CALL BDOS ; Manda o BDOS executar. RET ; Retorna NOMEDOPRG: DB 'Programa 4 - Brincando com loops',13,10,'$' AUTOR: DB ' Por Daniel Caetano',13,10,10,'$' FUDEBA: DB 'O DalPoz é um fudeba!',13,10,'$' END --- Cortar Aqui ---
Assemble seu programinha e rode no BrMSX e veja que agora o programa roda perfeitamente!
O QUE!?! Não rodou? Poxa, mas não tem nenhum erro de lógica! hummm... Como fazemos para diagnosticar esse problema? De repente até fazendo a "emulação de cabeça" a gente ache o problema, mas... poxa, isso dá trabalho pra caramba! Não tem uma ferramenta que faca isso pra gente? UAI, e não tem? Claro que tem! E vocês já a estão usando. É o BrMSX. Pra que usar um "emulador de cabeça" se temos um eletrônico, prontinho?
Vamos usar o BrMSX então pra descobrir porque nosso programa não está funcionando, mesmo que aparentemente deveria estar.
Entre no BrMSX normalmente até ele entrar no MSX-DOS, mas ainda NÃO rode o programa. Antes precisamos tomar algumas precauções e conhecer alguns detalhes.
Antes de mais nada pressione F10. Isso vai entrar na tela 1 do FuDebug. Tem um monte de numero aí, não? Tem, pois é. Esta tela é mais ou menos assim:
BrMSX debugger Z80 VDP PSG AF=0042 AF'=01A8 Reg0 00 55 10DC> 28 FB JR Z,10D9 BC=00FF BC'=0019 Reg1 F0 00 10DE CD 27 0A CALL 0A27 DE=007C DE'=0000 Reg2 00 00 10E1 21 9B FC LD HL,FC9B HL=0304 HL'=E0AB Reg3 00 00 10E4 7E LD A,(HL) IX=009F Reg4 01 00 10E5 FE 04 CP 04 IY=BB80 SZ5H3VNC Reg5 00 00 10E7 20 02 JR NZ,10EB PC=10DC 01000010 Reg6 00 00 10E9 36 00 LD (HL),00 SP=EAEF Reg7 F1 B8 10EB 2A FA F3 LD HL,(F3FA) I=00 EI IM1 R=63 Reg8 00 00 10EE 4E LD C,(HL) PPI MegaROM Reg9 00 00 10EF CD C2 10 CALL 10C2 0000 00 RegA 00 00 10F2 22 FA F3 LD (F3FA),HL A=A8 2000 00 RegB 00 0B C=5A 4000 00 RegC 00 00 Memory 6000 01 RegD 00 00 0000 F3 C3 D7 02 BF 1B 98 98 ........ Page0=0 8000 02 RegE 00 00 0008 C3 83 26 00 C3 B6 01 00 ..&..... Page1=2 A000 03 RegF 00 CF 0010 C3 86 26 00 C3 D1 01 00 ..&..... Page2=2 C000 00 0018 C3 45 1B 00 C3 17 02 00 .E...... Page3=2 E000 00 PSG addr 0E BrMSX settings Resolution: 320x200 Frame skipping: 0001 Bar Graph: OFF Emulation: NORMAL Joy: - Sound: ON VSync: OFF Session: SINGLE COM: 1 Video Cache: ON Command: SCC: OFF |
E pode ser dividida basicamente em três partes:
É nesse lugar que a maioria da atividade de debug acontece. Acostume-se com a cara dele. Aperte S para voltar à emulação. Rode o seu programa. Ele vai começar a mostrar infinitamente a frase "DalPoz é fudeba", como fazia antes. Agora, pressione F10.
Você deve aparecer na tela do FuDebug... mas... e aí? Onde tá o meu programa no meio desse lixo todo? Hummm... Você pode tentar procurar usando as teclas pra cima e para baixo. Você vai ver que a seção "BrMSX debugger" mexe. Nesta seção, todos os valores estão em hexadecimal. Você encontra, da esquerda para a direita, o endereço da memória, os bytes que compõem a instrução, e mais `a direita da seção o MNEMONICO ASSEMBLY da instrução. Mas isso é demorado. Pra pular para uma posição de memória especifica você pode usar o comando U (Unassemble) do FuDebug. Digite:
U 0100
E veja! Ele pulou direto para a posição 0100 da memória. Melhorou bastante, né? Mas onde está o meu programa? Difícil dizer. Na verdade sabemos que ele está em um lugar especifico (já conto pra vocês), mas ele pode não estar visível no momento (como em geral não vai estar). Em 99,99% dos casos o MSX estará processando algumas informações que nada tem a ver com o seu programa. Essa informações estão, em geral, na BIOS do seu MSX. A BIOS contem programas, como o seu, dentro, mas ela fica escondida em um lugar. Se você observar na Janela PPI, notará que existem as seguintes indicações:
Page 0 Page 1 Page 2 Page 3
Certo? Mas o que significa isso? Bem, vamos com calma. Lembra-se que na primeira aula eu disse que a folha de papel do MSX tinha 65536 lugares para escrever letrinhas? Então, eu menti de novo. Na verdade, não é uma folha, mas sim duas, frente e verso. Ou seja, temos 4 paginas, cada uma podendo ter até 16384 letrinhas. Uau! Mas pra que isso serve? Pra muita coisa como vamos ver já. Antes gostaria de fazer um parêntese: apesar de PAGINA ser a nomenclatura padronizada para isso no MSX1, devido a mudanças na estruturação do MSX2 a palavra PAGINA foi usada para OUTRA coisa. Vamos chamar essas 4 "coisas" de FRAMES (molduras) como ficou padronizado no MSX2 e superiores pelo MSX2 Technical Handbook. Assim, o MSX tem 4 frames, cada um podendo ter até 16386 bytes, ou seja, cada frame tem 16384 posições de memória, ou 16Kb. Alem disso, você deve ter notado que os MSX em geral possuem vários conectores de cartuchos (chamados SLOTS). Apesar de você só ver 2, na verdade existem 4 (dois deles costumam ser usados internamente, e nos já veremos com o que). Bem, pense agora que você pode ter expansões de memória conectados nesses slots, sejam elas RAM (mais memória pra você brincar) ou ROM (funções pre-prontas, como as do MSX-DOS, que ficam no chamado BDOS, ou as da própria BIOS). Agora, eu te digo que seu MSX *realmente* tem um monte de expansões ligadas. Em geral os slots que aparecem pra fora são o slot 1 e o 2. Mas que fim levaram os slots 0 e 3? Bem, é aí que entra a manha: o slot 0 tem ligado nele de fabrica a BIOS e o BASIC, enquanto o slot 3 costuma vir ligado com a RAM. Note que, tirando a necessidade da BIOS estar no slot 0, essa configuração não é totalmente obrigatório (embora seja seguida pela maioria das maquinas). E... tanto essa configuração não é 100% padrão que existem computadores que vêem com a BIOS no slot 0, RAM no Slot 2 e os slots 1 e 3 aparecem para fora no micro. E o Expert, emulado pelo BrMSX, é um deles.
Bem, em algum lugar a gente precisa dizer pro MSX em que slot está a RAM/ROM que queremos usar... E é pra isso que temos a memória do MSX dividida em 4 FRAMES de 16Kb: para que cada um deles possa conter dados de diferentes slots, sem que o Z80 precise se preocupar com "onde estou lendo esse dado?" Essa configuração é feita pela PPI, um processador externo ao Z80, que é quem faz essa gambiarra pra podermos ter essa facilidade de ter cada frame da memória do Z80 mostrando dado de um slot diferente.
E o que isso tudo tem a ver com o FuDebug? Bem, se você olhar, na seção PPI, os valores que estão em frente à
Page 0 Page 1 Page 2 Page 3
Se você estiver executando o programa, verá que em 99,99% dos casos a configuração esta:
Page 0=0 Page 1=2 Page 2=2 Page 3=2
(se não estiver, aperte S pra voltar pra emulação e depois aperte F10 de novo pra voltar ao FuDebug. Você tinha sido um felizardo dos 0,01%!)
Essa é a configuração do momento, para cada Frame (ou Page, com o emulador chama) qual é slot que está sendo usado. Assim, de 0 a 3FFFh (0 a 16383) temos informações do slot 0 (BIOS), e nos outros 3 frames, de 4000h a FFFFh (16384 a 65535) temos informações do Slot 2 (no caso do Expert e BrMSX, a RAM).
Assim, se o seu programa estivesse em qualquer área da RAM entre 16384 e 65535 você o encontraria simplesmente procurando com as setar ou usando U XXXX no FuDebug.
No entanto, existe uma peculiaridade no MSX-DOS: ele sempre carrega um programa a partir do endereço 0100h da RAM. Ora, mas se de 0000h a 3FFFh temos a BIOS, como vamos ver o nosso programa?
Bem, existe um jeito para fazer isso. O BrMSX tem um comando B (de Break) em que você escolhe uma posição de memória na qual o BrMSX automaticamente abrirá o FuDebug quando ela for executada.
Vamos fazer exatamente isso agora: Aperte S para voltar ao programa. Ele vai continuar imprimindo "DalPoz é fudeba" na tela. Pressione CTRL+C para parar com isso. Agora, ANTES de executar novamente seu programa, aperte F10 para entrar no FuDebug. No FuDebug, comande:
B 0100
O emulador automaticamente voltará para o MSX-DOS. Esse comando que você passou fará com que o FuDebug entre em ação exatamente quando a posição de memória 0100 (o seu programa!) for executada. ( Caso você se pergunte o que aconteceria se alguma coisa na ROM fosse executada no endereço 0100h, leia até o fim da aula, onde eu falo um pouco mais sobre isso. )
Manda o MSX-DOS executar o seu programa. Instantaneamente o FuDebug vai te mostrar algo mais ou menos assim:
BrMSX debugger Z80 VDP PSG AF=0044 AF'=0082 Reg0 00 55 0100> 00 NOP BC=00FF BC'=3CF6 Reg1 F0 00 0101 00 NOP DE=DDFF DE'=3CF6 Reg2 00 00 0102 00 NOP HL=0000 HL'=3CFB Reg3 00 00 0103 11 22 01 LD DE,0122 IX=3EFD Reg4 01 00 0106 CD 1C 01 CALL 011C IY=3FDB SZ5H3VNC Reg5 00 00 0109 11 45 01 LD DE,0145 PC=0100 01000100 Reg6 00 00 010C CD 1C 01 CALL 011C SP=D6FE Reg7 F1 B8 010F 06 05 LD B,05 I=00 EI IM1 R=5A Reg8 00 00 0111 11 5D 01 LD DE,015D PPI MegaROM Reg9 00 00 0114 CD 1C 01 CALL 011C 0000 00 RegA 00 00 0117 10 F8 DJNZ 0111 A=AA 2000 00 RegB 00 0B C=5A 4000 00 RegC 00 00 Memory 6000 01 RegD 00 00 0000 C3 03 DE 00 00 C3 06 D7 ........ Page0=2 8000 02 RegE 00 00 0008 00 00 00 00 C3 E8 F1 00 ........ Page1=2 A000 03 RegF 00 CF 0010 00 00 00 00 C3 EB F1 00 ........ Page2=2 C000 00 0018 00 00 00 00 C3 EE F1 00 ........ Page3=2 E000 00 PSG addr 0E BrMSX settings Resolution: 320x200 Frame skipping: 0001 Bar Graph: OFF Emulation: NORMAL Joy: - Sound: ON VSync: OFF Session: SINGLE COM: 1 Video Cache: ON Command: SCC: OFF |
Como você pode notar, todos os frames estão no Slot2 agora (RAM). E o ponteiro do BrMSX ( o ">" do lado direito do endereço na seção BrMSX debugger) indica a instrução que será executada em seqüência. E ele está exatamente sobre a posição 0100h, que é o inicio do seu programa! Vamos acompanhar... Existem 3 NOPs aí no começo que são acrescentados pelo assemblador, vamos ver futuramente para que eles servem. A seguir, vem
LD DE,0122h
E você vai pensar... "Poxa isso não tem nada a ver com o
LD DE,NOMEDOPRG ; Indica texto do nome do programa
Que tinha logo no inicio do meu programa!". Será que não mesmo? O que o FuDebug está mostrando é que estamos "apontando" DE para a posição de memória 0122h. Vamos ver o que tem lá? Digite
D 0122
No fudebug. Este comando vai fazer um "Dump" da memória a partir de 0120h, na seção "Memory" do fudebug. Se você fizer isso, vai ver a seguinte tela:
BrMSX debugger Z80 VDP PSG AF=0044 AF'=0082 Reg0 00 55 0100> 00 NOP BC=00FF BC'=3CF6 Reg1 F0 00 0101 00 NOP DE=DDFF DE'=3CF6 Reg2 00 00 0102 00 NOP HL=0000 HL'=3CFB Reg3 00 00 0103 11 22 01 LD DE,0122 IX=3EFD Reg4 01 00 0106 CD 1C 01 CALL 011C IY=3FDB SZ5H3VNC Reg5 00 00 0109 11 45 01 LD DE,0145 PC=0100 01000100 Reg6 00 00 010C CD 1C 01 CALL 011C SP=D6FE Reg7 F1 B8 010F 06 05 LD B,05 I=00 EI IM1 R=5A Reg8 00 00 0111 11 5D 01 LD DE,015D PPI MegaROM Reg9 00 00 0114 CD 1C 01 CALL 011C 0000 00 RegA 00 00 0117 10 F8 DJNZ 0111 A=AA 2000 00 RegB 00 0B C=5A 4000 00 RegC 00 00 Memory 6000 01 RegD 00 00 0122 50 72 6F 67 72 61 6D 61 Programa Page0=2 8000 02 RegE 00 00 012A 20 34 20 2D 20 42 72 69 4 - Bri Page1=2 A000 03 RegF 00 CF 0132 6E 63 61 6E 64 6F 20 63 ncando c Page2=2 C000 00 013A 6F 6D 20 6C 6F 6F 70 73 om loops Page3=2 E000 00 PSG addr 0E BrMSX settings Resolution: 320x200 Frame skipping: 0001 Bar Graph: OFF Emulation: NORMAL Joy: - Sound: ON VSync: OFF Session: SINGLE COM: 1 Video Cache: ON Command: SCC: OFF |
Olhe lá, na posição 0122h... "Programa 4 - Brincando com loops" ou seja, LD DE,0122h é a codificação exata que o assemblador gerou do seu LD DE,NOMEDOPROG. Uma das principais vantagens do assemblador e de se usar labels (etiquetas) em Assembly é que você não precisa se preocupar mais com "em que endereço isso vai ficar?", porque o assemblador cuida de tudo pra você. Mas você vai precisar ficar esperto ao usar o FuDebug, pois eles vão aparecer com os nomes já trocados para números durante a execução de seu programa. Seguindo essa idéia, o CALL 011Ch deve ser o mesmo que o "CALL MOSTRATXT" de nosso programa. E de fato é! Se você usar:
U 011C
(Unassemble 011C) vai notar que o seguinte vai aparecer na tela:
BrMSX debugger Z80 VDP PSG AF=0044 AF'=0082 Reg0 00 55 011C 0E 09 LD C,09 BC=00FF BC'=3CF6 Reg1 F0 00 011E CD 05 00 CALL 0005 DE=DDFF DE'=3CF6 Reg2 00 00 0121 C9 RET HL=0000 HL'=3CFB Reg3 00 00 0122 50 LD D,B IX=3EFD Reg4 01 00 0123 72 LD (HL),D IY=3FDB SZ5H3VNC Reg5 00 00 0124 6F LD L,A PC=0100 01000100 Reg6 00 00 0125 67 LD H,A SP=D6FE Reg7 F1 B8 0126 72 LD (HL),D I=00 EI IM1 R=5A Reg8 00 00 0127 61 LD H,C PPI MegaROM Reg9 00 00 0128 6D LD L,L 0000 00 RegA 00 00 0129 61 LD H,C A=AA 2000 00 RegB 00 0B C=5A 4000 00 RegC 00 00 Memory 6000 01 RegD 00 00 0122 50 72 6F 67 72 61 6D 61 Programa Page0=2 8000 02 RegE 00 00 012A 20 34 20 2D 20 42 72 69 4 - Bri Page1=2 A000 03 RegF 00 CF 0132 6E 63 61 6E 64 6F 20 63 ncando c Page2=2 C000 00 013A 6F 6D 20 6C 6F 6F 70 73 om loops Page3=2 E000 00 PSG addr 0E BrMSX settings Resolution: 320x200 Frame skipping: 0001 Bar Graph: OFF Emulation: NORMAL Joy: - Sound: ON VSync: OFF Session: SINGLE COM: 1 Video Cache: ON Command: SCC: OFF |
Ora, LD C,09 (STROUT), CALL 0005 (BDOS) e RET é exatamente sua função MOSTRATXT! Então é isso mesmo! Ih, mas e agora pra voltar onde estávamos? Em que posição estava mesmo o marcador do BrMSX?
Bem, o marcador do BrMSX apenas emula o marcador do próprio Z80. E claro, o Z80 real tem um marcador, afinal, isso é útil pra ele também. Esse marcador é um registrador especial, e chama-se PC (de Program Counter, ou contador de programa) e você pode olhar o valor atual dele na seção Z80, onde você vai encontrar:
PC=0100
"Ah, é! Era a posição 0100h!"
Bem, então digite U 0100h pra voltar pra lá... E pumba!
BrMSX debugger Z80 VDP PSG AF=0044 AF'=0082 Reg0 00 55 0100> 00 NOP BC=00FF BC'=3CF6 Reg1 F0 00 0101 00 NOP DE=DDFF DE'=3CF6 Reg2 00 00 0102 00 NOP HL=0000 HL'=3CFB Reg3 00 00 0103 11 22 01 LD DE,0122 IX=3EFD Reg4 01 00 0106 CD 1C 01 CALL 011C IY=3FDB SZ5H3VNC Reg5 00 00 0109 11 45 01 LD DE,0145 PC=0100 01000100 Reg6 00 00 010C CD 1C 01 CALL 011C SP=D6FE Reg7 F1 B8 010F 06 05 LD B,05 I=00 EI IM1 R=5A Reg8 00 00 0111 11 5D 01 LD DE,015D PPI MegaROM Reg9 00 00 0114 CD 1C 01 CALL 011C 0000 00 RegA 00 00 0117 10 F8 DJNZ 0111 A=AA 2000 00 RegB 00 0B C=5A 4000 00 RegC 00 00 Memory 6000 01 RegD 00 00 0122 50 72 6F 67 72 61 6D 61 Programa Page0=2 8000 02 RegE 00 00 012A 20 34 20 2D 20 42 72 69 4 - Bri Page1=2 A000 03 RegF 00 CF 0132 6E 63 61 6E 64 6F 20 63 ncando c Page2=2 C000 00 013A 6F 6D 20 6C 6F 6F 70 73 om loops Page3=2 E000 00 PSG addr 0E BrMSX settings Resolution: 320x200 Frame skipping: 0001 Bar Graph: OFF Emulation: NORMAL Joy: - Sound: ON VSync: OFF Session: SINGLE COM: 1 Video Cache: ON Command: SCC: OFF |
Voltamos. Tá, tudo bem, mas em que isso ajuda a gente? Simples. Experimente pressionar a tecla F8. Ah! O ponteiro andou pra próxima posição de memória! Sim, mas não só isso. Ele de fato executou a operação (no caso, NOP). Apertando F8 você pode ir passando comando por comando (Step Over). Para acompanhar o que está acontecendo na tela, pressione a tecla 0 que ele vai te mostrar a tela (sem descongelar a emulação). No momento em que a tela de emulação congelada estiver sendo mostrada, qualquer tecla faz voltar ao FuDebug. Assim, pressione 0 para ver que a tela do MSX-DOS continua lá, sem nenhuma alteração. Pressione alguma outra tecla pra voltar ao FuDebug. Pressione F8 até o marcador ficar em frente `a posição de memória 0103h. Nesta posição tem o comando LD DE,0122h. O ponteiro nessa linha indica que ela é a próxima a ser executada. Observe o valor que DE contem atualmente, na seção Z80. No meu caso é DDFFh, mas poderia ser qualquer valor. Pressione F8, executando a instrução LD DE,0122h (e agora o marcador vai apontar para a posição de memória 0106h). Agora você pode olhar que o valor do registrador DE foi mudado para... Exatamente 0122h! Uau! Pressione o F8 novamente para executar o CALL 011Ch. Isso deve ter colocado a mensagem que começa na posição 0122h da RAM na tela... Mas como vemos isso sem descongelar a emulação? Simples... pressione 0. Isso vai te mostrar a imagem com a emulação ainda congelada. Pressione qualquer tecla para voltar. É sempre a mesma coisa. (^= Agora pressione F8 algumas vezes, até que o ponteiro de execução ficar em frente à posição de memória 010Fh. A segunda mensagem já deve estar na tela (você pode conferir com a tecla 0). Da próxima vez que você apertar F8, o emulador vai executar a o comando:
LD B,005h
Que é justamente onde você inicializava o numero de vezes que queria a mensagem. Opa! Tá chegando o lugar onde deve estar o pepino! Pressione F8. B foi carregado com o valor 5. Até aqui tudo dentro do esperado. Pressione F8 novamente. DE deve ter sido carregado com 015Dh, que é o endereço da mensagem fudeba. Pressione F8 novamente. Verifique que a mensagem apareceu na tela, pois o CALL foi executado (use 0 para verificar a tela). Mas ... ei, se você notar por algum motivo estranho B tá valendo 00h! Onde foi parar o meu 5? Bem, agora então não vamos mais usar o F8, e sim o F7. A diferença do F8 para o F7 é que o F8 não entra dentro das funções (por exemplo, usando o F8 você não viu o que acontecia dentro do CALL. Usando o F7 vai poder ver). Assim, aperte F7 e você vai ver que a execução do DJNZ vai fazer tudo aquilo que já tinha dito: subtrair 1 do B (que de 00 passa a FFh, pois em Assembly os números "giram" assim mesmo) e pulou de volta para 0111h! Pressione F7 mais uma vez, e novamente DE será carregado com o valor 015Dh, o endereço da mensagem fudeba. Quando você apertar F7 novamente, vai ver que a execução pula agora para o endereço 011C, ou seja, com o F7 você entrou dentro da função. E o que você acha no endereço 011C? A sua função MOSTRATXT! Que é:
BrMSX debugger Z80 VDP PSG AF=0044 AF'=0082 Reg0 00 55 011C> 0E 09 LD C,09 BC=FF02 BC'=3CF6 Reg1 F0 00 011E CD 05 00 CALL 0005 DE=015D DE'=3CF6 Reg2 00 00 0121 C9 RET HL=0000 HL'=3CFB Reg3 00 00 0122 50 LD D,B IX=3EFD Reg4 01 00 0123 72 LD (HL),D IY=3FDB SZ5H3VNC Reg5 00 00 0124 6F LD L,A PC=011C 01000100 Reg6 00 00 0125 67 LD H,A SP=D6FC Reg7 F1 B8 0126 72 LD (HL),D I=00 EI IM1 R=71 Reg8 00 00 0127 61 LD H,C PPI MegaROM Reg9 00 00 0128 6D LD L,L 0000 00 RegA 00 00 0129 61 LD H,C A=AA 2000 00 RegB 00 0B C=5A 4000 00 RegC 00 00 Memory 6000 01 RegD 00 00 0122 50 72 6F 67 72 61 6D 61 Programa Page0=2 8000 02 RegE 00 00 012A 20 34 20 2D 20 42 72 69 4 - Bri Page1=2 A000 03 RegF 00 CF 0132 6E 63 61 6E 64 6F 20 63 ncando c Page2=2 C000 00 013A 6F 6D 20 6C 6F 6F 70 73 om loops Page3=2 E000 00 PSG addr 0E BrMSX settings Resolution: 320x200 Frame skipping: 0001 Bar Graph: OFF Emulation: NORMAL Joy: - Sound: ON VSync: OFF Session: SINGLE COM: 1 Video Cache: ON Command: SCC: OFF |
Note que B continua com o valor FF. Pressione F7 para executar o LD D,009h, e veja que C efetivamente cai conter esse valor, agora. ATENÇÃO: NÃO USE F7 AGORA! Sempre que houver uma chamada ao BDOS assim (CALL 0005) USE F8! Caso contrario você vai entrar dentro do BDOS e vai ficar maluquinho pra entender o que tá rolando. Antes de apertar F8 e executar o CALL, no entanto, note que B *ainda* vale FFh. Pressione F8. Agora o ponteiro deve estar indicando a posição de memória 0121, pois o CALL já foi executado... porem, note o valor de B agora...
BrMSX debugger Z80 VDP PSG AF=0044 AF'=0082 Reg0 00 55 0121> C9 RET BC=0002 BC'=3CF6 Reg1 F0 00 0122 50 LD D,B DE=0175 DE'=3CF6 Reg2 00 00 0123 72 LD (HL),D HL=0000 HL'=3CFB Reg3 00 00 0124 6F LD L,A IX=3EFD Reg4 01 00 0125 67 LD H,A IY=3FDB SZ5H3VNC Reg5 00 00 0126 72 LD (HL),D PC=0121 01000100 Reg6 00 00 0127 61 LD H,C SP=D6FC Reg7 F1 B8 0128 6D LD L,L I=00 EI IM1 R=6D Reg8 00 00 0129 61 LD H,C PPI MegaROM Reg9 00 00 012A 20 34 JR NZ,0160 0000 00 RegA 00 00 012C 20 2D JR NZ,015B A=AA 2000 00 RegB 00 0B C=5A 4000 00 RegC 00 00 Memory 6000 01 RegD 00 00 0122 50 72 6F 67 72 61 6D 61 Programa Page0=2 8000 02 RegE 00 00 012A 20 34 20 2D 20 42 72 69 4 - Bri Page1=2 A000 03 RegF 00 CF 0132 6E 63 61 6E 64 6F 20 63 ncando c Page2=2 C000 00 013A 6F 6D 20 6C 6F 6F 70 73 om loops Page3=2 E000 00 PSG addr 0E BrMSX settings Resolution: 320x200 Frame skipping: 0001 Bar Graph: OFF Emulation: NORMAL Joy: - Sound: ON VSync: OFF Session: SINGLE COM: 1 Video Cache: ON Command: SCC: OFF |
Voltou para zero! Uau! Então o vilão da nossa historia é esse "CALL BDOS"?!? Sim, mais ou menos isso. A chamada da função STROUT (9) do BDOS não preserva o valor do registrador B. Isso faz com que nosso contador fique zoado!
Bem, agora já achamos o Bug... voltemos à programação normal para tentar corrigi-lo...
Bem, então o problema é que precisamos "guardar" o contador em algum lugar seguro antes de chamar a função do BDOS. Poderíamos colocar o valor em um outro registrador, mas quem sabe qual deles é seguro? Como essa aula é pesada, não vou introduzir novos conceitos. Vamos corrigir essa falha da mesma forma que corrigimos quando descobrimos que perdíamos o valor de E após coletarmos o mesmo com o CONIN, numa das aula anteriores: salvando na memória.
Assim, a nossa funçãozinha:
LD B,5 ; Indica repetição de 5 vezes. LOOP: LD DE,FUDEBA ; Indica texto fudeba CALL MOSTRATXT ; Mostra texto DJNZ LOOP ; Repete os comandos LD DE,FUDEBA e CALL MOSTRATXT
Fica:
LD B,5 ; Indica repetição de 5 vezes. LOOP: LD DE,FUDEBA ; Indica texto fudeba LD A,B ; Copia em A o contador LD (VAR1),A ; Grava o valor do contador na MEMORIA. CALL MOSTRATXT ; Mostra texto LD A,(VAR1) ; Copia para A o conteúdo de VAR1 LD B,A ; Coloca de volta no contador DJNZ LOOP ; Repete os comandos LD DE,FUDEBA e CALL MOSTRATXT ; Decrementando B até ele ser zero.
Lembrando de acrescentar a VAR1 de volta na lista de variáveis, no fim do programa com:
VAR1: DB 000h
O programa final fica:
--- Cortar Aqui --- BDOS EQU 5 STROUT EQU 9 START: ; Mostra informações do programa e faz pergunta LD DE,NOMEDOPRG ; Indica texto do nome do programa CALL MOSTRATXT ; Mostra texto LD DE,AUTOR ; Indica texto do nome do autor CALL MOSTRATXT ; Mostra texto LD B,5 ; Indica repetição de 5 vezes. LOOP: LD DE,FUDEBA ; Indica texto fudeba LD A,B ; Copia em A o contador LD (VAR1),A ; Grava o valor do contador na MEMORIA. CALL MOSTRATXT ; Mostra texto LD A,(VAR1) ; Copia para A o conteúdo de VAR1 LD B,A ; Coloca de volta no contador DJNZ LOOP ; Repete os comandos LD DE,FUDEBA e CALL MOSTRATXT JP 0 ; Volta ao MSX-DOS ;------ ; MOSTRATXT - Função que mostra um texto cuja seqüência é terminada por '$'. ; Entrada: DE - Aponta para seqüência a ser mostrada, terminada por '$' ;------ MOSTRATXT: LD C,STROUT ; Indica função de mostrar texto do BDOS CALL BDOS ; Manda o BDOS executar. RET ; Retorna NOMEDOPRG: DB 'Programa 4 - Brincando com loops',13,10,'$' AUTOR: DB ' Por Daniel Caetano',13,10,10,'$' FUDEBA: DB 'O DalPoz é um fudeba!',13,10,'$' VAR1: DB 000h END --- Cortar Aqui ---
Que ao ser compilado e executado mostrará direitinho a mensagem 5 vezes.
Na próxima aula aprenderemos a mexer com alguns outros recursos do Z80, como a PILHA, e também introduziremos alguns novos conceitos... Aguardem.
Se você ficou curioso lá em cima, com o fato de ter usado o comando B 0100 e o MSX-DOS continuar funcionando normalmente, inclusive com seus comandos, e o BrMSX só' entrou quando o programinha foi executado, saiba que isso não é magica não. Na verdade, isso só ocorre porque o MSX-DOS não tem NADA de sua estrutura na região 0100h. E também não chama nenhuma função de BIOS no endereço 0100h. Caso essas duas condições não se verificassem, talvez ele pudesse parar num endereço 0100h que não fosse o seu programa (fosse a posição 0100h de algum outro slot que não o da memória). Principalmente no futuro, quando você muitas vezes usara o comando B (break) com endereços totalmente diferentes de 0100h (para parar direto na posição 0109h, por exemplo). Bem você precisa ficar esperto para perceber isso. Um bom indicador são os valores dos "PAGE0" a "PAGE3" da seção da PPI. Como seu programa está na RAM, se o endereço que você mandou parar estiver indicado como sendo um frame que não é da RAM (0 ou 1, por exemplo) com toda a certeza do mundo isso não será um trecho do seu programa. Conhecer bem seu programa também ajuda nessas horas. Assim, se parar no endereço que você queria, mas o conteúdo não é o que você esperava, aja simples: pressione F7 para ele pular para o próximo endereço e use novamente o comando B (break) indicando o mesmo endereço que havia dado anteriormente. Em geral isso resolve.
Povo,
Sei que esta aula é pesada, e que o FuDebug pode parecer meio confuso no começo. Por ser poderoso, acaba sendo um pouco complexo mexer com ele também. No entanto, com ele a programação fica centenas de vezes mais simples...! Eu não mostrei nem 5% da capacidade dele. Com o tempo vamos conhecendo mais. É essencial que vocês "percam tempo" mexendo com ele, e fiquem craques. "defeitos" como esse que mostrei no ultimo programa acontecem aos montes em programas assembly mais complicados, e identificar na mão muitas vezes não é possível, como nesse caso! Essa aula foi dividida em varias partes, e todas elas ficaram enormes (ainda não preparei as outras, mas imagino o que vou colocar nelas) mas é agora que a coisa começa a andar, e coisas úteis começam a ser construídas. Espero que o tempo que gastei bolando muita coisa que está aqui (meses) e o tempo que gastei escrevendo essa aula (muitas e muitas horas, mesmo!). E o que posso dizer é que estou tentando fazer a minha parte, mas consultar outros livros é essencial a aprender a programar ASM. Com o pouco que eu passei, já é possível pegar a lógica de como o assemblador funciona e, lendo um bom livro de Assembly Z80 e tendo à mão um MSX2 Technical Handbook já dá até pra ir embora sozinho... de modo que eu até pretendo dar continuidade no curso, mas adianto que com preguiça não se vai a lugar algum em ASM, porque os códigos são, costumeiramente, enormes e muito complexos, e demandam um bom tempo de tentativa e erro para aprender a lidar com as dificuldades mais comuns.
O meu desafio é: quero ver alguém que consiga fazer algo interessante com o que eu já passei até essa aula. Estou a disposição para responder duvidas e ajudar com alguma coisa, mas assembly é o tipo da coisa que só' praticando pra aprender. Ninguém aprende só lendo.
Um abarco e espero que esse curso, embora meio "quebrado" por períodos de estiagem, umedeça a mente de vocês, para que novas idéias possam germinar e ganhar vida... (pô, ficou bonito isso!)
Abraços,
Daniel Caetano.
PS: Sugestões e comentários são bem-vindos. (^=