Estava fazendo uns testes de código em MSX-DOS e experimentando como chamar a BIOS a partir do ambiente e… opa! Melhor explicar uma coisa importante antes.
No MSX, quando estamos no BASIC a memória tem o seguinte leiaute:
0x0000 - 0x7FFF : BIOS e MSX-BASIC 0x8000 - 0xFFFF : RAM (32Kib)
Ou seja uns 32KiB de ROM estão lá ocupando 50% do espaço de endereçamento que o Z80 entende e chama de “mundo”.
Mas quando estamos no MSX-DOS a memória fica assim:
0x0000-0xFFFF : RAM (64Kib)
Mas como se faz para acessar a BIOS já que ela “desapareceu” da vista do Z80?
Antes de continuar é bom lembrarque as variáveis de sistema nos dois casos ainda estão lá, organizadas a partir de 0xFFFF e seguindo rumo a 0x0000 — o que é bom pois tem muita coisa que precisa daquilo ali pra continuar funcionando — e também que o MSX-DOS (tal qual acontece com o CP/M) os primeiros 256 bytes (ou seja, de 0 até 255) contém o “sistema operacional” e ali não é para inventar de mexer!
Aliás este é o motivo dos programas começarem sempre no endereço 256 (ou 0x0100).
Chamando a BIOS
Existe uma rotina, a CALSLT (0x001C), que serve para executar um trecho de código que esteja localizado em qualquer slot — ela é irmã das RDSLT e WRSLT que, respectivamente, lê e escreve um byte em qualquer slot. Assim, basta informar no registrador IY o slot onde está rotina, em IX o endereço a executar e chamá-la. Assim:
ld iy,(EXPTBL-1)
ld ix,endereço da rotina
call CALSLT
Pelo que entendi a localização da ROM está armazenado no endereço anterior ao da variável de ambiente EXPTBL (0xFCC1), ou seja, está em 0xFCC0 mas como parece que todos evitam fazer referência direta a ela, também farei o mesmo.
Para fazer um programa em MSX-DOS usando a rotina rotina BEEP (0x00C0) da BIOS para emitir um bipe (dahhh….) o programa ficaria assim:
; ; BEEP.COM - emite um beep ; BEEP: equ 0x00C0 CALSLT: equ 0x001C EXPTBL: equ 0xFCC1 org 0x0100 ld iy,(EXPTBL-1) ld ix,BEEP call CALSLT ret
Lindo não? E pode montar que funciona! Mas se duvida…
$ pasmo -v -d beep1.asm beep1.com Loading file: beep1.asm in 0 Finished loading file: beep1.asm in 11 Entering pass 1 Pass 1 finished Entering pass 2 ORG 0100 BEEP EQU 00C0 CALSLT EQU 001C EXPTBL EQU FCC1 0100:FD2AC0FC LD IY, (FCC0) 0104:DD21C000 LD IX, 00C0 0108:CD1C00 CALL 001C 010B:C9 RET Pass 2 finished Emiting raw binary from 0100 to 010B
Automatizando um pouco
É óbvio que ao escrever a terceira chamada à BIOS num programa você já estará de saco cheio de escrever “LD IY,…” e “LD IX,…” e as chances de inverter a ordem ou esquecer algo começarão a aumentar. Mas como todo bom programador você poderia criar uma casca (um “wrapper”) para encapsular as chamadas mas aí você estaria encapsulando algo que já foi encapsulado.
É aí que entram as facilidades dos assemblers (lembre-se: montadores!) , basta criar uma macro que cuidará de automatizar a tarefa e deixando até mesmo! Ou seja, dizer ao montador que onde estiver X você quer que ele escreva A, B e C.
Eu criei uma assim:
; ; BEEP2.COM - emite um beep ("like a boss" edition) ; BEEP: equ 0x00C0 CALSLT: equ 0x001C EXPTBL: equ 0xFCC1 org 0x0100 MACRO __call,BIOS ld iy,(EXPTBL-1) ld ix,BIOS call CALSLT ENDM __call BEEP ret
A sintaxe com os dois sublinhados na frente são invenção minha para ajudar a diferenciar o mnemônico “call” da macro “__call” e, sim, é meio que uma inspiração direta de Python.
O fonte ficou com uma aparência mais bonita e até elegante porém um pouco mais longo ao saltar de 11 para 15 linhas, porém repare que se eu resolver tocar três bipes os dois códigos acabarão com o mesmo tamanho e a partir do quarto a versão que não usa a macro já será maior (além de mais confusa).
Na montagem acontece o seguinte:
$ pasmo -v -d beep2.asm beep2.com
Loading file: beep2.asm in 0
Finished loading file: beep2.asm in 15
Entering pass 1
Pass 1 finished
Entering pass 2
ORG 0100
BEEP EQU 00C0
CALSLT EQU 001C
EXPTBL EQU FCC1
Defining MACRO __call
Params: BIOS
Expanding MACRO __call
BIOS= BEEP
LD IY , ( EXPTBL - 0001 )
0100:FD2AC0FC LD IY, (FCC0)
LD IX , BIOS
0104:DD21C000 LD IX, 00C0
CALL CALSLT
0108:CD1C00 CALL 001C
ENDM
ENDM
End of MACRO __call
010B:C9 RET
Pass 2 finished
Emiting raw binary from 0100 to 010B
O bom observador perceberá que o programa gerado aqui é idêntico ao do primeiro exemplo já que não existe código novo, apenas a substituição da ocorrência da macro pelas instruções correspondentes, ou seja, no final fica igual.
Para os incrédulos:
$ md5sum beep?.com faa9a3b426121de5839e8295bca2d1c6 beep1.com faa9a3b426121de5839e8295bca2d1c6 beep2.com
Automatizando mais ainda!
A vantagem desta abordagem é permitir a criação de código facilmente adaptável a determinadas situações, por exemplo um código que se ajusta ao formato destino dele sem a necessidade de fazer (muitas) alterações no código:
; ; BEEP3.COM - emite um beep ("like a final boss" edition) ; BEEP: equ 0x00C0 CALSLT: equ 0x001C EXPTBL: equ 0xFCC1 if TARGET=1 ; ; monta para MSX-DOS ; MACRO __call,BIOS ld iy,(EXPTBL-1) ld ix,BIOS call CALSLT ENDM org 0x0100 else ; ; monta para MSX-BASIC (BLOAD) ; MACRO __call,BIOS call BIOS ENDM org 0x8000-7 db 0xfe dw START dw STOP dw EXEC endif START: EXEC: __call BEEP ret STOP:
Aqui há uma definição, a TARGET que igual a um produzirá um programa de MSX-DOS ou com outro valor resultará em um arquivo binário para o MSX-BASIC para carregar com o comando BLOAD. Basta colocar antes do “if” a definição “TARGET: equ 0” ou “TARGET: equ 1” dependendo da necessidade.
Já que a complexidade do código aumentou, vamos aumentar o processo de montagem também, este cara cuida de gerar as duas versões sem interferência humana com um pequeno script em Bash:
#!/bin/bash TEMP=/tmp/beep3_tmp.asm I=0 for J in bin com do echo "TARGET: equ ${I}" > ${TEMP} cat beep3.asm >> ${TEMP} pasmo -d -v ${TEMP} beep3.${J} > ${J}.log 2>${J}.err I=$((I+1)) done rm ${TEMP} exit 0
E mesmo assim o código gerado continua é o mesmo:
$ md5sum beep?.com faa9a3b426121de5839e8295bca2d1c6 beep1.com faa9a3b426121de5839e8295bca2d1c6 beep2.com faa9a3b426121de5839e8295bca2d1c6 beep3.com
Provocação final
Agora como provocação da minha parte vou deixar um exercício: Que tal acrescentar a possibilidade de produzir também um arquivo ROM? No código do Flappybird para MSX tem a dica de como produzir uma imagem ROM, inclusive com preenchimento do que falta com zeros (o padding). E boa diversão!
Muito bom artigo!
Fiquei com uma dúvida. As variáveis do sistema começam em 0xFFFF e continuam em 0x0000? 0xFFFF não é o final da memória?
Começam em 0xFFFF e vão “subindo” pra 0x0000, vou corrigir minha interpretação da realidade.
Excelente post. Sugiro montar uma seção, ou um blog, com dicas e descobertas.
Apoiado! Tem que ter uma área para o desenvolvimento de novos programas/jogos para diversas plataformas…
Recomendo o grupo do manu parça Popolon, o GDMSX. Que não é só de MSX.