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:
  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:   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:
  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:
  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.


<< Anterior Pascal Próxima >>