ASM – Tutorial 4 – Adam Hyde

Tutorial de Assembler de Adam Hyde 1.0
PARTE 4
Traduzido por Renato Nunes Bastos

Versão:  1.3
Data: 01-03-1996 /online by Renato 01-11-1998
Contato: blackcat@vale.faroc.com.au
http://www.faroc.com.au/~blackcat
;Renato: Contato
http://www.geocities.com/SiliconValley/Park/3174 (meu site antigo, agora está fora do ar)
http://www.krull.com.br


Exemplo de Comparação | Shifts
/ Aritmética Hexadecima e Binária
| Programado a VGA em Assembler


Bem-vindos mais uma vez, florescentes programadores Assembler. Os tutoriais parecem estar ficando populares agora, e eu tenho recebido e-mails pedindo-me para falar sobre o VGA, então eu vou dar olhada. Isso é basicamente para onde eu tenho conduzido no meu modo desconjuntado, de qualquer modo, já que programação gráfica não é só recompensante, é divertido também! Bem, eu acho que é.  🙂

Primeiramente porém, devemos terminar aquela coisa de CMP/JMP, e falar de shifts. Quando se está programando em Assembler, a gente acha que comparações, shifts e testar bits são operações muito comuns.


Um Exemplo de Comparação

 

Eu não vou perder tempo explicando minuciosamente o seguinte exemplo – ele é muito
fácil de entender e você deve pegar a idéia basica seja lá como for.

DOSSEG
.MODEL SMALL
.STACK 200h
.DATA

FirstString    DB  13, 10, "Este é um grande tutorial ou o quê? :) - $"
SecondString   DB  13, 10, "NÃO? NÃO? O que você quer dizer, NÃO?$"
ThirdString    DB  13, 10, "Excelente, vamos ouvir você dizer isso de novo.$"
FourthString   DB  13, 10, "Apenas um Y ou N já basta.$"
ExitString     DB  13, 10, "Bem, deixa pra lá!$"

.CODE

START:
  MOV   AX, @DATA ; Novo modo de dizer:
  MOV   DS, AX ; DS -> SEG segmento de dados

KeepOnGoing:
  MOV   AH, 9
  MOV   DX, OFFSET FirstString             ; DX -> OFFSET FirstString
  INT 21h                                  ; Escreve a primeira mensagem
  MOV AH, 0                                ; Pega uma tecla - armazena-a em AX
  INT 16h                                  ; AL - código ASCII, AH - "scan code"
                                           ; Ela não sai (ecoa) na tela, ou seja,
                                           ; nós mesmos temos que fazer isso.
  PUSH AX                                  ; Aqui nós mostramos na tela o caracter
  MOV DL, AL 				   ; note que nós salvamos AX. Obviamente,
  MOV AH, 2 				   ; usando-se AH para imprimir uma string
  INT 21h 				   ; destrói-se AX
  POP AX
  CMP AL, "Y" 				   ; Checa se foi teclado 'Y'
  JNE HatesTute 			   ; Se foi, continua
  MOV AH, 9 				   ; Mostra a mensagem "Excelente..."
  MOV DX, OFFSET ThirdString
  INT 21h
  JMP KeepOnGoing 			   ; Volta ao início e começa de novo

HatesTute:
  CMP AL, "N"                  ; Certifica que foi teclado 'N'
  JE DontLikeYou               ; Infelizmente, sim.
  MOV DX, OFFSET FourthString  ; Pede ao usuário para tentar de novo
  MOV AH, 9
  INT 21h
  JMP KeepOnGoing              ; Deixa ele tentar

DontLikeYou:
  MOV DX, OFFSET SecondString ; Mostra a string "NÃO? NÃO? O que..."
  MOV AH, 9
  INT 21h
  MOV DX, OFFSET ExitString   ; Mostra a string "Bem, deixa pra lá!"
  MOV AH, 9
  INT 21h
  MOV AX, 4C00h                ; Volta para o DOS
  INT 21h

END START

Você deveria entender este exemplo, brincar um pouco com ele e escrever algo melhor.
Aqueles que tiverem o livro do Peter Norton ou algo semelhante, experimentem as subfunções do teclado, e veja quais outras combinações de GetKey existem, ou melhor ainda, brinque com a interrupção 10h e entre em algum modo de vídeo sobrenatural – um que seu PC suporte! – e use algumas cores.


Shifts

Esse é um conceito smples, e que eu já devia ter discutido antes, mas como eu disse – eu tenho minha própria maneira “desconjuntada” de fazer as coisas.

Primeiro você vai precisar de entender um pouco de aritmética hexadecimal e binária – um assunto que eu _deveria_ ter coberto antes. Eu geralmente uso uma calculadora científica – ei, eu sempre uso uma calculadora, eu não sou estúpido! – mas é bom ser capaz de saber como multiplicar, somar e converter entre as várias bases. Você também não pode usar uma calculadora em provas de Computação, não na Austrália.

CONVERTENDO DE BINÁRIO PARA DECIMAL:

De Volta ao Tutorial Um, nós vimos como é que números binários se parecem, então imagine que eu tenha um número binário de oito dígitos, como:

11001101

O que é isso em decimal??? Há várias formas de converter tal número, e eu uso a
seguinte, que acredito se provavelmente a mais fácil:

 

Número Binário 1 1 0 0 1 1 0 1
Equivalente Decimal 27 26 25 24 23 22 21 20
128 64 32 16 8 4 2 1
Valor Decimal

128+64+0+0+8+4+0+1=

205

 

Pegou a idéia? Note que para a última linha, seria mais preciso escrever:

1 x 128 + 1 x 64 + 0 x 32 + 0 x 16 + 1 x 8 + 1 x 4 + 0 x 2 + 1 x 1
=     128 +     64 + 0 +      0 + 8 +     4 +     0 + 1
= 205

Desculpe se isto é um pouco confuso, mas é difícil explicar sem demonstrar.

Aqui vai outro exemplo:

 

Número Binário 0 1 1 1 1 1 0 0
Equivalente Decimal 27 26 25 24 23 22 21 20
128 64 32 16 8 4 2 1
Valor Decimal

0+64+32+16+8+4+0+0=

124

 

Obs.:

  • Você pode usar esta técnica com palavras de 16 ou 32 bits também, apenas faça do
    jeito certo. Ex: Depois de 128, você escreveria 256, depois 512, 1024 e assim por diante.
  • Você pode dizer se o equivalente decimal ser par ou ímpar pelo primeiro bit.
    Ex.: No exemplo acima, o primeirobit = 0, então o número é PAR. No primeiro exemplo, o primeiro bit é 1, então o
    número é ÍMPAR.

FATO ENGRAÇADO: Caso você não saiba ainda, a palavra “bit” vem de Binary digIT.  🙂

CONVERTENDO DE DECIMAL PARA BINÁRIO:

Isso é provavelmente mais fácil que da base-2 para base-10. Para calcular o que 321 seria em binário, você faria o seguinte:

321                =     256  X  1
321 - 256 = 65     =     128  X  0
65                 =      64  X  1
65  -  64   =  1   =      32  X  0
1                  =      16  X  0
1                  =       8  X  0
1                  =       4  X  0
1                  =       2  X  0
1                  =       1  X  1

E você obteria o número binário – 101000001. Fácil, né?  Vamos tentar outro para ter certeza que sabemos fazer:

198                =     128  X 1
198 - 128 = 70     =      64  X 1
70  -  64 =  6     =      32  X 0
6                  =      16  X 0
6                  =       8  X 0
6                  =       4  X 1
6   -   4 =  2     =       2  X 1
2   -   2 =  0     =       1  X 0

E isto nos dá  – 11000110.  Note como você pode checar o primeiro dígito para ver se você conseguiu sua conversão certa. Quando eu escrevi o primeiro exemplo, eu notei que eu fiz um erro quando eu chequei o primeiro bit. No primeiro exemplo, eu consegui 0 – não muito bom para um número ímpar. Eu entendi o erro e corrigi o exemplo.

CONVERTENDO DE HEXADECIMAL PARA DECIMAL:

Antes de começar, você deveria saber que o sistema numérico hexadecimal usa os
seguintes ‘dígitos’:

0         =  0 (decimal)    =     0 (binário)
1         =  1 (decimal)    =     1 (binário)
2         =  2 (decimal)    =    10 (binário)
3         =  3 (decimal)    =    11 (binário)
4         =  4 (decimal)    =   100 (binário)
5         =  5 (decimal)    =   101 (binário)
6         =  6 (decimal)    =   110 (binário)
7         =  7 (decimal)    =   111 (binário)
8         =  8 (decimal)    =  1000 (binário)
9         =  9 (decimal)    =  1001 (binário)
A         = 10 (decimal)    =  1010 (binário)
B         = 11 (decimal)    =  1011 (binário)
C         = 12 (decimal)    =  1100 (binário)
D         = 13 (decimal)    =  1101 (binário)
E         = 14 (decimal)    =  1110 (binário)
F         = 15 (decimal)    =  1111 (binário)

Você vai comumente ouvir “hexadecimal” sendo chamado de “hex”, ou “base-16”, e ela é comumente denotada por um ‘h’ – ex.: 4C00h, ou um ‘$’, ex.: – $B800.

Trabalhar com hexadecimal não é tão difícil como pode parecer, e converter pra lá ou pra cá é bem fácil. Como exemplo, vamos converter B800h para decimal:

FATO ENGRAÇADO: B800h é o endereço inicial do vídeo em modo texto para CGA e placas superiores.  🙂

B    = 4096 x B =  4096 x 11  = 45056
8    =  256 x 8 =  256 x  8   =  2048
0    =   16 x 0 =   16 x  0   =     0
0    =    1 x 0 =    1 x  0   =     0

Logo B800h = 45056 + 2048 + 0 +0 = 47104

Obs.:  Para números em hexadecimal maiores que FFFFh (65535 em decimal), você somente segue o mesmo procedimento como para binário, logo, para o quinto dígito hexadecimal, você multiplicaria por 65535.

Tecle 16 X X na sua calculadora, e fique apertando =.
Você verá os números que precisaria usar. O mesmo aplica-se
para binário. Ex.:  2 X X e = lhe daria 1, 2, 4, 8, 16... etc.

OK, isso pareceu bem fácil. Eu acho que nem precisamos de um segundo exemplo.
Vamos dar uma olhada em:

CONVERTENDO DE DECIMAL PARA HEXADECIMAL:

Mais uma vez, o mesmo tipo de procedimento como usamos para binário. Logo, para converter 32753 para hexadecimal, você faria assim:

32753 / 4096       =    7 (decimal) = 7h
32753 - (4096 x 7) = 4081
4081 /  256        =   15 (decimal) = Fh
4081 - (256 x 15)  =  241
241 / 16           =   15 (decimal) = Fh
241 - (16 x 15)    =    1
1 / 1              =    1 (decimal) = 1h

Assim, eventualmente temos 7FF1h como resposta. Este não é particularmente um bom
processo e requer alguma explicação.

  1. Quando você divide 32753 por 4096 você consegue 7.9963379… Não estamos interessados no lixo .9963379, só pegamos o 7, já que 7 é o maior número inteiro que podemos usar.
  2. O resto da operação acima é 4081. Devemos agora realizar a mesma operação nisso, mas com 256.  Dividindo 4081 por 256 nos dá 15.941406… Novamente, pegamos só o 15.
  3. Agora temos um resto de 241.  Dividindo isto por 16 nos dá 15.0625. Pegamos o 15, e calculamos o resto.
  4. Nosso último resto acontece que é um. Dividindo isso por um chegamos a, você advinhou – um.  VOCÊ NÃO DEVERIA CONSEGUIR UMA RESPOSTA COM MUITAS CASAS DECIMAIS AQUI. SE VOCÊ TEM,  CALCULOU ERRADO!!!

É um processo muito imundo, mas funciona.  Eu não uso isso, exceto quando eu tenho que usar – eu não sou maluco. Eu uso uma calculadora científica, ou a calculadora do Windows <brrrrr> se eu precisar.


OK, agora que já lidamos com os cálculos horripilantes, você já está pronto para
os shifts. Há geralmente 2 formas da instrução shift – SHL (shift left/esquerda) e SHR
(shift right/direita). Basicamente, tudo o que essas instruções fazem é deslocar uma expressão para a esquerda ou direita um certo número de bits. Sua principal vantagem é a habilidade de lhe deixar substituir multiplicações lentas com shifts mais rápidos. Você vai descobrir que isso acelera pra caramba os algoritmos de pixel/linhas/círculo.

Os PC’s estão ficando cada vez mais rápidos a cada dia – um pouco rápido demais pro meu gosto. De volta aos dias do XT – a multiplicação era _realmente_ lenta – talvez levando atá 4 segundos para certas operações. Hoje em dia isso não acontece assim, mas é uma boa idéia otimizar seu código.

Quando nós plotamos um pixel na tela, temos que encontar o offset do pixel a plotar. Basicamente, o que fazemos é multiplicar a posição Y por 320, somar a posição X, e somar isso ao endereço A000h.

Assim basicamente, temos:   A000:Yx320+X

Agora, seja lá quão rápido seu maravilhoso 486 ou Pentium é, isso poderia se feito um pouco mais rápido. Vamos reescrever aquela equação acima, assim, vamos usar alguns números diferentes:

Offset = Y x 28   +  Y x 26  + X

Ou:

Offset = Y x 256 +  y x 64 + X

Reconhece esses números? Eles parecem terrivelmente com aqueles que nós vimos naquela tabela de conversão binário-decimal. Contudo, nós ainda estamos usando multiplicação. Como podemos incorporar shifts?

Que tal:

Offset = Y SHL 8 + Y SHL 6 + X

Agora, isso é muito mais rápido, já que tudo o que o computador tem que fazer é um shift à esquerda com o número – muito melhor. Note que o shift à esquerda AUMENTA o número, e o shift à direita DIMINUI o número.

Aqui está um exemplo que pode te ajudar se você ainda está em dúvida no que está acontecendo. Digamos que estamos trabalhando em base-10 – decimal. Agora peguemos o número 36 como exemplo. “Shiftando” este número à esquerda de 1, temos:

36  +  36 = 72

Agora SHL 2:

36  +  36  +  36  +  36 = 144

E SHL 3:

36 +  36   +  36  +  36  +  36  + 36  +  36  +  36   = 288

Notou os núeros que se formaram? Havia 2 36’s com SHL 1, 4 36’s com SHL 2 e 8 36’s com SHL 3. Seguindo este padrão, seria justo assumir que 36 SHL 4 equivalerá a 36 x 16.

Note porém, o que está realmente acontecendo. Se você fosse trabalhar com o valor binário de 36, que é mais ou menos isso: 100100, e então shiftasse 36 à esquerda de 2, você teria 144, ou 10010000. Tudo o que a CPU faz na verdade é colocar alguns 1’s e 0’s extras na posição de memória.

Como outro exemplo, pegue o número binário 1000101. Se fizermos um shift à esquerda de 3, terminaríamos com:

      1 0 0 0 1 0 1
      <---------- SHL 3
1 0 0 0 1 0 1 0 0 0

Agora vamos deslocar o número 45 à DIREITA de 2 unidades. Em binário isso é 101101. De onde:

    1 0 1 1 0 1
SHR 2 ---->
        1 0 1 1

Notou o que ocorreu? É muito mais fácil para a CPU apenas mover alguns bits
(aproximadamente 2 unidades de clock), do que multiplicar um número. (Pode demorar  até 133 unidades de clock).

Nós vamos usar bastante shifts quando estivermos programando a VGA, assim, tenha
certeza de que você entendeu os conceitos por trás disso.


 

PROGRAMANDO A VGA
EM ASSEMBLER  

 

Eu tenho recebido um monte de mails me pedindo para cobrir a VGA. Então, para todos os que pediram, nós estaremos gastando a maior parte do nosso tempo, mas não todo, em programar a VGA. Além do mais, não querem todos programar com gráficos?

Quando nós falamos sobre programar a VGA, nós estamos geralmente falando do modo 13h, ou um de seus parentes. Em VGA padrão este é o _único_ modo de usar 256 cores, e é provavelmente um dos modos mais fáceis também. Se você já tentou experiências com a SVGA, você vai entender o pesadelo que é para o programador dar suporte a todas as diferentes placas SVGA que existem – exceto se você usar VESA que é o que discutiremos outra hora. A grande vantagem do modo padrão 13h é que você sabe que todas as placas VGA que existem vão suportá-lo. As pessoas hoje frequentemente ignoram o modo 13h, achando a resolução muito granulada para os padrões de hoje, mas não se esqueça que Duke Nukem, DOOM, DOOM II, Halloween Harry e a maioria dos jogos da Apogee usam este modo para realizar alguns grandes efeitos.

A grande coisa sobre o modo 13h – isto é 320x200x256 caso você desconheça, é que acessar a VGA RAM é incrivelmente fácil. Como 320 x 200 é igual a  64,000, é possível encaixar a tela inteira em um segmento de 64K.

As más notícias são que o modo padrão 13h realmente só te dá uma página para usar, seriamente embaraçante para scroll e page-flipping. nós vamos cobrir mais tarde estes assuntos, como entrar em seus próprios modos – e modo X que evitará esses problemas.

Então, como entrar no modo padrão 13h?

A resposta é simples. Usamos a interrupção 10h – interrupção de vídeo, e chamamos a subfunção 00h – seleciona o modo. Em Pascal, você poderia declarar uma procedure como esta:

Procedure Init300x200;   Assembler;
Asm                   { Init300x200 }
mov   ah, 00h         { Acerta o modo de vídeo }
mov   al, 13h         { Usa o modo 13h }
int   10h             { Faz isso }
End;                  { Init300x200 }

você também pode ver:

mov   ax, 13h
int   10h

Isso é perfeitamente correto, e provavelmente economiza um tempo de clock por não colocar 00h em AH e então 13h em AL, mas é mais correto usar o primeiro exemplo.

OK, então estamos no modo 13h, mas o que podemos realmente fazer nele, além de olhar para uma tela em branco? Poderíamos voltar ao modo texto usando:

mov   ah, 00h
mov   al, 03h
int   10h

Mas isso é um pouco idiota. Porque não pintar um pixel?


Há inúmeros modos de colocar um pixel na tela. O modo mais fácil em Assembler é usar interrupções. Você faria mais ou menos assim em Pascal:

Procedure PutPixel(X, Y : Integer; Color : Byte);   Assembler;
Asm     { PutPixel }
mov   ah, 0Ch        { subfunção de desenhar pixel   }
mov   al, [Color]    { Move a cor a plotar para AL   }
mov   cx, [X]        { Move o valor X para CX        }
mov   dx, [Y]        { Move o valor Y para DX        }
mov   bx, 1h         { BX = 1, p gina 1              }
int   10h            { Plota }
End;                 { PutPixel }

Contudo, mesmo isso sendo em Assembler, não é particularmente rápido. “Por quê?”, você pergunta. Porque isso usa interrupção. Interrupções são ótimas para entar e sair de modos de vídeo, ligar e desligar o cursor, etc… mas não para gráficos.

Você pode imaginar interrupções como uma secretária eletrônica. “A CPU está ocupada neste momento, mas se você deixar sua subfunção após o sinal – nós entraremos em contato.”

Isso não é bom.

Vamos usar a técnica que discutimos anteriormente durante shifts. O que queremos fazer é botar o valor da cor que desejamor plotar na VGA diretamente. Para fazer isso,  precisamos mover o endereço da VGA para ES, e calcular o offset do pixel que queremos plotar. Um exemplo disso é mostrado abaixo:

Procedure PutPixel(X, Y : Integer; Color : Byte);   Assembler;
Asm                { PutPixel }
mov   ax, 0A000h   { Move o segmento da VGA para AX, }
mov   es, ax       { e agora para ES                 }
mov   bx, [X]      { Move o valor X para BX          }
mov   dx, [Y]      { Move o valor Y para DX          }
mov   di, bx       { Move X para DI }
mov   bx, dx       { Move Y para BX }
shl   dx, 8        { Nesta parte usamos shifts para multiplicar }
shl   bx, 6        { Y por 320 }
add   dx, bx       { Agora somamos X ao valor acima calculado, }
add   di, dx       { dando DI = Y x 320 +  X   }
mov   al, [Color]  { Põe a cor a plotar em AL  }
stosb              { Põe o byte, AL, em ES:DI  }
End;               { PutPixel }

Esta procedure é rápida o suficiente para começar, embora eu tenha dado uma muito mais rápida uns tutoriais atrás que usa uma técnica genial para pegar DI.


OK, acho que é o suficiente para essa semana. Brinque com as rotinas de PutPixel e veja o que você pode fazer com elas. Para aqueles com um livro do Peter Norton, veja que outros procedimentos você pode fazer usando interrupções.

COISAS PARA FAZER:

  1. Cobrimos muita coisa nesse tutorial, e alguns conceitos importantes estão nele. Certifique-se de estar comfortável com comparações, porque vamos começar a testar bits em breve.
  2. Tenha certeza que entendeu aquela coisa de binário ->decimal, decimal -> binário, decimal -> hex e hex -> decimal. Faça você mesmo alguns exemplos de soma e teste suas respostas com a calculadora do Windows.
  3. Você  tem que entender shifts. Se você ainda tem problemas, faça algumas expressões num papel e teste suas respostas num programa como:
Begin      { Main }
  WriteLn(45 SHL 6);
  ReadLn;
End.       { Main }

e/ou a calculadora do Windows.

  1. Dê uma olhada na parte de VGA, e certifique-se de ter pego a teoria por trás disso, porque na próxima semana vamos entrar a fundo nisso.

Semana que vem vou tentar colocar alguns exemplos em C/C++ além de Pascal para vocês programadores de C aí fora.


No próximo tutorial vamos ver:

  • Como a VGA é arrumada
  • Como podemos desenhar linas e círculos
  • Pegando e acertando a palette em Assembler
  • Fades
  • Alguns exemplos em C/C++

Se você deseja ver um tópico discutido num tutorial no futuro, escreva-me, e eu vou
ver o que eu posso fazer.


Não perca!!! Baixe o tutorial da próxima semana na minha homepage


Vejo vocês na próxima semana!

– Adam.
– Renato Nunes Bastos

“Eu _nunca_ escrevo código com bugs, eu apenas coloco algumas características a mais sem querer!”

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

 

A Nova Krull's HomePage