Curso de Assembly
Sub-rotinas da ROM do MSX (BIOS)


Você está em: MarMSX >> Cursos >> Assembly Z80   Foi visto no capítulo sobre níveis de programação do curso de Assembly, que o microprocessador é um circuito eletrônico (nível 0). Ele foi desenvolvido para resolver determinados problemas, através programas escritos em linguagem de máquina (nível 2). Os acessórios (periféricos) do computador são também microprocessadores independentes, desenvolvidos para desempenhar determinados papéis como emitir sons (processador de som), controlar teclado ou emitir um sinal visual na tela (processador de vídeo). O processador principal (Z80) é o responsável pela comunicação e gerenciamento desses periféricos. A comunicação entre os dispositivos é feita através de portas (em linguagem de máquina, através dos comandos IN e OUT mais o endereço da porta).
  Um programador em nível de linguagem de máquina que deseje emitir um som ou desenhar algo na tela, deverá ter conhecimentos sobre o funcionamento desses hardwares periféricos, assim como saber quais dados enviar para eles. Dessa forma, uma simples tarefa torna-se algo um pouco complexo.
  De forma a facilitar a vida desse programador, foram desenvolvidas pelos projetistas do MSX rotinas em linguagem de máquina, com o objetivo de realizar essas tarefas complexas. Além disso, tais rotinas também são responsáveis pelo gerenciamento do funcionamento do MSX. Essas rotinas são chamadas de BIOS, e residem na ROM principal do computador.
  A BIOS é uma espécie de sistema operacional do MSX (nível 3). A partir dela, o programador não necessita mais de conhecimentos profundos de como operar os processadores de som, vídeo etc, bastando apenas saber como utilizar tais rotinas. Um exemplo de rotina da BIOS é a rotina WRTVRM, que escreve um caractere na tela, bastando informar qual o código ASCII do caractere, mais o local da VRAM.
  As sub-rotinas equivalem às funções das linguagens de alto nível, onde o usuário apenas deverá chamar a função e informar os parâmetros necessários, e então ela realiza uma determinada tarefa para ele. Assim, o que se passa dentro de uma função é transparente para o usuário final (caixa-preta).

  Sejam os seguintes exemplos em Pascal:

Sem uma função Com uma função
var i : integer;
    a, b, produto : real;
begin
  
produto := 0; a := 5; b := 4; for i:=1 to b do begin produto := produto + a; end; writeln(produto);
end.
function produto(a : integer; b : real) : real;
var i : integer;
begin
  produto := 0;

  for i:=1 to b do
  begin
    produto := produto + a;
  end;
end;

begin
  
writeln(produto(5,4));
end.

  No exemplo acima, observamos as duas situações: um código sem o auxílio de uma sub-rotina pronta e outro com o auxílio. Na coluna da esquerda da tabela, observamos um código sem o auxílio de uma sub-rotina "pronta". Assim, o trabalho do programador (destacado em verde) é completo na realização do cálculo do produto. Já na coluna da direita, uma sub-rotina (no caso é a função) já havia sido desenvolvida. Assim, o trabalho do programador é apenas passar os valores 5 e 4 para essa rotina (assinalada em verde). Dessa forma, o programador não precisa saber como é feito o cálculo do produto, e sim apenas passar a informação para a rotina e aguardar o resultado.

  No caso das sub-rotinas da ROM, as funções são chamadas através da instrução CALL. Em vez de um nome, é passado o endereço de memória em que está localizado o inicio da sub-rotina. Os parâmetros de entrada e saída são passados ou recebidos diretamente nos registradores.

  Há duas boas fontes de consulta às sub-rotinas existentes do MSX: o Livro Vermelho do MSX, ou The MSX Red Book, e o livro MSX Top Secret (versões 1 e 2), do Edison Moraes. Ambos possuem versões digitais para serem baixadas.

  Cada sub-rotina possui um endereço inicial de memória, conforme já foi dito. Entretanto, ao consultar esses livros, você notará que cada uma dessas sub-rotinas possui também uma etiqueta com o nome da função. Isso serve para facilitar a referência a essas rotinas.

  No MSX, a BIOS possui uma tabela a partir da posição 0000 contendo ponteiros para a real localização das sub-rotinas. Isso permite que a ROM seja reescrita, mantendo a compatibilidade entre sistemas. Exemplo:
 Ponteiro | Rotina | Endereço Real | Descrição
 ---------+--------+---------------+------------
 004AH    | RDVRM  | 07D7H         | Lê byte da VRAM
 004DH    | WRTVRM | 07CDH         | Escreve byte na VRAM
  Dessa forma, o acesso a cada sub-rotina deverá SEMPRE ser feito utilizando o valor de ponteiro e não do endereço real.
 CALL &H4D	; Correto.
 CALL &H7CD	; Funciona no MSX 1, mas poderá falhar em sistemas futuros.
  Veja a recomendação do Livro Vermelho [3]:
  "A descrição da ROM começa com uma lista de pontos de entrada para as rotinas padrões. Para manter a máxima compatibilidade com software futuros, um programa deverá restringir sua dependência à ROM apenas para essas localizações."

  Para exemplificar, vamos utilizar a sub-rotina chamada WRTVRM, que tem como objetivo escrever dados na VRAM. No nosso caso, queremos escrever um caractere na tela. Para isso, basta alterar a tabela de caracteres da screen 0, que começa na posição 0 da VRAM.
  O ponteiro para essa rotina está localizado na posição &H004D da tabela da BIOS. Já o código dela se localiza na posição &H07CD da BIOS. Podemos fazer o acesso direto a esta posição, mas para manter a compatibilidade com sistemas diferentes do MSX 1, vamos utilizar o valor do ponteiro, que é &H4D.
  Ao consultar a sub-rotina WRTVRM no Livro Vermelho do MSX, encontramos as seguintes configurações de entrada e saída:
WRTVRM
 Endereço: 07CDH
 Nome: WRTVRM
 Entrada: A=byte de dado, HL=endereço da VRAM.
 Saída: Nada.
 Modifica: EI
  Esse endereço não deve ser usado, mas sim o &H4D.
  Essa instrução possui dois parâmetros de entrada:   Como desejamos imprimir um caractere na coordenada 0x0 da screen 0, o dado do registrador A será o código ASCII do caractere a ser impresso na tela e o endereço passado para o registrador HL será 0. Para maiores detalhes sobre a tabela de caracteres do MSX, clique aqui.
  Essa sub-rotina não retorna qualquer informação, pois a saída está vazia.
  O registrador EI é modificado nessa operação.

  A seguir, será apresentado um programa exemplo para imprimir a letra "A" no canto superior esquerdo da tela.
  O código ASCII da letra A é &H41. Portanto, o nosso programa fica assim:
Endereço  Assembly  Linha  Mnemônico

004D                10     WRTVRM: EQU &H4D
D000                20     ORG &HD000
D000      3E41      30     LD A,&H41
D002      210000    40     LD HL,0
D005      CD4D00    50     CALL WRTVRM
D008      C9        60     RET
  Obs: para gravar no RSCII, use: GB "wrtvrm.bin",&hD000,&HD008.

  De forma a compreendermos o que a rotina WRTVRM faz para a gente, vamos dar uma olhada no código-fonte dela [1], juntamente com o código-fonte do SETWRT [1], que é referenciado por ele:

WRTVRM 07CD SETWRT 07DF
END	COD		MNEMONICO

07CD	F5		PUSH AF
07CE	CD 07 DF	CALL SETWRT
07D1	E3		EX (SP),HL
07D2	E3		EX (SP),HL
07D3	F1		POP AF
07D4	D3 98		OUT (&H98),A
07D6	C9		RET
END	COD		MNEMONICO

07DF	7D		LD A,L
07E0	F3		DI
07E1	D3 99		OUT (&H99),A
07E2	7C		LD A,H
07E3	E6 3F		AND &B00111111
07E4	F6 40		OR &B01000000
07E6	D3 99		OUT (&H99),A
07E8	FB		EI
07EB	C9		RET

  Para entendermos um pouco do funcionamento dessas rotinas, vamos à teoria sobre o VDP encontrada no livro Assembler para o MSX [2]:

  O chip 9128 VDP contém todos os circuitos eletrônicos necessários para gerar o sinal de vídeo. Ele aparece para o Z80 como sendo duas portas de entrada/saída, chamadas de portada de dados e porta de comando.
  Embora o VDP tenha seus próprios 16 Kb de VRAM, cujo conteúdo define a imagem da tela, ela (a memória) não pode ser acessada diretamente pelo Z80.
  Apesar de utilizar duas portas de entrada/saída para modificar a VRAM, faz-se necessário ajustar várias condições de operação do VDP.

  Porta de dados (porta de E/S &H98):
  A porta de dados é usada para ler o escrever bytes simples na VRAM.
  O VDP possui um registro de endereçamento interno apontando para uma localização na VRAM. Lendo a porta de dados, um byte será lido a partir de uma localização da VRAM, enquanto escrevendo nessa porta, fará com que um byte seja armazenado lá.
  Após uma operação de leitura/escrita, o registro de endereçamento será automaticamente incrementado para o próximo endereço de VRAM.
  Um sequência de bytes pode ser acessada simplesmente pela leitura ou escrita contínua da porta de dados.

  Porta de comandos (porta de E/S &H99):
  Esta porta de comandos é utilizada para três propósitos:
  1. Setar o registro de endereços da porta de dados.
  2. Ler o registro de estado do VDP.
  3. Escrever a um dos registros de modo do VDP.
  Registro de endereços:
  O registro de endereçamento da porta de dados deve ser setado de diferentes modos, dependendo se o acesso subseqüente será de leitura ou escrita.
  Ele pode ser setado para qualquer valor entre &H0000 a &H3FFF, primeiro escrevendo-se o byte menos significativo e a seguir o byte mais significativo na porta de comando. Os bits 6 e 7 do byte mais significativo são usados pelo VDP para determinar se o registro de endereços esta sendo setado para subseqüentes leituras (00) ou escritas (01). O esquema é apresentado a seguir:
  +---------+----------+----------+
  |         |   LSB    |   MSB    |
  +---------+----------+----------+
  | Leitura | XXXXXXXX | 00XXXXXX |
  +---------+----------+----------+
  | Escrita | XXXXXXXX | 01XXXXXX |
  +---------+----------+----------+
  Obs: LSB = byte menos significativo, MSB = byte mais significativo e "X" indica bits não afetados.

  É importante notar que nenhum outro acesso é feito para a VDP, que não seja escrevendo o byte mais significativo e o byte menos significativo, por causa de sua sincronização.
  A manipulação das interrupções da ROM do MSX está continuamente lendo o registro de estado do VDP de forma que as interrupções podem ser desabilitadas, se necessário." [2]

  Através desses conceitos, observa-se quantos conhecimentos do VDP são necessários para manipulá-lo diretamente. Vamos enumerar algumas tarefas necessárias para escrever um byte:
  1. Desabilitar interrupção.
  2. Indicar se operação é de leitura ou escrita.
  3. Ajustar endereço da VRAM pela porta de comandos.
  4. Habilitar interrupções
  5. Enviar dado pela porta de dados
  A partir da sub-rotina WRTVRM da ROM , o que o programador necessita é apenas setar em HL o endereço da VRAM e em A o código ASCII do byte.

  Observe a seguinte frase da teoria do VDP apresentada: "faz-se necessário ajustar várias condições de operação do VDP". Que condições são essas? Não importa para o usuário a nível de sistema operacional (BIOS do MSX) !!

  A seguir, os códigos do WRTVRM e SETVRM serão unidos em um único código, onde cada linha será comentada.
10  PUSH AF		; Armazena o conteúdo de AF na pilha 
20  LD A,L		; Carrega A com o byte menos significativo (LSB) de HL 
30  DI			; Desabilita interrupções
40  OUT (&H99),A	; Envia LSB para a porta de comandos do VDP
50  LD A,H		; Carrega A com o byte mais significativo (MSB) de HL 
60  AND &B00111111	;
70  OR &B01000000	; Seta bits 7 e 6 como modo de "escrita" do VDP
80  OUT (&H99),A	; Envia MSB para a porta de comandos do VDP
90  EI			; Habilita interrupções
100 EX (SP),HL		; 
110 EX (SP),HL		; 
120 POP AF		; Recupera registro AF da pilha
130 OUT (&H98),A	; Envia código do caractere para a porta de dados da VDP
140 RET			; Retorna
  Um efeito colateral desse código genérico pode ser observado: para a escrita de mais de um byte em seqüência, só é necessário indicar o endereço do primeiro caractere, conforme visto no texto do livro. Dessa forma, o código acima faz, desnecessariamente, o ajuste do endereço da VRAM para cada byte em seqüência, no caso de escrita de uma string na tela.
  A sub-rotina LDIRVM[1] transfere um bloco inteiro da RAM para a VRAM, e é mais eficiente para esse fim:
10  LDIRVM: EX DE,HL
20  CALL SETWRT
30  LDIVM1: LD A,(DE)
40  OUT (&H98),A
50  INC DE
60  DEC BC
70  LD A,C
80  OR B
90  JR NZ,LDIVM1
100 RET
  Agora, a transferência de bytes está dentro de um loop e o endereço é ajustado apenas uma vez. DE recebe o endereço inicial da VRAM, HL o endereço inicial da RAM e BC o comprimento da string.

  Poderão haver casos específicos em que uma sub-rotina da ROM não atenda ou atenda de forma ineficiente um dado problema. Um exemplo disso é o acoplamento de um novo hardware ao MSX, como um braço de robô ou nova placa de som. Nesses casos, é necessário escrever um novo código para atender os requisitos necessários ao acesso a esses hardwares, uma vez que essas novidades não foram previstas pelos projetistas do MSX. Esses programas são geralmente escritos pelos próprios fabricantes dos hardwares e são chamados de "drivers".

  Exercício:

  Escrever a frase "O MSX vive" na tela, em Assembly.

  Solução:
10  WRTVRM: EQU &H4D
20          ORG &HD000
30          LD HL,0             ; "Variável" com o endereço da VRAM
40          LD BC,NOME          ; "Variável" com o endereço inicial da frase
50  INICIO: LD A,(BC)           ; Carrega letra em A
60          CALL WRTVRM         ; Chama sub-rotina de escrita na tela
70          INC HL              ; Passa para a próxima posição na tela (na variável)
80          INC BC              ; Passa para a próxima letra (na variável)
90          CP 0                ; Verifica se letra é byte de terminação (valor 0)
100         JR NZ,INICIO        ; Se não for, continua
110         RET                 ; Retorna ao programa chamador
120 NOME:   DEFM "O MSX Vive"
130         DEFB 0
  Gravar: GB "frase.bin",&hD000,&hD01B. Deve-se considerar a frase e o byte finalizador na conta dos bytes.

  Dica: Podemos simular esse programa no RSCII, usando o comando SI &HD000, de forma a observar o comportamento dele.
  De forma a simplificar a simulação, substitua a frase "O MSX vive" por "MSX".
  Execute todas as linhas, pressionando a tecla "E", exceto a linha 100 (CALL WRTVRM), pressionando a tecla "S" para saltar.

  Reescrever o código acima utilizando a sub-rotina LDIRVM.
10  LDIRVM: EQU &H5C
20          ORG &HD000
30          LD DE,0             ; "Variável" com o endereço da VRAM
40          LD HL,NOME          ; "Variável" com o endereço inicial da frase na RAM
50          LD BC,10		; Comprimento da string
60          CALL LDIRVM         ; Chama sub-rotina de escrita na tela
70          RET                 ; Retorna ao programa chamador
80  NOME:   DEFM "O MSX Vive"



  Referências:

  [1] - MSX Bios - The Complete MSX Basic I/O Listing, Ed. Qest Publishing, 1985.
  [2] - Assembler para o MSX, José Eduardo M. Carvalho, Ed. McGraw Hill, 1987.
  [3] - O Livro Vermelho do MSX, Avalon Software, editora McGraw Hill.


<< Anterior Assembly Próxima >>