Pascal for Experts
Criando Rotinas em Assembly
Você está em: MarMSX >> Cursos >> Pascal
Linguagem Assembly exclusiva do MSX.
O comando inline do Pascal permite a inserção de códigos em linguagem de máquina dentro de um programa.
Sintaxe:
inline ( elemento_1 / elemento_2 / ... / elemento_n);
Um elemento de código é composto por um ou mais elementos de dado, separados por um sinal de mais (+) ou de menos (-).
Um elemento de dado pode ser:
- Um valor inteiro
- Uma variável
- Uma procedure
- Uma função
- Uma referência de contador de local
Cada elemento de código pode gerar um ou dois bytes de código. Quando houver sinal, o valor gerado é o resultado da expressão.
Quando utilizamos uma procedure ou função como elemento de dado, é o endereço delas que é retornado como código. Isto é útil para desviar rotinas em Assembly para uma procedure ou função.
A referência de contador de local, indicada pelo asterisco(*), indica a localização do próximo byte. Por exemplo, *+2 salta duas posições de memória.
Adaptado de [1].
Exemplos:
var i : integer;
begin
i:=8;
inline ($44 / 10 / i);
end;
Código gerado (hexa): 44 0A 08 00.
var i : byte;
begin
i:=8;
inline ($44 / *+1 / i+1);
end.
Código gerado (hexa): 44 xx 09, onde "xx" indica lixo de memória.
O programa a seguir fará uma chamada a um procedimento através do código em LM.
procedure imprime;
begin
writeln('Ok');
end;
begin
inline( $CD/imprime ); { CALL imprime }
end.
Nota importante: o comando inline irá gerar e executar o código em linguagem de máquina passado a ele.
Configuração da Memória no DOS
Quando criamos um programa em Assembly para rodar no ambiente do Basic, temos os 64 KB de memória configurados da seguinte maneira:
0000H ┌─────┐
│ ROM │ Página 0 - BIOS
4000H ├─────┤
│ ROM │ Página 1 - Interpretador Basic
8000H ├─────┤
│ RAM │ Página 2 - Livre
C000H ├─────┤
│ RAM │ Página 3 - Livre
FFFFH └─────┘
Observamos na configuração apresentada, que tanto a BIOS como o interpretador Basic já estão ativos nas páginas 0 e 1, respectivamente. Assim, qualquer chamada à BIOS ou ao interpretador Basic é feita diretamente ao endereço da rotina.
Por exemplo, para chamar a rotina de limpeza de tela (CLS) da BIOS, desviamos o programa para o endereço &H00C3.
10 CLS: EQU &HC3
CD C3 00 20 CALL CLS
C9 30 RET
No ambiente MSX-DOS, a memória é configurada da seguinte maneira:
0000H ┌─────┐
│ RAM │ Página 0 - DOS / Livre
4000H ├─────┤
│ RAM │ Página 1 - Livre
8000H ├─────┤
│ RAM │ Página 2 - Livre
C000H ├─────┤
│ RAM │ Página 3 - Livre
FFFFH └─────┘
Agora, as quatro páginas de memória estão configuradas para RAM. O acesso à BIOS ou ao interpretador Basic só poderá ser feito, se alterarmos as páginas 0 e/ou 1 para o modo ROM.
Esse processo pode ser feito de duas formas:
- Através de uma rotina de troca de slots da BIOS, que também está disponível para o DOS.
- Trocando manualmente os slots.
A primeira opção realiza a troca de slots automaticamente para o usuário, chama a rotina e depois retorna à configuração do DOS.
As rotinas para troca de slots da BIOS, onde algumas também estão disponíveis para o DOS são as seguintes [2]:
Rotina |
Endereço |
Descrição |
RDSLT |
000CH |
Lê um byte em qualquer slot |
WRSLT |
0014H |
Escreve um byte em qualquer slot |
CALSLT |
001CH |
Chama uma rotina em qualquer slot |
ENASLT |
0024H |
Troca páginas e slots |
CALLF |
0030H |
Chama uma rotina em qualquer slot |
RSLREG |
0138H |
Lê um registrador em slot primário |
WSLREG |
013BH |
Escreve em um registrador de slot primário |
SUBROM |
015CH |
Chama uma rotina na SUB-ROM |
EXTROM |
015FH |
Chama uma rotina na SUB-ROM |
A rotina de troca de slots mais utilizada é a rotina CALSLT (&H1C), que será abordada na próxima seção.
A outra opção é trocar manualmente os slots, através da porta &HA8. Quando isso é feito, o programa em execução DEVERÁ estar em uma página diferente da página da ROM requisitada. Por exemplo, quando chamamos uma rotina da BIOS, devemos estar em uma página diferente da 0.
Essa opção será abordada na solução do SLotman da seção "Rodando uma função do interpretador Basic em um endereço fixo".
Observação importante: se o seu programa em Assembly NÃO faz qualquer acesso à BIOS ou ao interpretador Basic, NÃO é necessário trocar os slots.
Podemos obter os slots da ROM e SUB-ROM acessando as variáveis de sistema, localizadas na página 3 da RAM. Esta página está disponível tanto para o Basic, como para o DOS.
EXPTBL (&HFCC1) - Slot da ROM
EXBRSA (&HFAF8) - Slot da SUB-ROM
O acesso à BIOS do MSX
Uma vez que o MSX-DOS ocupa as 4 páginas de memória com a RAM, chamadas à BIOS poderão ser feitas utilizando uma chamada inter-slot.
O procedimento padrão é o seguinte:
begin
inline (
$F3/ { DI }
$FD/$2A/$C0/$FC/ { LD IY,(EXPTBL-1) }
$DD/$21/rotina/ { LD IX,rotina_da_bios }
$CD/$1C/$00/ { CALL CALSLT }
$FB { EI }
);
end.
Onde:
- EXPTBL fica em &HFCC1
- CALSLT fica em &H00C1
O registrador IY recebe o slot da ROM através do conteúdo de EXPTBL-1, enquanto que o registrador IX recebe o endereço de memória a ser chamado.
As interrupções são desabilitadas (DI) para evitar que o código seja interrompido. Ao final, são novamente habilitadas (EI).
Quando a rotina da BIOS tiver dados de entrada, eles devem ser fornecidos no programa em LM antes do CALSLT. Veja o exemplo da rotina que muda o modo de tela do Lammassaari.
procedure Screen(mode:Byte);
begin
inline (
$f3 { DI }
/$3a/mode { LD A,(MODE) }
/$fd/$2a/$c0/$fc { LD IY,(EXPTBL-1) }
/$DD/$21/$5f/00 { LD IX,CHGMOD }
/$CD/$1c/00 { CALL CALSLT }
/$fb { EI }
);
end;
A rotina da BIOS CHMOD (&H005F) usa como parâmetro de entrada no registrador A o modo de tela.
O acesso à SUB-ROM do MSX 2
A SUB-ROM do MSX 2 se localiza na página 0, mas em outro slot diferente da ROM principal. Ela contém uma série de rotinas, especialmente para desenhar caracteres, caixa, caixa preenchida no modo gráfico e funções de relógio.
O procedimento padrão para o acesso à SUB-ROM é o seguinte [2]:
EXTROM: EQU &H015F
LD IX,ROTINA
CALL EXTROM
Entretanto, a chamada EXTROM não funciona no modo DOS. Assim, para alcançar a SUB-ROM deveríamos primeiro alterar o slot para ROM, para depois chamar a EXTROM.
Para contornar esse problema, iremos adotar a mesma solução de chamada à BIOS. Para isso, passamos como parâmetro para IY a variável EXBRSA (&HFAF8), que contém o slot da SUB-ROM em vez do slot da ROM.
Assim, o procedimento padrão para o acesso à SUB-ROM do MSX 2 no Pascal é o seguinte:
begin
inline (
$F3/ { DI }
$FD/$2A/$F7/$FA/ { LD IY,(EXBRSA-1) }
$DD/$21/rotina/ { LD IX,rotina_da_subrom }
$CD/$1C/$00/ { CALL CALSLT }
$FB { EI }
);
end.
Onde:
- EXBRSA fica em &HFAF8
- CALSLT fica em &H00C1
O exemplo a seguir é a função line_b do Lammassaari, que irá fazer acesso a rotina NVBXLN da SUB-ROM [2] para desenhar uma caixa na tela.
procedure line_b(x,y,x2,y2:integer;kleur,log_op:byte);
begin
mem[$fcb3]:=lo(x2);mem[$fcb4]:=hi(x2);
mem[$fcb5]:=lo(y2);mem[$fcb6]:=hi(y2);
mem[$f3f2]:=kleur;
mem[$fb02]:=log_op;
inline($ed/$4b/x/ { LD BC,(X) }
$ed/$5b/y/ { LD DE,(Y) }
$fd/$2a/$f7/$fa/ { LD IY,(&HFAF7) }
$dd/$21/$c9/$00/ { LD IX,&H00C9 }
$cd/$1c/$00/ { CALL &H001C }
$fb) { EI }
end;
Onde:
NVBXLN (00C9H)
Função: Desenha uma caixa nas telas gráficas.
Entrada: Ponto inicial: BC - coordenada X (horizontal).
DE - coordenada Y (vertical).
Ponto final:
GXPOS (FCB3H) - coordenada X (horizontal).
GYPOS (FCB5H) - coordenada Y (vertical)
Cor:
ATRBYT (F3F2H) - atributo.
Código de operação lógica: LOGOPR (FB02H).
Saída: Nenhuma.
Registradores: Todos.
Note que essa rotina tem como parâmetro de entrada além dos registradores BC e DE, as posições de memória &HFCB3, &HFCB5, &HF3F2 e &HFB02.
Resumindo, temos:
Chamda à BIOS (ROM):
LD IX,rotina
LD IY,(EXPTBL-1) ; EXPTBL em &HFCC1
CALL CALSLT ; CALSLT em &H001C
Chamada à SUB-ROM:
LD IX,rotina
LD IY,(EXBRSA-1) ; EXBRSA em &HFAF8
CALL CALSLT ; CALSLT em &H001C
Rodando um programa em LM em um endereço fixo
O comando inline do Pascal gera e executa um programa em linguagem de máquina em um endereço aleatório de memória. Entretanto, se o programa a ser executado necessitar estar em um endereço fixo da memória, podemos criá-lo nesse endereço e então chamá-lo através do inline.
Exemplo:
const code : array[0..6] of byte = ($21,$00,$f1, { LD HL,&HF100 }
$3e,$05, { LD A,5 }
$77, { LD (HL),A }
$c9); { RET }
var prog : array[0..6] of byte absolute $f000;
begin
move(code,prog,7); { Copia o codigo para &HF000 }
inline($cd/$00/$f0); { CALL &HF000 }
writeln('Valor em F100: ', mem[$f100]);
end.
Saída:
Valor em F100: 5
O programa em LM altera o valor da posição de memória em &HF100 para 5. Essa alteração é verificada quando a posição de memória &HF100 é lida pela instrução writeln.
Obs: Uma vez que o inline chama o programa através de uma instrução CALL (&HCD), devemos utilizar a instrução RET (&HC9) ao final desse programa.
Rodando uma função da BIOS em um endereço fixo
Para acessar uma rotina da BIOS desse modo, devemos fazer o chaveamento de páginas ou utilizar a rotina ENASLT [2] para alcançar a ROM.
ENASLT (0024H)
Função: Habilita uma página em qualquer slot. As interrupções são
desativadas durante a habilitação. Somente as páginas 1 e
2 podem ser habilitadas por esta rotina, a 0 e 3 não.
Entrada: A - indicador de slot (igual a RDSLT - 000CH).
HL - qualquer endereço da página a ser habilitada.
Saída: Nenhuma.
Registradores: Todos
Se a página for 0 (BIOS), fazemos HL=0. Se a página for 1 (Interpretador Basic), fazemos HL=&H4000.
Procedimento padrão [3]:
F3 DI
3A C1 FC LD A,(&hFCC1)
21 00 00 LD HL,0
CD 24 00 CALL &H24
CD rotina CALL rotina
3A 41 F3 LD A,(&HF341) ; Slot da página 0 da RAM
26 00 LD H,00
CD 24 00 CALL &H24
FB EI
C9 RET
Programa que chama a rotina CHGET (&H9F) da BIOS.
const code : array[0..22] of byte = ($f3,$3a,$c1,$fc,$21,$00,$00,$cd,$24,$00,
$cd,$9f,$00,$3a,$41,$f3,$26,$00,$cd,$24,
$00,$fb,$c9);
var prog : array[0..22] of byte absolute $f000;
begin
move(code,prog,23); { Copia o codigo para &HF000 }
inline($cd/$00/$f0); { CALL &HF000 }
end.
Ao executar esse programa, o cursor irá aguardar que o usuário tecle alguma coisa.
Rodando uma função do interpretador Basic em um endereço fixo
Procedimento padrão [3]:
F3 DI
3A C1 FC LD A,(&hFCC1)
F5 PUSH AF
21 00 00 LD HL,0
CD 24 00 CALL &H24 ; Habilita pagina 0 da ROM
F1 POP AF
26 40 LD H,&H40
CD 24 00 CALL &H24 ; Habilita pagina 1 da ROM
CD rotina CALL rotina
3A 41 F3 LD A,(&HF341) ; Slot da página 0 da RAM
26 00 LD H,00
CD 24 00 CALL &H24
3A 42 F3 LD A,(&HF342) ; Slot da página 1 da RAM
26 00 LD H,&H40
CD 24 00 CALL &H24
FB EI
C9 RET
Solução alternativa criada pelo SLotman [4], que troca manualmente os slots:
B100 DB A8 IN A,(&HA8) ; Le configuração de slots
B102 F5 PUSH AF ; Salva configuração
B103 3E A0 LD A,SLOT ; Muda para slot 0
B105 D3 A8 OUT (&HA8),A ; as página 0 e 1
ROTINA
B10C F1 POP AF ; Recupera configuração
B10D D3 A8 OUT (&HA8),A ; de slots
B10F C9 RET
Onde:
SLOT := port[$A8] and $F0;
O vetor pré-definido "port" lê ou escreve um dado na porta especificada.
A configuração de slots na porta &HA8 é feita assim:
bit 76543210
pagina 33221100
Há duas maneiras de chamar uma rotina do interpretador Basic do MSX:
A primeira consiste em passar a string que vêm logo após o comando Basic e chamar a rotina desse comando. Exemplo:
CIRCLE(128,95),40
Passar o endereço da seguinte string para o registrador HL:
"(128,95),40"
Assim, substituímos o trecho da ROTINA do SLotman por:
21 00 B0 LD HL,&HB000 ; Endereço da string
CD 11 5B CALL &H5B11 ; Rotina do circle
Criamos a string no endereço &HB000 antes de rodar o programa:
var cmd : string[80] absolute $B000;
...
cmd := '(128,95),40';
inline (programa_todo);
A segunda consiste em alterar as variáveis de sistema utilizadas por uma determinada rotina do interpretador Basic, saltar a parte da interpretação da string de comando e ir direto
para o trecho do programa que realiza a ação. No caso do circle, o local de inicio ficaria em &H5BB2. As rotinas do Lammassaari que acessam o interpretador Basic fazem isso.
Referências:
[1] - Turbo Pascal - Reference Manual, 1988.
[2] - MSX Top Secret 2, Edison Moraes, 2004.
[3] - Rotinas do Lammassaari, "circle.inc".
[4] - MSXPad, disponível em: http://www.icongames.com.br/msxfiles.