Curso Fudeba de ASM

 


Aula 6: Truques Matemáticos II - Muliplicações com o Z80.

  Na aula passada vimos de maneira detalhada a forma de imprimir números em hexadecinal. Juntamente com isso, vimos alguns conceitos importantes sobre valores binários, deslocamentos de bits e coisas deste tipo.
  Como foi possível ver, existiu uma conversão razoavelmente complexa entre a forma que o computador trabalha com os números para que pudéssemos ver o resultado na tela. Isso indica uma coisa: o computador lida com os números de uma forma bem diferente da nossa, e é melhor fugir dos números decimais na hora de implementar funções matemáticas no computador.
  Fundamentalmente, na maioria das vezes iremos implementar usando exatamente a mesma idéia que usamos nas contas decimais que fazemos à mão, mas sempre tomando o cuidado de adaptá-las para que funcionem em base binária... Evitando assim muita perda de tempo de processamento.
  Nesta aula vamos criar uma função interessante: MULUU. Esta função deve multiplicar um numero de 8 bits por outro de 8 bits, gerando um número de 16 bits. 
  Deixaremos a divisão para uma aula posterior, caso contrário essa aula seria literalmente interminável.

  Multiplicando 2 Números

  Como era de se esperar, o nosso programa base desta aula será baseado no programa final da aula 5. O esqueleto está reproduzido abaixo:

--- Cortar Aqui ---
BDOS	EQU	5
STROUT	EQU	9

START:
	; Mostra informacoes do programa
	LD	DE,NOMEDOPRG		; Indica texto do nome do programa
	CALL	MOSTRATXT		; Mostra texto
	LD	DE,AUTOR		; Indica texto do nome do autor
	CALL	MOSTRATXT		; Mostra texto

	JP	0			; Volta ao MSX-DOS

NOMEDOPRG:	DB	'Programa 6a - Multiplicando Numeros.',13,10,'$'
AUTOR:		DB	'  Por Daniel Caetano',13,10,10,'$'
PULALINHA:	DB	13,10,'$'


;------
; MOSTRATXT - Funcao que mostra um texto cuja sequencia e' terminada por '$'.
;    Entrada: DE - Aponta para sequencia a ser mostrada, terminada por '$'
;------
MOSTRATXT:
	LD	C,STROUT		; Indica funcao de mostrar texto do BDOS
	CALL	BDOS			; Manda o BDOS executar.
	RET				; Retorna

;------
; HEX - Converte um número na RAM para um texto hex terminado por '$'.
; Entrada: DE - Posição de memória onde o texto será colocado (3 bytes)
;          A  - Valor a ser "convertido"
;------
HEX:	LD	B,A		; Salva valor original
	SRL	A		; Pega valor do nibble superior
	SRL	A
	SRL	A
	SRL	A
	CALL	DIG2ASCII	; Converte valor de A para ASCII
	LD	(DE),A		; Guarda na memória
	INC	DE		; Aponta próxima posição de memória
	LD	A,B		; Recupera valor numérico original
	AND	00001111b	; Aplica máscara (isola nibble inferior)
	CALL	DIG2ASCII	; Converte valor de A para ASCII
	LD	(DE),A
	INC	DE		; Aponta próxima posição de memória
	LD	A,'$'
	LD	(DE),A
	RET


;------
; DIG2ASCII - Converte um valor de 0 a 15 para seu correspondente ASCII
; Entrada: A - Valor a ser convertido
; Saída:   A - Valor convertido
;------
DIG2ASCII:
	ADD	A,'0'		; Soma o valor ASCII de '0' indepentende do
				; valor original de A
	CP	'9'+1		; Verifica se o valor em A é menor que o
				; valor de '9' + 1 (ou seja, se é menor ou 
				; igual  a '9')
	RET	C		; Se sim, vai embora...!
	; Aqui é caso o valor seja maior... ou seja, A a F
	ADD	A,'A'-('0'+10)	; Corrige para um valor de A a F
	RET

;------
; HEXW - Converte um número na RAM para um texto hex terminado por '$'.
; Entrada: DE - Posição de memória onde o texto será colocado (5 bytes)
;          HL - Valor a ser "convertido"
; Modifica: A, DE
;------
HEXW:	LD	A,H
	CALL	HEX
	LD	A,L
	CALL	HEX
	RET


	END
--- Cortar Aqui ---

  A primeira coisa que podemos definir é que a nossa nova função terá a seguinte cara:

;------
; MULUU - Multiplica um número de 8 bits por outro de 8 bits (sem sinal)
; Entrada: L  - Multiplicando
;          A  - Multiplicador
; Saída:   A - Produto
;------
MULUU:
	RET

  Bem, pensemos um pouco. Como fazer um processador que só faz soma e subtração realizar uma multiplicação? A forma mais simples, baseada naquela que aprendemos quando crianças na escola, é fazer um monte de somas... Afinal, é correto afirmar que:

   5*1 = 1 + 1 + 1 + 1 + 1 = 5
   7*3 = 7 + 7 + 7 = 21

  Opa! Isso parece uma tarefa para aquela instrução bizarra da aula 4... o DJNZ! Para quem já se esqueceu, o DJNZ é uma instrução que significa "Decrement B and Jump if Not Zero". Se fizermos uma construção do tipo:

	LD	B,n
LOOP:	; Operações
	DJNZ LOOP

  As operações serão realizadas "n" vezes! Ora... se 7*3 = somar 7 por 3 vezes... Hum! Interessante. Implementando essa idéia, temos que colocar o valor de A em B, e ir somando L várias vezes... O que resulta em:

;------
; MULUU - Multiplica um número de 8 bits por outro de 8 bits (sem sinal)
; Entrada: L - Multiplicando
;          A - Multiplicador
; Saída:   A - Produto
;------
MULUU:
	LD	B,A
MUUL:	ADD	A,L
	DJNZ MUUL
	RET

 E pronto! No nosso programa teríamos:

--- Cortar Aqui ---
BDOS	EQU	5
STROUT	EQU	9

START:
	; Mostra informacoes do programa
	LD	DE,NOMEDOPRG		; Indica texto do nome do programa
	CALL	MOSTRATXT		; Mostra texto
	LD	DE,AUTOR		; Indica texto do nome do autor
	CALL	MOSTRATXT		; Mostra texto

	LD	L,010h			; Multiplicando
	LD	A,005h			; Multiplicador
	CALL	MULUU			; Chama multiplicacao (resultado em A)

	LD	DE,NUMERO		; Indica posição do texto
	CALL	HEX			; Converte para ASCII
	
	LD	DE,NUMERO		; Aponta número
	CALL	MOSTRATXT		; Imprime

	JP	0			; Volta ao MSX-DOS

NOMEDOPRG:	DB	'Programa 6a - Multiplicando Numeros.',13,10,'$'
AUTOR:		DB	'  Por Daniel Caetano',13,10,10,'$'
NUMERO:		DB	0,0,0
PULALINHA:	DB	13,10,'$'

;------
; MULUU - Multiplica um número de 8 bits por outro de 8 bits (sem sinal)
; Entrada: L  - Multiplicando
;          A  - Multiplicador
; Saída:   A - Produto
;------
MULUU:
	LD	B,A
MUUL:	ADD	A,L
	DJNZ MUUL
	RET

;------
; MOSTRATXT - Funcao que mostra um texto cuja sequencia e' terminada por '$'.
;    Entrada: DE - Aponta para sequencia a ser mostrada, terminada por '$'
;------
MOSTRATXT:
	LD	C,STROUT		; Indica funcao de mostrar texto do BDOS
	CALL	BDOS			; Manda o BDOS executar.
	RET				; Retorna

;------
; HEX - Converte um número na RAM para um texto hex terminado por '$'.
; Entrada: DE - Posição de memória onde o texto será colocado (3 bytes)
;          A  - Valor a ser "convertido"
;------
HEX:	LD	B,A		; Salva valor original
	SRL	A		; Pega valor do nibble superior
	SRL	A
	SRL	A
	SRL	A
	CALL	DIG2ASCII	; Converte valor de A para ASCII
	LD	(DE),A		; Guarda na memória
	INC	DE		; Aponta próxima posição de memória
	LD	A,B		; Recupera valor numérico original
	AND	00001111b	; Aplica máscara (isola nibble inferior)
	CALL	DIG2ASCII	; Converte valor de A para ASCII
	LD	(DE),A
	INC	DE		; Aponta próxima posição de memória
	LD	A,'$'
	LD	(DE),A
	RET


;------
; DIG2ASCII - Converte um valor de 0 a 15 para seu correspondente ASCII
; Entrada: A - Valor a ser convertido
; Saída:   A - Valor convertido
;------
DIG2ASCII:
	ADD	A,'0'		; Soma o valor ASCII de '0' indepentende do
				; valor original de A
	CP	'9'+1		; Verifica se o valor em A é menor que o
				; valor de '9' + 1 (ou seja, se é menor ou 
				; igual  a '9')
	RET	C		; Se sim, vai embora...!
	; Aqui é caso o valor seja maior... ou seja, A a F
	ADD	A,'A'-('0'+10)	; Corrige para um valor de A a F
	RET

;------
; HEXW - Converte um número na RAM para um texto hex terminado por '$'.
; Entrada: DE - Posição de memória onde o texto será colocado (5 bytes)
;          HL - Valor a ser "convertido"
; Modifica: A, DE
;------
HEXW:	LD	A,H
	CALL	HEX
	LD	A,L
	CALL	HEX
	RET

	END
--- Cortar Aqui ---

  Entretanto, ao rodar esse programa... Algo estranho aconteceu. O número impresso nada tem a ver com a resposta esperada. O programa nos retornou o valor 55h, quando bem sabemos que 5h * 10h = 50h! O que está errado?!
  Analisemos com calma aquilo que a função está fazendo: Colocamos o número de repetições em B e somamos B vezes L em A... opa! Estamos somando o valor em A, antes de ter zerado o valor de A... então, na realidade, fizemos 5h * 10h + 5h = 55h! Isso pode ser corrigido então, usando a seguinte modificação:

;------
; MULUU - Multiplica um número de 8 bits por outro de 8 bits (sem sinal)
; Entrada: L  - Multiplicando
;          A  - Multiplicador
; Saída:   A - Produto
;------
MULUU:
	LD	B,A
	LD	A,0
MUUL:	ADD	A,L
	DJNZ MUUL
	RET

  E pronto! A vai conter o resultado da multiplicação na saída da função, e o programa imprime corretamente o valor 50h. Entretanto, basta substituir:

	LD	L,010h			; Multiplicando
	LD	A,005h			; Multiplicador

  Por algo como:

	LD	L,082h			; Multiplicando
	LD	A,005h			; Multiplicador

  Que a nossa alegria vai para o vinagre. O programa passa a responder 8A, quando deveria responder 28A. O que está errado? Certamente a nossa concepção do problema. Observe que o valor máximo possível de se guardar em um registrador de 8 bits é FFh. 28Ah é um número bem superior a FFh.
  A solução então é para isso usar um registrador de mais bits. Quantos? Bem, se estamos multiplicando dois números de 8 bits... temos, por uma regra matemática simples, que:

	(2^8 * 2^8) = 2^(8+8) = 2^16

  Ou seja, basta um número de 16 bits que conseguiremos, com sucesso, armazenar qualquer resultado de uma multiplicação entre dois números de 8 bits. Uma outra forma de ver isso seria pensar assim: qual é o maior número de 8 bits possível? FFh. Quanto vale FFh * FFh ? FE01. Assim, um número capaz de armazenar FE01 será suficiente para guardar resultado de uma multiplicação de 8 bits por 8 bits. Assim, iremos usar o registrador HL, o que implica que precisamos inicializar, antes de mais nada, o valor de H:

------
; MULUU - Multiplica um número de 8 bits por outro de 8 bits (sem sinal)
; Entrada: L  - Multiplicando
;          A  - Multiplicador
; Saída:   HL - Produto
;------
MULUU:
	LD	H,0	; Zera H...
	LD	B,A	; Indica número de vezes
	LD	A,0	; Zera acumulador
MUUL:	ADD	A,L	; Soma B vezes o valor de L
	DJNZ MUUL
	LD	L,A	; Copia pro destino
	RET

  Ao inserir essa função no programa principal, algumas mudanças precisam ser realizadas no corpo do código. O programa completo fica:

--- Cortar Aqui ---
BDOS	EQU	5
STROUT	EQU	9

START:
	; Mostra informacoes do programa
	LD	DE,NOMEDOPRG		; Indica texto do nome do programa
	CALL	MOSTRATXT		; Mostra texto
	LD	DE,AUTOR		; Indica texto do nome do autor
	CALL	MOSTRATXT		; Mostra texto

	LD	L,082h			; Multiplicando
	LD	A,005h			; Multiplicador
	CALL	MULUU			; Chama multiplicacao

	; HL já vem com o valor da reposta
	LD	DE,NUMERO		; Indica posição do texto
	CALL	HEXW			; Converte para ASCII
	
	LD	DE,NUMERO		; Aponta número
	CALL	MOSTRATXT		; Imprime

	JP	0			; Volta ao MSX-DOS

NOMEDOPRG:	DB	'Programa 6a - Multiplicando Numeros.',13,10,'$'
AUTOR:		DB	'  Por Daniel Caetano',13,10,10,'$'
NUMERO:		DB	0,0,0,0,0
PULALINHA:	DB	13,10,'$'

;------
; MULUU - Multiplica um número de 8 bits por outro de 8 bits (sem sinal)
; Entrada: L  - Multiplicando
;          A  - Multiplicador
; Saída:   HL - Produto
;------
MULUU:
	LD	H,0	; Zera H...
	LD	B,A	; Indica número de vezes
	LD	A,0	; Zera acumulador
MUUL:	ADD	A,L	; Soma B vezes o valor de L
	DJNZ MUUL
	LD	L,A	; Copia pro destino
	RET

;------
; MOSTRATXT - Funcao que mostra um texto cuja sequencia e' terminada por '$'.
;    Entrada: DE - Aponta para sequencia a ser mostrada, terminada por '$'
;------
MOSTRATXT:
	LD	C,STROUT		; Indica funcao de mostrar texto do BDOS
	CALL	BDOS			; Manda o BDOS executar.
	RET				; Retorna

;------
; HEX - Converte um número na RAM para um texto hex terminado por '$'.
; Entrada: DE - Posição de memória onde o texto será colocado (3 bytes)
;          A  - Valor a ser "convertido"
;------
HEX:	LD	B,A		; Salva valor original
	SRL	A		; Pega valor do nibble superior
	SRL	A
	SRL	A
	SRL	A
	CALL	DIG2ASCII	; Converte valor de A para ASCII
	LD	(DE),A		; Guarda na memória
	INC	DE		; Aponta próxima posição de memória
	LD	A,B		; Recupera valor numérico original
	AND	00001111b	; Aplica máscara (isola nibble inferior)
	CALL	DIG2ASCII	; Converte valor de A para ASCII
	LD	(DE),A
	INC	DE		; Aponta próxima posição de memória
	LD	A,'$'
	LD	(DE),A
	RET


;------
; DIG2ASCII - Converte um valor de 0 a 15 para seu correspondente ASCII
; Entrada: A - Valor a ser convertido
; Saída:   A - Valor convertido
;------
DIG2ASCII:
	ADD	A,'0'		; Soma o valor ASCII de '0' indepentende do
				; valor original de A
	CP	'9'+1		; Verifica se o valor em A é menor que o
				; valor de '9' + 1 (ou seja, se é menor ou 
				; igual  a '9')
	RET	C		; Se sim, vai embora...!
	; Aqui é caso o valor seja maior... ou seja, A a F
	ADD	A,'A'-('0'+10)	; Corrige para um valor de A a F
	RET

;------
; HEXW - Converte um número na RAM para um texto hex terminado por '$'.
; Entrada: DE - Posição de memória onde o texto será colocado (5 bytes)
;          HL - Valor a ser "convertido"
; Modifica: A, DE
;------
HEXW:	LD	A,H
	CALL	HEX
	LD	A,L
	CALL	HEX
	RET

	END
--- Cortar Aqui ---

  Entretanto... mais uma vez, ao rodar o programa, a decepção: O programa responde 008A, quando deveria responder 028A. O que ainda está errado? Analisemos, mais uma vez, com calma.

;------
; MULUU - Multiplica um número de 8 bits por outro de 8 bits (sem sinal)
; Entrada: L  - Multiplicando
;          A  - Multiplicador
; Saída:   HL - Produto
;------
MULUU:
	LD	H,0	; Zera H...
	LD	B,A	; Indica número de vezes
	LD	A,0	; Zera acumulador
MUUL:	ADD	A,L	; Soma B vezes o valor de L
	DJNZ MUUL
	LD	L,A	; Copia pro destino
	RET

  Se pensarmos bem, estamos ainda fazendo a conta só com 8 bits, pois estamos usando o registrador A para realizar o cálculo. E, com efeito, devemos realizar com o registrador HL! Assim, a funcao deve ser modificada da seguinte forma:

;------
; MULUU - Multiplica um número de 8 bits por outro de 8 bits (sem sinal)
; Entrada: L  - Multiplicando
;          A  - Multiplicador
; Saída:   HL - Produto
;------
MULUU:
	LD	H,0	; Zera H... HL agora contem o número a ser multiplicado
	LD	D,H	; Zera DE
	LD	E,H
	EX	DE,HL	; Troca o conteudo de DE com HL (HL fica 0000 e DE fica o multiplicando)
	LD	B,A	; Indica número de vezes
MUUL:	ADD	HL,DE	; Soma B vezes o valor de DE em HL
	DJNZ MUUL
	RET

  Isso corrige o problema dos 8 bits para 16 bits. Entretanto, se percebermos bem... Caso o multiplicador seja ZERO teremos uma reposta errada: a multiplicação ocorrerá, mas será uma multiplicação por 256!
  Ora, e por que isso acontece? Por uma peculiaridade do DJNZ. O DJNZ decrementa o valor de B antes de testá-lo. Desta forma, se enviarmos um valor ZERO como multiplicador, este valor será colocado em B e, ao ser decrementado, se tornará FFh. Desta forma, a multiplicação não será finalizada e o resultado será incorreto.
  Para corrigir isso, basta que verifiquemos, no início, se o valor do multiplicador é zero e, se for, iremos embora. Para isso, utilizaremos um CP 0 e um RET Z (RETurn if Zero). Note que colocamos isso em um local que HL tem um valor zero, assim o valor da solução estará correto (0*n = 0):

;------
; MULUU - Multiplica um número de 8 bits por outro de 8 bits (sem sinal)
; Entrada: L  - Multiplicando
;          A  - Multiplicador
; Saída:   HL - Produto
;------
MULUU:
	LD	H,0	; Zera H... HL agora contem o número a ser multiplicado
	LD	D,H	; Zera DE
	LD	E,H
	EX	DE,HL	; Troca o conteudo de DE com HL (HL fica 0000 e DE fica o multiplicando)
	CP	0	; Verifica se A é zero
	RET	Z	; Vai embora se for
	LD	B,A	; Indica número de vezes
MUUL:	ADD	HL,DE	; Soma B vezes o valor de DE em HL
	DJNZ MUUL
	RET

  Com essas correções, nossa função de multiplicar está pronta! O programa final completo tem este aspecto:

--- Cortar Aqui ---

BDOS	EQU	5
STROUT	EQU	9

START:
	; Mostra informacoes do programa
	LD	DE,NOMEDOPRG		; Indica texto do nome do programa
	CALL	MOSTRATXT		; Mostra texto
	LD	DE,AUTOR		; Indica texto do nome do autor
	CALL	MOSTRATXT		; Mostra texto

	LD	L,082h			; Multiplicando
	LD	A,005h			; Multiplicador
	CALL	MULUU			; Chama multiplicacao

	; HL já vem com o valor da reposta
	LD	DE,NUMERO		; Indica posição do texto
	CALL	HEXW			; Converte para ASCII
	
	LD	DE,NUMERO		; Aponta número
	CALL	MOSTRATXT		; Imprime

	JP	0			; Volta ao MSX-DOS

NOMEDOPRG:	DB	'Programa 6a - Multiplicando Numeros.',13,10,'$'
AUTOR:		DB	'  Por Daniel Caetano',13,10,10,'$'
NUMERO:		DB	0,0,0,0,0
PULALINHA:	DB	13,10,'$'

;------
; MULUU - Multiplica um número de 8 bits por outro de 8 bits (sem sinal)
; Entrada: L  - Multiplicando
;          A  - Multiplicador
; Saída:   HL - Produto
;------
MULUU:
	LD	H,0	; Zera H... HL agora contem o número a ser multiplicado
	LD	D,H	; Zera DE
	LD	E,H
	EX	DE,HL	; Troca o conteudo de DE com HL (HL fica 0000 e DE fica o multiplicando)
	CP	0	; Verifica se A é zero
	RET	Z	; Vai embora se for
	LD	B,A	; Indica número de vezes
MUUL:	ADD	HL,DE	; Soma B vezes o valor de DE em HL
	DJNZ MUUL
	RET

;------
; MOSTRATXT - Funcao que mostra um texto cuja sequencia e' terminada por '$'.
;    Entrada: DE - Aponta para sequencia a ser mostrada, terminada por '$'
;------
MOSTRATXT:
	LD	C,STROUT		; Indica funcao de mostrar texto do BDOS
	CALL	BDOS			; Manda o BDOS executar.
	RET				; Retorna

;------
; HEX - Converte um número na RAM para um texto hex terminado por '$'.
; Entrada: DE - Posição de memória onde o texto será colocado (3 bytes)
;          A  - Valor a ser "convertido"
;------
HEX:	LD	B,A		; Salva valor original
	SRL	A		; Pega valor do nibble superior
	SRL	A
	SRL	A
	SRL	A
	CALL	DIG2ASCII	; Converte valor de A para ASCII
	LD	(DE),A		; Guarda na memória
	INC	DE		; Aponta próxima posição de memória
	LD	A,B		; Recupera valor numérico original
	AND	00001111b	; Aplica máscara (isola nibble inferior)
	CALL	DIG2ASCII	; Converte valor de A para ASCII
	LD	(DE),A
	INC	DE		; Aponta próxima posição de memória
	LD	A,'$'
	LD	(DE),A
	RET


;------
; DIG2ASCII - Converte um valor de 0 a 15 para seu correspondente ASCII
; Entrada: A - Valor a ser convertido
; Saída:   A - Valor convertido
;------
DIG2ASCII:
	ADD	A,'0'		; Soma o valor ASCII de '0' indepentende do
				; valor original de A
	CP	'9'+1		; Verifica se o valor em A é menor que o
				; valor de '9' + 1 (ou seja, se é menor ou 
				; igual  a '9')
	RET	C		; Se sim, vai embora...!
	; Aqui é caso o valor seja maior... ou seja, A a F
	ADD	A,'A'-('0'+10)	; Corrige para um valor de A a F
	RET

;------
; HEXW - Converte um número na RAM para um texto hex terminado por '$'.
; Entrada: DE - Posição de memória onde o texto será colocado (5 bytes)
;          HL - Valor a ser "convertido"
; Modifica: A, DE
;------
HEXW:	LD	A,H
	CALL	HEX
	LD	A,L
	CALL	HEX
	RET

	END

--- Cortar Aqui ---

  Entretanto, vocês devem ter percebido que essa função é um tanto quanto lenta. Se precisarmos realizar muitas dessas operações por segundo, podemos ter um problema realmente sério. Em especial se substituirmos isso:

	LD	L,082h			; Multiplicando
	LD	A,005h			; Multiplicador

  Por uma operação deste tipo:

	LD	L,082h			; Multiplicando
	LD	A,0FFh			; Multiplicador

  Uma forma de acelerar neste caso, seria escolher o menor valor como sendo o multiplicador... Entretanto essa solução não resolve todos os problemas. Por exemplo, se a operação a ser realizada for:

	LD	L,0FFh			; Multiplicando
	LD	A,0FFh			; Multiplicador

  Não há muita alternativa... não é? Sim, é claro que há. (^=

  Vamos voltar um pouco à teoria e pensar: Como fazemos contas de multiplicar? Obviamente ninguem fica contando nos dedos o número de vezes... Nem usa palitinho. E, de certa forma, foi isso que ensinamos o computador fazer.
  Nós não fazemos isso manualmente porque é lento e desajeitado. Não há muita razão para imaginar que seria diferente para o computador. Pensemos então: como fazmos conta de multiplicação no papel?

  A idéia da multiplicação no papel é a seguinte:

   A idéia da multiplicação no papel é a seguinte:

   134 x 7 é reproduzida na seguinte operação:

   7*4 = 28. Fica 8 e vai dois.
   7*3 = 21 mais dois = 23. Fica 3, vai 2.
   7*1 = 7 mais 2 = 9. Fica 9.

  Com isso construímos a primeira operação: multiplicamos o multiplicando pelo primeiro dígito do multiplicador. Costumamos desenhar isso da seguinte forma:

   134
    x7
   ---
   938

  Ora, podemos escrever também essa operação de forma inversa:

      7
   x134
   ----
    938

  Embora seja anti-natural, ela facilita um pouco a explicação do que está por vir. Como realizaríamos essa conta, agora?

   4*7 = 28.

  Reescreveríamos isso assim:

      7
   x134
   ----
     28
      +
   
   3*7 = 21.

  Compondo a segunda etapa:

      7
   x134
   ----
     28
    21+
     ++

   1*7 = 7.

  Sendo finalmente:

      7
   x134
   ----
     28
    21+
    7++
   ----
    938

  Mas esses "+" não parecem ajudar muito na explicação matemática, certo? Uma outra forma de realizar este mesmo cálculo, mais elucidativa matematicamente, seria:

      7
   x134
   ----
    938

   4*7 = 28.

   ...

      7
   x134
   ----
     28
   
   30*7 = 210.

   ...

      7
   x134
   ----
     28
    210

   100*7 = 700.

  Sendo finalmente:

      7
   x134
   ----
     28
  + 210
  + 700
   ----
    938

  Pode-se dizer então que fizemos foi o seguinte:

   134 * 7 =      1*4*7 +     10*3*7 +    100*1*7
	   = (10^0)*4*7 + (10^1)*3*7 + (10^2)*1*7

  O que fizemos aqui foi transformar uma multiplicação de um número complicado em uma sequências de multiplicações por números entre 0 e 9 (números da base decimal, em cada uma das etapas), multiplicações por potências de 10, ou seja, da base (entre cada etapa) e finalmente em um número de somas que é igual ao número de dígitos do multiplicador (no caso, 3).

  O que aconteceria se transformássemos essa lógica para uma multiplicação de lógica binária? Poderíamos escrever, por exemplo, que:

   4         * 3
   00000100b * 00000011b

  Seria como? Vamos tentar aplicar a mesma lógica: Multiplicar o primeiro dígito do nultiplicador pelo multiplicando, multiplicado pela base elevada a zero; o segundo dígito do multiplicador pelo multiplicando, multiplicado pela base elevada a um; o terceiro dígido to multiplicador pelo multiplicando, multiplicado pela base elevada a dois... e assim por diante:

   00000100b * 00000011b

   1o. Digito do Multiplicador:
   0 * 00000011b * 2^0 = 0

   2o. Digito do Multiplicador:
   0 * 00000011b * 2^1 = 0

   3o. Digito do Multiplicador:
   1 * 00000011b * 2^2 = 00000011b * 4 = 00001100b

... (todos os outros dígitos do multiplicador são zero)

Somando todos estes resultados:

   00000000b + 00000000b + 00001100b = 00001100b = 12

  E 12 é exatamente o valor do cálculo 4*3!

  Qual é a vantagem de se fazer isso? Se observarmos bem, podemos reescrever assim:

   1o. Digito do Multiplicador:
   0 * 00000011b * 2^0 = 0 * 00000011b * 1 = 	0 * 00000011b

   2o. Digito do Multiplicador:
   0 * 00000011b * 2^1 = 0 * 00000011b * 2 =    0 * 00000110b

   3o. Digito do Multiplicador:
   1 * 00000011b * 2^2 = 1 * 00000011b * 4 =    1 * 00001100b

...

  Observem os valores mais da direita... Eles significam que podemos transformar uma multiplicacao em uma sequência de somas de valores... e esses valores, vão sendo deslocados uma casa à esquerda em cada iteração!
Parece estranho, mas... é exatamente ESSA a função daquele "+", "++", "+++" ... que colocamos numa operação como essa:

      7

   x134

   ----

     28

    21+

    7++

   ----

    938

  Podemos dizer que "escrever esse +" é uma boa forma de realizar potências da base utilizada sem ter de pensar muito a respeito! 

  Assim, um bom algoritmo de multiplicação pode se utilizar dessa "manha" para reduzir seu loop. Uma multiplicacao de dois números de 8 bits se reduzirá de 255 somas (no pior caso) para 8 somas e 8 deslocamentos à esquerda!
Um bom algoritmo para realizar isso, em binário, seria:

   0- Zera-se resultado.

   1- Primeira casa do multiplicador é um?
      Não? Pula pro passo 2.
      Sim? Soma ao resultado o multiplicando

   2- Rotaciona-se o multiplicando à esquerda
      Segunda casa do multiplicador é um?
      Não? Pula pro passo 3.
      Sim? Soma ao resultado o multiplicando.

   3- Rotaciona-se o multiplicando à esquerda
      Terceira casa do multiplicador é um?
      Não? Pula pro passo 4.
      Sim? Soma ao resultado o multiplicando.

  ... e assim por diante até serem verificadas as 8 casas do multiplicador.

  Nesta primeira implementação usaremos algumas instruções de rotação novas: RRCA, SLA e RL. O que essas instruções fazem?

  RRCA - Rotate Right (with Copy) Accumulator. Em língua de gente, a RRCA rotaciona o acumulador pra direita, jogando o bit 7 pro bit 6, o bit 6 pro bit 5 e assim por diante... até o bit 0, que é jogado no bit 7. Além disso, uma cópia do bit 0 é sempre jogada no carry. Desta forma, depois de aplicada 8 vezes esta instrução faz o Acumulador (registrador A) voltar a ter exatamente o mesmo valor original. Usaremos essa instrução para poder testar bit por bit do acumulador, usando-se do flag Carry para isso (o qual podemos usar facilmente em instruções JP (JumP) e JR (Jump Relative).
  SLA - Shift Left And clear. Apesar do nome bizarro o que esta rotina faz é simples. Ela desloca para a esquerda os bits de um registrador qualquer, jogando bit 7 no flag Carry, o bit 6 no bit 7, o bit 5 no bit 6... até o bit 0 no bit 1. E no lugar no bit 0 um bit de valor "0" é inserido. Trocando em miúdos, multiplica um registrador por 2 e indica no carry se "foi um". Usaremos essa para multiplicar o byte menos significativo do multiplicando.
  RL - Rotate Left. Essa aqui é a mais simples de rodas. Simplesmente rotaciona um registrador pra esquerda, jogando o conteúdo do flag Carry no bit 0, o bit 0 no bit 1, o bit 2 no bit 2... até o bit 7, que vai parar no carry. Como temos um possível "vai um" no byte menos significativo, essa instrução vai fazer no byte mais significativo uma multiplicação por 2 caso o valor do Carry for zero (não houve "vai um") ou então fará uma multiplicação por 2 e em seguida somará 1, caso o valor do Carry seja zero.

  Com essas instruções, podemos implementar a multiplicação da seguinte forma:

;------
; MULUU - Multiplica um número de 8 bits por outro de 8 bits (sem sinal)
; Entrada: L  - Multiplicando
;          A  - Multiplicador
; Saída:   HL - Produto
;------
MULUU:
	LD	H,0		; Zera H... HL agora contem o número a ser multiplicado
	LD	D,H		; Zera DE
	LD	E,H
	EX	DE,HL		; Troca o conteudo de DE com HL (HL fica 0000 e DE fica o multiplicando)
	LD	B,8		; Repetir 8 vezes, uma vez pra cada bit
MUUL:	; Verifica o bit mais baixo de A
	RRCA			; Joga bit mais baixo de A no flag Carry
	JR	NC,MUUL2	; Se era zero, não soma nada ao HL.
	; Aqui se bit valia 1, então vai somar...
	ADD	HL,DE
MUUL2:	; Faz o Shift pra esquerda em DE (multiplica DE por 2)
	SLA	E		; Shifta E pra direita, jogando bit mais significativo no Carry
	RL	D		; Rotaciona D, colocando o bit do carry no bit 0
	DJNZ	MUUL		; Repete 8 vezes
	RET

  Apesar da rotina ser bem maior, ela seguramente, no caso médio, é bem mais rápida que a de somas simples, que fazia todas as somas. Essa rotina sempre vai fazer 24 shifts e de 0 a 8 somas. A rotina de somas simples podia, em alguns casos, realizar apenas algumas poucas somas. Mas para qualquer número maior que 32, por exemplo, esta rotina será mais eficiente.
  Esta forma de raciocínio é extensível e permite realizar operações com bem mais de 16 e 8 bits (quantos for desejado). Obviamente nestes casos é necessário um pouco mais de controle sobre alguns flags, mas a idéia é basicamente a mesma.

  Vale ressaltar que para alguns casos especiais, pode não ser interessante usar essa rotina. Por exemplo, se voce sempre vai multiplicar por números entre 1 e 15, por exemplo, pode considerar uma rotina que conte só até o quarto bit, mudando a
linha:

	LD	B,8		; Repetir 8 vezes, uma vez pra cada bit

para:

	LD	B,4		; Repetir 4 vezes, uma vez pra cada bit

  Se a maioria das vezes a multiplicação é por números pequenos, mas às vezes o número é grande, talvez seja interessante trocar o DJNZ por algo um pouco diferente:

;------
; MULUU - Multiplica um número de 8 bits por outro de 8 bits (sem sinal)
; Entrada: L  - Multiplicando
;          A  - Multiplicador
; Saída:   HL - Produto
;------
MULUU:
	LD	H,0		; Zera H... HL agora contem o número a ser multiplicado
	LD	D,H		; Zera DE
	LD	E,H
	EX	DE,HL		; Troca o conteudo de DE com HL (HL fica 0000 e DE fica o multiplicando)
MUUL:	; Verifica o bit mais baixo de A
	SRL	A		; Joga bit mais baixo no flag Carry
	JR	NC,MUUL2	; Se era zero, não soma nada ao HL.
	; Aqui se bit valia 1, então vai somar...
	ADD	HL,DE
MUUL2:	; Faz o Shift pra esquerda em DE (multiplica DE por 2)
	SLA	E		; Shifta E pra direita, jogando bit mais significativo no Carry
	RL	D		; Rotaciona D, colocando o bit do carry no bit 0
	CP	0		; Verifica se A virou zero
	JR	NZ,MUUL		; Se A ainda não é zero, continua o loop
	RET

  Note as diferenças importantes: primeiramente trocamos o RRCA pelo SRL. Por que isso? Bem, a idéia desta segunda implementação é ir "limpando" o multiplicador à medida que testamos seus bits, de forma que quando ele for zero a rotina será finalizada, mesmo que nem todos os 8 bits tenham sido testados um a um (se o multiplicador virou zero, significa que todos os bits restantes são zero, e portanto não há mais nada a somar).
  Ora, como eu disse, o RRCA deixa o Acumulador intocado (e ainda por cima faz o favor de "sujá-lo" com o valor desconhecido do Carry durante as 8 rotações). Obviamente esta instrução não serve para o nosso propósito. Precisamos de uma instrução que vá jogando os bits do registrador no Carry, deslocando-os para a direita, e inserindo um zero no bit 7. Pois é exatamente isso que a instrução SRL faz:

  SRL - Shift Right and cLear the most significant bit. Ou seja, desloca todos os bits pra direita (bit 0 vai pro Carry, bit 1 vai pro bit 0... e assim por diante... e o bit 7 ganha um bit de valor "0" nele). Assim, na pior hipótese, após 8 instruções destas seguidas... o registrador sempre valerá zero.

  Ora, mas então ... porquê eu não propus essa forma originalmente? Bem, vamos analisar qual é o "overhead" adicionado com as instruções extras, por etapa da operação:

   Saiu		TStates		Entrou		TStates
   RRCA		4+1 = 5		SRL		8+2 = 10
   DJNZ		13+1 = 14	CP 0 + JR NZ	4+1 + 12+1 = 18

  Ora, acrescentamos um overhead de (10-5)+(18-14) = 9 TStates em cada rodada do Loop. É preciso que esses 9 TStates por loop sejam compensados pela redução de rodadas. De fato, no melhor caso do loop "original" (quando o bit sendo testado é zero), o tempo de uma rodada do loop é SRL + JR NC (verdadeiro) + SLA + RL + DJNZ. O tempo disso é 10 + 13 + 10 + 10 + 18 = 61 T States. Como na versão modificada temos apenas 9 TStates a mais por loop, vejamos a tabela:

   Bit mais alto	TStates 	TStates 		Variação
   No multiplicador	economizados	gastos a mais
   5			61*2 = 122	9*6 = 54		-68
   6			61*1 = 61	9*7 = 63		2
   7			61*0 = 0	9*8 = 72		72

  Por essa conta simples fica fácil de ver que, quando tivermos um número de até 6 bits, (multiplicadores de 0 a 63) não vale a pena usar a versão "original" da rotina, é melhor usar a versão extendida (o ganho é tanto maior quanto menor for o multiplicador, pois há uma economia de tempo cada vez maior). Para números entre 64 e 127 o desempenho é praticamente o mesmo, sendo ligeiramente mais rápido na versão original.
  Para números maiores (de 128 a 255), a versão original é claramente mais rápida, e portanto a versão "mexida" perde o sentido. Assim, é importante fazer uma análise de qual é o problema que se está resolvendo e usar uma ou outra rotina. Veja que embora a maior parte dos números (64 a 255) seja resolvido mais rapidamente pela versão "original", os ganhos são brutais para multiplicadores entre 0 e 63 na versão extendida.
  Na dúvida de qual o escopo de soluções, talvez seja mais interessante ficar com a versão original.

  O programa desta aula, com a versão "original" (que sempre executa os 8 passos do loop) da função tem a seguinte cara:

--- Cortar Aqui ---

BDOS	EQU	5
STROUT	EQU	9

START:
	; Mostra informacoes do programa
	LD	DE,NOMEDOPRG		; Indica texto do nome do programa
	CALL	MOSTRATXT		; Mostra texto
	LD	DE,AUTOR		; Indica texto do nome do autor
	CALL	MOSTRATXT		; Mostra texto

	LD	L,082h			; Multiplicando
	LD	A,005h			; Multiplicador
	CALL	MULUU			; Chama multiplicacao

	; HL já vem com o valor da reposta
	LD	DE,NUMERO		; Indica posição do texto
	CALL	HEXW			; Converte para ASCII
	
	LD	DE,NUMERO		; Aponta número
	CALL	MOSTRATXT		; Imprime

	JP	0			; Volta ao MSX-DOS

NOMEDOPRG:	DB	'Programa 6a - Multiplicando Numeros.',13,10,'$'
AUTOR:		DB	'  Por Daniel Caetano',13,10,10,'$'
NUMERO:		DB	0,0,0,0,0
PULALINHA:	DB	13,10,'$'

;------
; MULUU - Multiplica um número de 8 bits por outro de 8 bits (sem sinal)
; Entrada: L  - Multiplicando
;          A  - Multiplicador
; Saída:   HL - Produto
;------
MULUU:
	LD	H,0		; Zera H... HL agora contem o número a ser multiplicado
	LD	D,H		; Zera DE
	LD	E,H
	EX	DE,HL		; Troca o conteudo de DE com HL (HL fica 0000 e DE fica o multiplicando)
	LD	B,8		; Repetir 8 vezes, uma vez pra cada bit
MUUL:	; Verifica o bit mais baixo de A
	RRCA			; Joga bit mais baixo de A no flag Carry
	JR	NC,MUUL2	; Se era zero, não soma nada ao HL.
	; Aqui se bit valia 1, então vai somar...
	ADD	HL,DE
MUUL2:	; Faz o Shift pra esquerda em DE (multiplica DE por 2)
	SLA	E		; Shifta E pra direita, jogando bit mais significativo no Carry
	RL	D		; Rotaciona D, colocando o bit do carry no bit 0
	DJNZ	MUUL		; Repete 8 vezes
	RET

;------
; MOSTRATXT - Funcao que mostra um texto cuja sequencia e' terminada por '$'.
;    Entrada: DE - Aponta para sequencia a ser mostrada, terminada por '$'
;------
MOSTRATXT:
	LD	C,STROUT		; Indica funcao de mostrar texto do BDOS
	CALL	BDOS			; Manda o BDOS executar.
	RET				; Retorna

;------
; HEX - Converte um número na RAM para um texto hex terminado por '$'.
; Entrada: DE - Posição de memória onde o texto será colocado (3 bytes)
;          A  - Valor a ser "convertido"
;------
HEX:	LD	B,A		; Salva valor original
	SRL	A		; Pega valor do nibble superior
	SRL	A
	SRL	A
	SRL	A
	CALL	DIG2ASCII	; Converte valor de A para ASCII
	LD	(DE),A		; Guarda na memória
	INC	DE		; Aponta próxima posição de memória
	LD	A,B		; Recupera valor numérico original
	AND	00001111b	; Aplica máscara (isola nibble inferior)
	CALL	DIG2ASCII	; Converte valor de A para ASCII
	LD	(DE),A
	INC	DE		; Aponta próxima posição de memória
	LD	A,'$'
	LD	(DE),A
	RET


;------
; DIG2ASCII - Converte um valor de 0 a 15 para seu correspondente ASCII
; Entrada: A - Valor a ser convertido
; Saída:   A - Valor convertido
;------
DIG2ASCII:
	ADD	A,'0'		; Soma o valor ASCII de '0' indepentende do
				; valor original de A
	CP	'9'+1		; Verifica se o valor em A é menor que o
				; valor de '9' + 1 (ou seja, se é menor ou 
				; igual  a '9')
	RET	C		; Se sim, vai embora...!
	; Aqui é caso o valor seja maior... ou seja, A a F
	ADD	A,'A'-('0'+10)	; Corrige para um valor de A a F
	RET

;------
; HEXW - Converte um número na RAM para um texto hex terminado por '$'.
; Entrada: DE - Posição de memória onde o texto será colocado (5 bytes)
;          HL - Valor a ser "convertido"
; Modifica: A, DE
;------
HEXW:	LD	A,H
	CALL	HEX
	LD	A,L
	CALL	HEX
	RET

	END

--- Cortar Aqui ---

  Entretanto, essas são sempre as melhores soluções? De forma alguma. Essas são ótimas soluções para cálculos mais "genéricos", por assim dizer... quando de antemão você não sabe exatamente o tipo de multiplicação que vai realizar.
  Por exemplo: tem um jeito muito mais rápido e eficiente de realizar multiplicações quando se tratam de potências de dois, que é simplesmente fazer o número de shifts adequados, sem loops, sem toda essa parafernalha. Aliás, uma boa idéia, quando a velocidade é algo realmente fundamental, é abrir esses loops, ou seja: tirar fora o DJNZ ou o JR NZ... e simplesmente copiar 8 vezes a rotina. Com isso o ganho é razoável, já que cada um desses DJNZs ou CP 0 + JR NZ levam respectivamente 14 T States e 18 T States para serem executados. (^=

  Uma outra aproximação é usar uma tabela pré-fixada, mas essa solução só é viável quando a multiplicação é sempre por um número fixo. Cria-se uma tabela com as respostas na seguinte forma:

MULTTBL:
	DW	RESULTADO0	; N*0
	DW	RESULTADO1	; N*1
	DW	RESULTADO2	; N*2
	(...)
	DW	RESULTADO255	; N*255

  E para o cálculo da multiplicação a nossa função fica:

;------
; MULNU - Multiplica N por um número de 8 bits
; Entrada: A  - Multiplicador
; Saída:   HL - Produto
;------
MULNU:	LD	L,A		; Coloca multiplicador em HL
	LD	H,0
	ADD	HL,HL		; Multiplica por 2 (offset da tabela)
	LD	DE,MULTTBL	; Coloca endereço base da tabela em DE
	ADD	HL,DE		; Calcula posição da memória que contém a solução, em HL
	LD	A,(HL)		; Pega solução em HL
	INC	HL
	LD	H,(HL)
	LD	L,A
	RET

MULTTBL:
	DW	RESULTADO0	; N*0
	DW	RESULTADO1	; N*1
	DW	RESULTADO2	; N*2
	(...)
	DW	RESULTADO255	; N*255

  Essa é uma solução de alto desempenho, mas gasta uma grande quantidade de memória. Essa é, aliás, uma das grandes dicotomias do ASM. Muitas vezes para acelerar você é obrigado a gastar mais espaço, e muitas vezes para economizar espaço você deixa as coisas mais lentas.
  Note, entretanto, que isso não é uma regra sem exceção. Códigos mal planejados tornam-se maiores e mais lentos do que seria necessário. Nem sempre um código enorme é sinônimo de rápido e nem sempre um código pequeno é sinônimo de lento.
  Como sempre... há casos e casos.

  Na próxima aula trataremos então as divisões no Z80. Até lá!
 
  Um abraço e até a próxima!

  Daniel Caetano.

  PS: Sugestões e comentários são bem-vindos. (^=