Curso de Basic
Basic com Assembly
Você está em: MarMSX >> Cursos >> BASIC
A linguagem Basic é interpretada, ou seja, a cada instrução, o interpretador Basic deve traduzir o código em Basic para a linguagem de máquina e então executar, tornando a execução de um programa bem mais lenta que se fosse direta em Linguagem de Máquina.
Vejamos a seguinte comparação: dois programas que preenchem toda a tela da screen 0 com o caractere "A", um feito em Basic, o outro em Assembly.
Programa em Basic:
10 TIME=0
20 FOR I=0 TO 959
30 VPOKE I,&H41
40 NEXT
50 PRINT TIME/60
Tempo gasto para escrever: 3.42 segundos.
Programa em Assembly:
10 FOR E=&HC000 TO &HC00B
20 READ A$
30 POKE E,VAL("&H"+A$)
40 NEXT
50 DEFUSR=&HC000
60 TIME=0
70 X=USR(0)
80 PRINT TIME/60
90 END
100 DATA 3E,41,21,00,00,01,C0,03,CD,56,00,C9
Tempo gasto para escrever: 0.017 segundos.
Código fonte em Assembly (RSCII):
Endereço Assembly Linha Mnemônico
0056 10 FILVRM: EQU &H56
C000 20 ORG &HC000
C000 3E41 30 LD A,&H41
C002 210000 40 LD HL,0
C005 01C003 50 LD BC,&H3C0
C008 CD5600 50 CALL FILVRM
C00B C9 60 RET
O programa em Assembly foi 200 vezes mais rápido do que o seu correspondente em Basic.
O objetivo desse capítulo é mostrar como chamar rotinas em Linguagem de Máquina a partir do Basic, de forma a acelerar os programas ou, até mesmo, utilizar recursos que são possíveis somente em Linguagem de Máquina.
Conforme pode ser visto na seção Dicas dos leitores da revista Micro Sistemas, muitos programadores utilizavam o recurso de escrever partes críticas de um programa em Linguagem de Máquina.
Preparando a área do programa
Antes de colocarmos nosso programa na memória do MSX, temos que conhecer a RAM ocupada pelo Basic e as variáveis de sistema, de forma a não termos conflitos de endereços durante a execução de um programa Basic com rotinas em Assembly.
Mapa da RAM do MSX:
8000H ┌──────────────────┐ TXTTAB - 8001H
│ Área do programa │
│ em Basic │
├──────────────────┤ VARTAB - 8003H
│ Área de │
│ variáveis │
├──────────────────┤ ARYTAB - 8003H
│ Área de │
│ matrizes │
├──────────────────┤ STREND - 8003H
│ Área │
│ livre │
├──────────────────┤
│ Área de │
│ stack │
├──────────────────┤ STKTOP - F0A0H
│ Área de │
│ string │
├──────────────────┤ MEMSIZ - F168H
│ Bloco de contr. │ FRETOP - F168H
│ de arquivo │
F380H ├──────────────────┤
│ Área do │
│ sistema │ HIMEM - F380H
FFFFH └──────────────────┘
Descrição das áreas:
- Área do programa: área onde reside o programa em Basic.
- Área de variáveis: armazena variáveis numéricas e os ponteiros para as strings.
- Área livre: área não usada.
- Área de stack: área de pilha utilizada pelas funções FOR-NEXT e GOSUB-RETURN.
- Área de string: área utilizada para armazenar strings. A instrução CLEAR define o tamanho dessa área.
- Bloco de controle de arquivos: área utilizada para E/S de arquivos. O tamanho é determinado pela instrução MAXFILES.
As variáveis apresentadas no gráfico anterior são descritas a seguir.
Variável |
Endereço |
Valor inicial |
Descrição |
TXTTAB |
F676H |
8001H |
Contém o endereço do primeiro byte da área de programa. |
VARTAB |
F6C2H |
8003H |
Contém o endereço do primeiro byte da área de variáveis. |
ARYTAB |
F6C4H |
8003H |
Contém o endereço do primeiro byte da área de matrizes. |
STREND |
F6C6H |
8003H |
Contém o próximo endereço livre ao final da área de matrizes. |
STKTOP |
F674H |
F0A0H |
Contém o endereço do topo da pilha do Z80. |
MEMSIZ |
F672H |
F168H |
Contém o endereço do topo da área de strings. |
FRETOP |
F69BH |
F168H |
Contém o primeiro endereço livre após a área de strings. |
HIMEM |
FC4AH |
F380H |
Contém o primeiro endereço após o final da RAM utilizada pelo interpretador Basic. |
Obs: os valores iniciais de algumas dessas variáveis muda quando há um disk-drive acoplado ao sistema.
Exemplo de uso das variáveis em um MSX 1 com disk-drive:
10 DEF FN HX$(E)=RIGHT$("00"+HEX$(PEEK(E+1)),2)+RIGHT$("00"+HEX$(PEEK(E)),2)
20 PRINT"TXTTAB: ";FN HX$(&HF676)
30 PRINT"STKTOP: ";FN HX$(&HF674)
40 PRINT"HIMEM : ";FN HX$(&HFC4A)
Saída:
TXTTAB: 8001
STKTOP: DB97
HIMEM : DE77
A inclusão de um disk-drive diminui consideravelmente a área total do Basic.
O comando CLEAR poderá ser utilizado para "proteger" a área do programa em Linguagem de Máquina, uma vez que ele define a área ocupada por todo o programa Basic.
Sintaxe:
CLEAR tamanho_da_area_de_caracteres, endereco_da_RAMTOP
O comando CLEAR faz:
- Inicializa todas as variáveis.
- Estabelece o tamanho da área de caracteres.
- Define o HIMEM.
Exemplo:
CLEAR 200,&HD000
Define o HIMEM para &HD000, ficando a área de D000H a F379H livre para um programa em Linguagem de Máquina. Observa-se que os endereços de F380H a FD99H reside a área de trabalho do MSX, onde são armazenadas as variáveis utilizadas pelo interpretador Basic e a BIOS.
Se o seu programa em Basic não for muito grande, ele poderá ser armazenado sem maiores problemas na "área livre" do Basic, como por exemplo a partir do endereço &HC000.
A necessidade de proteção surge quando há possibilidade de conflitos de endereçamento, como por exemplo um programa Basic grande ou com bastante variáveis e strings. Definindo o endereço do programa em &HC000, sobram 16383 bytes para o código do programa mais as variáveis.
Carregando e executando um programa em LM na memória
Um programa em Linguagem de Máquina é carregado na memória RAM do MSX, byte a byte, através da instrução POKE.
Sintaxe dos comandos de E/S para a memória RAM:
POKE endereco, dado ' Escreve dado na RAM
PEEK (endereco) ' Lê dado da RAM
Seja o seguinte programa em Assembly, que limpa a tela.
Endereço Assembly Linha Mnemônico
00C3 10 CLS: EQU &HC3
C000 20 ORG &HC000
C000 CDC300 30 CALL CLS
C003 C9 40 RET
Ele poderá ser carregado na memória assim:
10 POKE &HC000,&HCD
20 POKE &HC001,&HC3
30 POKE &HC002,&H00
40 POKE &HC003,&HC9
Ou assim:
10 FOR E=&HC000 TO &HC003
20 READ A$
30 POKE E,VAL("&H"+A$)
40 NEXT
50 DATA CD,C3,00,C9
Escrevemos o programa na memória, mas como chamá-lo? É isso que fazem os comandos DEFUSR e USR. O primeiro define o endereço inicial de uma sub-rotina em Linguagem de Máquina, enquanto que o segundo executa a sub-rotina.
Sintaxe:
DEFUSRn = endereco
DEFUSR permite armazenar até 10 endereços de execução de sub-rotinas em linguagem de máquina (0-9), através da variável "n". Se "n" for omitido, assume-se o valor igual a 0.
Exemplos:
DEFUSR=&HC000
DEFUSR1=&HC010
Sintaxe:
valor_recebido = USRn(valor_enviado)
O valor de "n" corresponde ao endereço definido por DEFUSR, o valor_enviado é uma valor passado para a sub-rotina e o valor_recebido é o valor devolvido pela sub-rotina.
Para executar o programa que limpa a tela, digitamos:
DEFUSR = &HC000 ↵
X=USR(0) ↵
O valor_enviado pode ser qualquer valor, quando a sub-rotina não recebe dados. Entretanto, se receber, o valor enviado será lido pela sub-rotina da seguinte maneira [2]:
Tipo enviado Registro A Registro HL Local dos dados
-------------------------------------------------------
inteiro 8 &HF7F6 &HF7F6-&HF7FD
precisão simples 4 &HF7F6 &HF7F6-&HF7F9
dupla precisão 2 &HF7F6 &HF7F8-&HF7F9
O registro A indica o tipo de dado e o registro HL o endereço inicial do dado, exceto para o número inteiro, que deverá ser incrementado de 2.
Tipo enviado Registro A Registro DE
---------------------------------------------
string 3 End. do ponteiro
p/ string
O registro A indica o tipo de dado e o registro DE o endereço do bloco de informações da string, que é apresentado a seguir:
OFFSET Tamanho Dado
00 1 Comprimento da string
01 2 Local da string
Importante: o tipo de retorno é o mesmo tipo de envio.
Exemplo para um tipo de envio/retorno inteiro: um programa em LM que dobra o valor passado.
Endereço Assembly Linha Mnemônico
C000 10 ORG &HC000
C000 23 20 INC HL ; Posiciona HL em
C001 23 30 INC HL ; &HF7F8
C002 7E 40 LD A,(HL) ; Lê o valor passado
C003 87 50 ADD A,A ; Dobra o valor
C004 77 60 LD (HL),A ; Armazena o resultado
C005 C9 100 RET
Programa em Basic para testar:
10 FOR E=&HC000 TO &HC005
20 READ A$
30 POKE E,VAL("&H"+A$)
40 NEXT
50 DEFUSR=&HC000
60 PRINT USR(4)
100 DATA 23,23,7E,87,77,C9
Saída:
8
Agora vamos ver um exemplo usando string [3].
O programa a seguir recebe uma string e aplica uma rotação de 1 posição na tabela ASCII. Por exemplo, "ABC" fica "BCD".
Endereço Assembly Linha Mnemônico
C000 10 ORG &HC000
C000 FE03 20 CP 3 ; Verifica se A é uma string
C002 C0 30 RET NZ ; Retorna se não for
C003 1A 40 LD A,(DE) ; Lê o número de caracteres
C004 B7 50 OR A
C005 C8 60 RET Z ; Retorna se num. caract. for 0
C006 47 70 LD B,A ; Armazena em B o compr. da string
C007 EB 80 EX DE,HL ; Troca conteúdo de DE e HL
C008 23 90 INC HL
C009 5E 100 LD E,(HL) ; Grava em DE o endereço da string
C00A 23 110 INC HL
C00B 56 120 LD D,(HL)
C00C 1A 130 LOOP: LD A,(DE) ; Carrega caractere
C00D 3C 140 INC A ; Soma 1 ao código ASCII
C00E 12 150 LD (DE),A ; Salva caractere modificado
C00F 13 160 INC DE
C010 10FA 170 DJNZ LOOP
C012 C9 180 RET
Programa em Basic para testar:
10 FOR E=&HC000 TO &HC012
20 READ A$
30 POKE E,VAL("&H"+A$)
40 NEXT
50 DEFUSR=&HC000
60 PRINT USR("marmsx")
100 DATA FE,03,C0,1A,B7,C8,47,EB,23,5E,
23,56,1A,3C,12,13,10,FA,C9
Saída:
nbsnty
Referências:
[1] - Linguagem Basic MSX, editora Aleph, 5a. Edição, 1987.
[2] - O Livro Vermelho do MSX, Avalon Software, editora McGraw Hill.
[3] - Adaptado do programa "Inverse no MSX", publicado na Micro Sistemas.