Fuente: Zona de Pruebas
El programa
Este ensamblador es un completo entorno de desarrollo para el microprocesador Z80 que incorpora un editor y traduce el 100% de sus mnemotécnicos a código máquina.
Como funciona
Al ejecutar el programa entramos en el modo “prompt” donde podemos entrar una de las siguientes opciones con parámetros opcionales separados por una coma o espacio:
E [línea]
Introducir líneas a partir de la línea indicada o a partir de la última línea introducida.
El editor no permite editar una línea ya introducida. Se debe reescribir completamente.
I desde , nºlíneas
Inserta un número de líneas en blanco a partir de la línea indicada. Las líneas existentes se desplazan hacia abajo.
D desde [hasta]
Borra las líneas indicadas. Las líneas existentes se desplazan hacia arriba.
L [desde] [hasta]
Permite listar por pantalla todo el programa, la línea indicado o desde una línea hasta otra.
P [desde] [hasta]
Permite listar por impresora todo el programa, la línea indicado o desde una línea hasta otra.
A [opción]
Ensambla el programa con la opción indicada. Por defecto el ensamblado se realiza en dos pasadas.
“opción = 1” solo realiza la primera pasada, que calcula la dirección de las etiquetas y ensambla las líneas que no las usan.
“opción = 2” solo realiza la segunda pasada y no verifica la dirección de las etiquetas. Es más rápido.
E [dirección]
Ejecuta el programa desde la posición por defecto del programa (58000), el indicado por ORG o el indicado por el parámetro “dirección”.
S [nombre]
Graba el programa fuente con el nombre indicado o el último nombre de fichero usado. El nombre no puede llevar la extensión y por defecto se pone “.ASM”. El nombre de fichero por defecto es “SOURCE.ASM”.
O [nombre]
Carga el programa fuente con el nombre indicado o el último nombre de fichero usado. El nombre no puede llevar la extensión y por defecto se pone “.ASM”. El nombre de fichero por defecto es “SOURCE.ASM”.
F
Muestra por pantalla los ficheros con extensión “.ASM”.
W columnas
Se especifica el número de columnas usado en pantalla, que puede estar entre 32 y 80. Por defecto se usan 38.
Q
Salir del ensamblador. Se puede volver a entrar al ensamblador en frío con “RUN” o en caliente con “GOTO 700”.
Si se modifica el programa BASIC la vuelta al ensamblador se realizará siempre en frío.
Características del ensamblador
Lo primero y más importante: todos los comandos e instrucciones se deben introducir usando mayúsculas.
El editor admite la introducción de hasta 250 líneas de programa, que puede ser ampliado modificando el valor de la variable H% y de la matriz L$(200,3) de la línea 100.
El editor admite la definición de hasta 50 etiquetas. Este valor puede ser ampliado modificando los valores de las matrices Q$(50) y M(50), y el “Q%<50” de la línea 174.
Las líneas no se pueden tabular y no pueden contener más espacios que el que hay entre la instrucción y sus parámetros. Los parámetros pueden ir separados por un espacio o una coma.
Los valores hexadecimales se introducen con un símbolo $ delante. De todas formas se recomienda el uso de valores decimales porque los hexadecimales no son admitidos en algunos casos que se indican más adelante.
Las etiquetas se definen con un nombre de hasta 8 letras y siempre empiezan con dos puntos. No puede haber una etiqueta y una instrucción en la misma línea.
No se admiten comentarios en el código fuente.
Por defecto el programa en código máquina se carga a partir de la dirección 58000. Para modificar este valor se debe usar la directiva “ORG dirección” siendo el valor de la dirección un número decimal o hexadecimal.
Para cargar valores numéricos en una determinada dirección de memoria se pueden usar las directivas DB (un byte) y DW (dos bytes). Si se introducen listas de números se debe usar “DB” y los valores se deben separar con comas y estar en formato decimal. Si se introduce una cadena de caracteres debe estar comprendida entre unas comillas simples. No se pueden mezclar listas de números y cadenas en la misma línea.
Todas las pruebas se han realizado en un equipo con disquetera. Para usar el ensamblador con cintas de casete se debe introducir CAS: delante del nombre. De todas formas se recomienda el usado de disquetera.
Para guardar el programa en código máquina generado durante el ensamblado es necesario el uso del comando “BSAVE” desde el BASIC, saliendo del ensamblador con “Q”.
Bloques
He dividido el listado en 5 grandes bloques:
- Análisis de la línea a ensamblar.
- Introducción en memoria del código ensamblado.
- Colección de algoritmos para ensamblar las distintas instrucciones.
- Menú de opciones.
- Rutinas del entorno de desarrollo.
Cómo funciona
Todo el programa ocupa un total de 179 líneas. A continuación detallo las distintas secciones del programa.
100 – Declaración de matrices del ensamblador.
110 – Inicio del proceso de ensamblado. Control de las pasadas y números de líneas.
116 – Inicio del analizador de los parámetros de la línea.
118 – Detectar si es un valor numérico decimal o hexadecimal y si lleva paréntesis.
136 – Bucle para identificar el registro usado.
152 – Bucle para identificar la etiqueta usada.
168 – Llamar a la subrutina correspondiente a la instrucción a ensamblar.
174 – En la primera pasada muestra la dirección de memoria de las etiquetas del programa.
178 – En la segunda pasada muestra la dirección de memoria y los valores hexadecimales del ensamblado de la línea.
184 – Bucle que introduce los valores hexadecimales en la memoria.
194 – Final del proceso de ensamblado.
300 – Ensamblado de LD
330 – Ensamblado de PUSH / POP
340 – Ensamblado de INC / DEC
350 – Ensamblado de ADD / ADC / SUB / SBC / AND / XOR / OR / CP
370 – Ensamblado de RET
380 – Ensamblado de CALL / JP
390 – Ensamblado de JR / DJNZ
410 – Ensamblado de BIT / RES / SET
420 – Ensamblado de RLS / RRC / RL / RR / SLA / SRA / SRL
430 – Ensamblado de IN / OUT
440 – Ensamblado de IM
450 – Ensamblado de RST
460 – Ensamblado de EX
470 – Ensamblado de DB / DW
490 – Ensamblado de ORG
700 – Menú principal.
720 – Separar una línea por parámetros.
740 – Comando EDIT (E)
760 – Comando INSERT (I)
780 – Comando DELETE (D)
800 – Comandos LIST (L) y PRINT (P)
820 – Comando SAVE (S)
840 – Comando LOAD (O)
860 – Rutina de PRESSKEY
870 – Comando ASSEMBLE (A)
880 – Comando FILES (F)
890 – Comando EXECUTE (E)
900 – Inicio del ensamblador. Carga de matrices y mensaje en pantalla.
910 – Comando QUIT (Q)
912 – Rutina de salto si hay error.
950 – Líneas DATA con las listas de instrucciones, registros, código de ensamblado y líneas de salto por instrucción.
El programa
' Reservar 4KB para variables de cadena y definición de matrices.
100 CLEAR 4096:H%=250:N$="SOURCE.ASM":DIM I$(70),V$(34),R$(32),P(7),J%(36),P$(5),M(50),L$(250,3),H$(250),Q$(50)
' Inicializar y saltar al prompt.
102 GOSUB900:GOTO700
' Inicio del proceso de Ensamblado
110 IFZ1%=1THENQ%=0
' Control de pasadas de ensamblado
112 FORY%=Z1%TOZ2%
' Dirección de ensamblado por defecto
114 D=58000!:B=D:PRINT"Pass:";Y%
' Bucle de líneas del programa
116 FORZ%=1TOLF%-1
' La línea es una etiqueta?
118 IFLEFT$(L$(Z%,1),1)=":"THENO%=99:E$="":GOTO172
' La línea ya se ha ensamblado?
120 IFH$(Z%)<>""THENO%=0:E$=H$(Z%):GOTO172
' Si la instrucción no lleva parámetros coger su código y ensamblar
122 O%=VAL(L$(Z%,1)):IFO%>36THENE$=V$(O%-36):GOTO172
' Controlar número de parámetros
124 IFL$(Z%,3)=""THENX%=2ELSEX%=3
' Guardar los parámetros y el Id de instrucción
126 P(2)=0:P(3)=0:P(4)=.1:P(5)=.1:E$="":E1$="":E2$="":P$(4)="":P$(5)=""
' Analizar el/los parámetros
128 FORN%=2TOX%
130 R%=0:B$=L$(Z%,N%):IFLEFT$(B$,1)="("THENP(N%+4)=1:A$=MID$(B$,2,LEN(B$)-2)ELSEP(N%+4)=0:A$=B$
' El parámetro es un valor numérico?
132 C$=LEFT$(A$,1):IFC$>="0"ANDC$<="9"THEN160
' El parámetro es un número hexadecimal?
134 IFC$="$"THENA$=MID$(A$,2):P(N%+2)=VAL("&H"+A$):GOTO162
' Detectar registro usado
136 FORF%=1TO32
138 IFB$=R$(F%)THENP(N%)=F%:F%=99
140 NEXT
142 IFP(N%)ORO%=14THEN164ELSEIFP(N%+4)=0THEN150
' Se usa un registro de indice?
144 IFLEFT$(B$,4)="(IX+"THENE1$="DD"
146 IFLEFT$(B$,4)="(IY+"THENE1$="FD"
148 IFE1$<>""THENE2$=RIGHT$("00"+HEX$(VAL(MID$(B$,5))),2):P(N%)=7:GOTO164
' Comprobar si se está usando una etiqueta
150 R%=1:IFY%=1THENA$=STR$(D):GOTO160
152 FORF%=1TOQ%
154 IFQ$(F%)=A$THENA$=STR$(M(F%)):P(N%+2)=M(F%):F%=99
156 NEXT
158 IFP(N%+2)<0THENE$="":GOTO178
160 P(N%+2)=VAL(A$):A$=RIGHT$("000"+HEX$(VAL(A$)),4)
162 P$(N%+2)=RIGHT$(A$,2)+LEFT$(A$,2)
164 NEXTN%
' Guardar los valores de los parámetros
166 R1%=P(2):R2%=P(3):V1=P(4):V2=P(5)
' Saltar a la rutina que ensambla la instrucción
168 ONJ%(O%)GOSUB300,330,340,350,370,380,390,410,420,430,440,450,460,470,490
' Si la línea no usa una etiqueta se guarda el código
170 IFR%=0THENH$(Z%)=E$
' Si es la segunda pasada salta la parte de etiquetas
172 IFY%=2THEN178ELSEIFO%<>99THEN186
174 IFQ%<50THENQ%=Q%+1:Q$(Q%)=MID$(L$(Z%,1),2):M(Q%)=D
' Mostrar la dirección de la etiqueta
176 PRINTUSING"##### = ";D;:PRINTQ$(Q%):GOTO186
' Mostrar el ensamblado de la línea
178 PRINTUSING"### ##### ";Z%;D;:IFLEFT$(L$(Z%,1),1)=":"THENPRINTL$(Z%,1):GOTO188
180 PRINT" ";E$;TAB(21);I$(VAL(L$(Z%,1)));" ";L$(Z%,2);:IFL$(Z%,3)<>""THENPRINT",";L$(Z%,3)ELSEPRINT
' Si no se ha generado código mostrar el error (excepto ORG)
182 IFE$=""THENIFO%<>36THENPRINT"Error in ";Z%:Z%=999:Y%=2:GOSUB860:GOTO188
' Introducir el código generado en memoria
184 IFE$<>""THENFORF%=1TOLEN(E$)-1STEP2:POKED,VAL("&H"+MID$(E$,F%,2)):D=D+1:NEXT:GOTO188
186 D=D+LEN(E$)/2
' Siguiente línea del programa / Siguiente pasada de ensamblado
188 NEXTZ%:PRINT:NEXTY%
' Indicar el tamaño total en bytes del programa ensamblado
194 IFE$<>""THENPRINTUSING"Program length:##### bytes";D-B:RETURNELSERETURN
' Ensamblado de LD
300 IFR1%>0ANDR1%<9ANDR2%>0ANDR2%<9THENE$=E1$+HEX$(56+8*R1%+R2%-1)+E2$:RETURN 302 IFR1%>0ANDR1%<9ANDV2<>.1ANDP(7)=0THENE$=RIGHT$("0"+HEX$(-2+8*R1%),2)+LEFT$(P$(5),2):RETURN
304 IFR1%<>8THEN310ELSEIFV2<>.1ANDP(7)THENE$="3A"+P$(5):RETURN
306 IFR2%=16ORR2%=17THENIFR2%=16THENE$="0A":RETURNELSEE$="1A":RETURN
308 IFR2%=20ORR2%=21THENIFR2%=20THENE$="ED57":RETURNELSEE$="ED5F":RETURN
310 IFR2%<>8THEN316ELSEIFV1<>.1ANDP(6)THENE$="32"+P$(4):RETURN
312 IFR1%=16ORR1%=17THENIFR1%=16THENE$="02":RETURNELSEE$="12":RETURN
314 IFR1%=20ORR1%=21THENIFR1%=20THENE$="ED47":RETURNELSEE$="ED4F":RETURN
316 IFR1%=13ORR1%=14THENIFR1%=13THENE1$="DD":R1%=11ELSEE1$="FD":R1%=11
318 IFR1%>8ANDR1%<15ANDV2<>.1ANDP(7)=0THENE$=E1$+RIGHT$("0"+HEX$(1+16*(R1%-9)),2)+P$(5):RETURN
320 IFR1%>8ANDR1%<15ANDV2<>.1ANDP(7)THENIFR1%<>11THENE$="ED"+HEX$(75+16*(R1%-9))+P$(5):RETURNELSEE$=E1$+"2A"+P$(5):RETURN
322 IFR2%>8ANDR2%<15ANDV1<>.1ANDP(6)THENIFR2%<>11THENE$="ED"+HEX$(67+16*(R2%-9))+P$(4):RETURNELSEE$=E1$+"22"+P$(4):RETURN
324 IFR2%=13ORR2%=14THENIFR2%=13THENE1$="DD":R2%=11ELSEE1$="FD":R2%=11
326 IFR1%=12ANDR2%=11THENE$=E1$+"F9":RETURNELSERETURN
' Ensamblado de PUSH / POP
330 IFR1%=15THENR1%=12
332 IFR1%=13ORR1%=14THENIFR1%=13THENE1$="DD":R1%=11ELSEE1$="FD":R1%=11
334 IFR1%>8ANDR1%<15THENE$=E1$+HEX$(197+4*(O%-3)+(R1%-9)*16):RETURNELSERETURN 340 IFR1%>0ANDR1%<9THENE$=E1$+RIGHT$("0"+HEX$(-8+8*R1%+O%),2)+E2$:RETURN 342 IFR1%=13ORR1%=14THENIFR1%=13THENE1$="DD":R1%=11ELSEE1$="FD":R1%=11 344 IFR1%>8ANDR1%<15THENE$=E1$+RIGHT$("0"+HEX$(3+16*(R1%-9)+8*(O%-4)),2):RETURNELSERETURN
' Ensamblado de ADD / ADC / SUB / SBC / AND / XOR / OR / CP
350 IF(O%<8ORO%=9)ANDR1%=8ANDX%=3THENR1%=R2%:V1=V2:P$(4)=P$(5) 352 IFR1%>0ANDR1%<9THENE$=E1$+HEX$(128+8*(O%-6)+R1%-1)+E2$:RETURN
354 IFR1%=0ANDV1<>.1THENE$=HEX$(198+8*(O%-6))+LEFT$(P$(4),2):RETURN
356 IF(O%=7ORO%=9)ANDR1%=11ANDR2%>8ANDR2%<13THENE$="ED"+HEX$(66+4*(9-O%)+16*(R2%-9)):RETURN 358 IFR1%=13ORR1%=14THENIFR1%=13THENE1$="DD":R1%=11ELSEE1$="FD":R1%=11 360 IFR2%=13ORR2%=14THENR2%=11 362 IFR1%=11ANDR2%>8ANDR2%<13THENE$=E1$+RIGHT$("0"+HEX$(9+16*(R2%-9)),2):RETURNELSERETURN
' Ensamblado de RET
370 IFR1%=2THENR1%=28ELSEIFL$(Z%,2)<>""ANDR1%24THENE$=HEX$(194+8*(R1%-25))+P$(5):RETURNELSERETURN
' Ensamblado de JR / DJNZ
390 IFR%=0THENIFX%=2THENN%=V1:GOTO394ELSEN%=V2:GOTO394
391 N%=D-P(2+X%):IFN%>126ORN%0THENN%=254-N%ELSEN%=-N%-2
394 IFO%=18THENE$="10"+RIGHT$("0"+HEX$(N%),2):RETURN
396 IFR1%=2THENR1%=28ELSEIFR1%>28THENRETURN
398 IFX%=2THENE$="18"ELSEE$=HEX$(32+8*(R1%-25))
400 E$=E$+RIGHT$("0"+HEX$(N%),2):RETURN
' Ensamblado de BIT / RES / SET
410 IFR2%>0ANDR2%=0ANDV1<8THENE$=E1$+"CB"+E2$+HEX$(64*(O%-18)+V1*8+R2%-1):RETURNELSERETURN ' Ensamblado de RLS / RRC / RL / RR / SLA / SRA / SRL 420 IFO%=28THENO%=29 422 IFR1%>0ANDR1%<9THENE$=E1$+"CB"+E2$+RIGHT$("0"+HEX$(8*(O%-22)+R1%-1),2):RETURNELSERETURN ' Ensamblado de IN / OUT 430 IFO%=29ANDR1%>0ANDR1%<9ANDR2%=22THENE$="ED"+HEX$(56+8*R1%):RETURN 432 IFO%=30ANDR2%>0ANDR2%<9ANDR1%=22THENE$="ED"+HEX$(57+8*R2%):RETURN 434 IFO%=29ANDR1%=8ANDP(7)THENE$="DB"+LEFT$(P$(5),2):RETURN 436 IFO%=30ANDR2%=8ANDP(6)THENE$="D3"+LEFT$(P$(4),2):RETURNELSERETURN ' Ensamblado de IM 440 IFV1>=0ANDV1<3THENE$="ED"+MID$("46565E",V1*2+1,2):RETURNELSERETURN ' Ensamblado de RST 450 IFV1>=0ANDV1<=56THENE$=MID$("C7CFD7DFE7EFF7FF",V1/4+1,2):RETURNELSERETURN
' Ensamblado de EX
460 IFR1%=10ANDR2%=11THENE$="EB":RETURN
462 IFR1%=15ANDR2%=19THENE$="08":RETURN
464 IFR2%=13ORR2%=14THENIFR2%=13THENE1$="DD":R2%=11ELSEE1$="FD":R2%=11
466 IFR1%=18ANDR2%=11THENE$=E1$+"E3":RETURNELSERETURN
' Ensamblado de DB / DW
470 IFLEFT$(L$(Z%,2),1)="'"THEN482
472 IFO%=34THENE$=LEFT$(P$(4),2)ELSEE$=P$(4)
474 IFL$(Z%,3)=""THENRETURNELSEA$=L$(Z%,3)+",":C$=""
476 FORF%=1TOLEN(A$)
478 IFMID$(A$,F%,1)<>","THENC$=C$+MID$(A$,F%,1)ELSEE$=E$+RIGHT$("0"+HEX$(VAL(C$)),2):C$=""
480 NEXT:RETURN
482 R%=0:A$=MID$(L$(Z%,2),2,LEN(L$(Z%,2))-2)
484 FORF%=1TOLEN(A$):E$=E$+RIGHT$("0"+HEX$(ASC(MID$(A$,F%,1))),2):NEXT:RETURN
' Ensamblado de ORG
490 R%=1:D=V1:B=D:E$="":RETURN
' Prompt de opciones del programa
700 LINE INPUT ">";A$:GOSUB720:O%=0
702 FOR F%=1TO13
704 IFMID$("EIDLASONQWFPX",F%,1)=P$(1)THENO%=F%:F%=99
706 NEXT
708 ONO%GOSUB740,760,780,800,870,820,840,904,910,850,880,800,890:GOTO700
' Separar los parámetros de la línea de entrada
720 X%=1:P$(1)="":P$(2)="":P$(3)=""
722 FORF%=1TOLEN(A$)
724 C$=MID$(A$,F%,1):IFC$<>" "ANDC$<>","THENP$(X%)=P$(X%)+C$:GOTO728
726 IFX%<3ANDP$(X%)<>""THENX%=X%+1ELSEIFX%=3THENP$(3)=P$(3)+MID$(A$,F%):F%=LEN(A$)
728 NEXT:RETURN
' Editor de líneas
740 F%=VAL(P$(2)):IFF%>0ANDF%<LF%+1THENLI%=F%
742 A$="":PRINTUSING"### ";LI%;:LINEINPUTA$:IFA$=""THENRETURNELSEGOSUB720
744 H$(LI%)="":IFLEFT$(A$,1)=":"THENL$(LI%,1)=LEFT$(P$(1),8):GOTO756
746 B$=P$(1):O%=0
748 FORF%=1TO70
750 IFB$=I$(F%)THENO%=F%:F%=99
752 NEXT
754 IFO%THENL$(LI%,1)=STR$(O%):L$(LI%,2)=P$(2):L$(LI%,3)=P$(3)ELSEPRINT"Syntax Error !!!":GOTO742
756 IFLI%<H%THENLI%=LI%+1ELSERETURN 758 IFLI%>LF%THENLF%=LF%+1:GOTO742ELSE742
' Insertar líneas en el programa
760 F%=VAL(P$(2)):N%=VAL(P$(3)):IFF%>LF%ORN%=0ORF%+N%>H%THENRETURNELSELF%=LF%+N%
762 FORI%=LF%TOF%+N%STEP-1:L$(I%,1)=L$(I%-N%,1):L$(I%,2)=L$(I%-N%,2):L$(I%,3)=L$(I%-N%,3):H$(I%)=H$(I%-N%):NEXT
764 F%=VAL(P$(2)):FORI%=F%TOF%+N%-1:L$(I%,1)="":L$(I%,2)="":L$(I%,3)="":H$(I%)="":NEXT:RETURN
' Eliminar líneas del programa
780 F%=VAL(P$(2)):N%=VAL(P$(3)):IFN%=0THENN%=F%
782 IFF%=0ORF%>LF%ORN%>LF%THENRETURNELSELF%=LF%-(N%-F%+1)
784 FORI%=F%TOLF%:N%=N%+1:L$(I%,1)=L$(N%,1):L$(I%,2)=L$(N%,2):L$(I%,3)=L$(N%,3):H$(I%)=H$(N%):NEXT:RETURN
' Listar/Imprimir el programa
800 F%=VAL(P$(2)):N%=VAL(P$(3)):IFF%=0ORF%>LF%THENF%=1:N%=LF%-1
802 IFN%=0THENN%=F%
804 IFN%>=LF%THENN%=LF%-1
805 IFO%=12THEN814
806 FORI%=F%TON%
808 PRINTUSING"### ";I%;:IFLEFT$(L$(I%,1),1)=":"THENPRINTL$(I%,1):GOTO812
810 PRINTTAB(14);I$(VAL(L$(I%,1)))+" "+L$(I%,2);:IFL$(I%,3)<>""THENPRINT",";L$(I%,3)ELSEPRINT
812 NEXT:RETURN
814 FORI%=F%TON%
815 LPRINTUSING"### ";I%;:IFLEFT$(L$(I%,1),1)=":"THENLPRINTL$(I%,1):GOTO817
816 LPRINTTAB(14);I$(VAL(L$(I%,1)))+" "+L$(I%,2);:IFL$(I%,3)<>""THENLPRINT",";L$(I%,3)ELSELPRINT
817 NEXT:RETURN
' Grabar el programa
820 IFP$(2)<>""THENN$=P$(2)+".ASM"
822 OPENN$FOROUTPUTAS#1
824 FORF%=1TOLF%-1
826 A$=L$(F%,1)+" "+L$(F%,2):IFL$(F%,3)<>""THENA$=A$+","+L$(F%,3)
828 IFL$(F%,1)<>""THENPRINT#1,A$
830 NEXT:CLOSE#1:RETURN
' Cargar el programa
840 IFP$(2)<>""THENN$=P$(2)+".ASM"
842 GOSUB906:OPENN$FORINPUTAS#1
844 IFEOF(1)THENCLOSE#1:LF%=LI%:RETURNELSELINEINPUT#1,A$:GOSUB720
846 L$(LI%,1)=P$(1):L$(LI%,2)=P$(2):L$(LI%,3)=P$(3):LI%=LI%+1:GOTO844
850 F%=VAL(P$(2)):IFF%>31ANDF%<81THENWIDTHF%:RETURNELSEWIDTH38:RETURN
' Rutina de PressKey
860 IFINKEY$=""THEN860ELSERETURN
' Número de pasadas al ensamblar
870 F%=VAL(P$(2)):IFF%=1ORF%=2THENZ1%=F%:Z2%=FELSEZ1%=1:Z2%=2
872 GOTO110
' Directorio de fuentes en el disquete
880 FILES"*.ASM":PRINT:RETURN
' Ejecutar el programa ensamblado
890 F=VAL(P$(2)):IFF=0THENF=B
892 DEFUSR=F:F=USR(0):RETURN
' Iniciar el ensamblador y cargar matrices
900 FORF%=1TO70:READI$(F%):NEXT:FORF%=1TO34:READV$(F%):NEXT
902 FORF%=1TO32:READR$(F%):NEXT:FORF%=1TO36:READJ%(F%):NEXT
903 FORF%=1TO10:READN%:KEYF%,I$(N%)+" ":NEXT
904 CLS:WIDTH38:PRINT"Z80 Assembler - (c) Scainet Soft, 2014":PRINT
906 LI%=1:LF%=1:Q%=0:FORF%=1TOH%:L$(F%,1)="":L$(F%,2)="":L$(F%,3)="":H$(F%)="":NEXT
908 RETURN:ONERRORGOTO912:RETURN
' Salir del ensamblador
910 END
912 PRINT "Internal Error.":PRINT:GOSUB908:RESUME700
' Datos de las Instrucciones del Z80 y sus códigos hexadecimales
950 DATA "LD","POP","PUSH","INC","DEC","ADD","ADC","SUB","SBC","AND","XOR","OR","CP","RET","CALL","JP","JR"
952 DATA "DJNZ","BIT","RES","SET","RLC","RRC","RL","RR","SLA","SRA","SRL","IN","OUT","IM","RST","EX","DB","DW"
954 DATA "ORG","DAA","CPL","NEG","HALT","EXX","RRCA","RLCA","RRA","RLA","RLD","RRD","LDI","LDIR","LDD","LDDR"
956 DATA "CPI","CPIR","CPD","CPDR","INI","INIR","IND","INDR","OUTI","OTIR","OUTD","OTDR","NOP","CCF","SCF"
958 DATA "EI","DI","RETI","RETN","27","2F","ED44","76","D9","0F","07","1F","17","ED6F","ED67","EDA0","EDB0"
960 DATA "EDA8","EDB8","EDA1","EDB1","EDA9","EDB9","EDA2","EDB2","EDAA","EDBA","EDA3","EDB3","EDAB","EDBB"
962 DATA "00","3F","37","FB","F3","ED4D","ED45","B","C","D","E","H","L","(HL)","A","BC","DE","HL","SP","IX"
964 DATA "IY","AF","(BC)","(DE)","(SP)","AF'","I","R","(C)","(IX)","(IY)","NZ","Z","NC","C","PO","PE","P","M"
' Datos de Salto o función según ID de instrucción
966 DATA 1,2,2,3,3,4,4,4,4,4,4,4,4,5,6,6,7,7,8,8,8,9,9,9,9,9,9,9,10,10,11,12,13,14,14,15
' Datos de las teclas de función
968 DATA 1,3,4,15,16,13,2,5,14,17
Apuntes finales
La historia
Este es un proyecto que me planteé por primera vez hace más de 25 años.
En mis inicios en el mundo de la informática, y con la ayuda de mi ZX-81, empecé a hacer mis pinitos en el mundo del código máquina. En aquellos tiempos ensamblaba a mano, y desarrollé sencillas rutinas que no usé para nada.
Casi cuatro años más tarde conseguí mi ansiado INVES SPECTRUM+ y después de desarrollar tres programas en BASIC me decidí a programar en ensamblador de una forma más seria. Por aquellos tiempos intentar conseguir un ensamblador no era una tarea fácil si no conocías a alguien que lo tuviera, que no era mi caso, o estuvieras dispuesto a pagar. Y aquí empezó mi primer intento por hacerme un ensamblador en BASIC. Después de una tarde ante interminables listas de IF lo dejé correr y pude conseguir una copia del GENS-3 con fotocopias del manual por el módico precio de 2.000 pesetas. Hice muchos programas con este estupendo ensamblador.
Unos tres años más tarde me hice con un COMMODORE-64, que usé básicamente para jugar, pero me llamó la atención descubrir en una revista un ensamblador de 6502 realizado íntegramente en BASIC. Me tomé la molestia de teclearlo, y realmente funcionaba, aunque era lento. Bueno, el BASIC del C-64 es así. Aún conservo este programa en cinta.
Más de 20 años después, analizando las listas de instrucciones y mnemotécnicos del Z80 vi que con unas fórmulas relativamente sencillas podías generar el código hexadecimal de grandes grupos de instrucciones. De esta investigación salió mi “K-Assembler”, un sencillo pero funcional ensamblador para el ZX-81 con 1K de memoria RAM. Ensamblaba algo más del 50% de los mnemotécnicos del Z80, pero era consciente que con un poco más de memoria en una máquina más rápida y con mejor BASIC podía hacer el ensamblador completo. Aquí tenéis el resultado.
Condiciones iniciales
Antes de ponerme ha desarrollar el ensamblador hice una lista de las condiciones mínimas que debía tener:
- Velocidad. Este tema es básico. No podía tardar más de un minuto en ensamblar las 250 líneas.
- Sencillo. El entorno de desarrollo debería ser cómodo y sencillo de usar.
- Profesional. Debía poder ensamblar listados aparecidos en revistas realizados con otros ensambladores.
Por suerte y tras mucho análisis he conseguido cumplir con las tres condiciones.
Cosillas del BASIC de los MSX
El MSX reserva por defecto 200 bytes para almacenar cadenas de texto. En este caso es absolutamente insuficiente.
Con el “CLEAR 4096” de la línea 100 reservo 4 KB de RAM para poder introducir las distintas matrices y líneas del editor.
Es muy importante poner al principio del programa en BASIC las líneas más usadas ya que el intérprete BASIC las localiza más rápidamente. Por otro lado he eliminado todos los espacios que hay en el listado porque la ejecución es más rápida y el programa ocupa menos memoria.
He apreciado que, a veces, el programa se queda “paralizado” durante unos segundos durante el proceso de ensamblado. He llegado a la conclusión de que el intérprete va rellenando secuencialmente la memoria reservada a las cadenas y que cuando llega al final hace una especie de compactación, eliminando los espacios vacíos que puedan haber quedado al ir concatenando valores.
Con todo esto, el programa ocupa unos 7 KB y necesita unos 10 KB más para almacenar variables y matrices. Quedan así unos 12 KB libres para almacenar el programa en código máquina.
Velocidad. ¿Como se consigue? Este ensamblador puede llegar a ensamblar más de 300 líneas de código por minuto. Conseguirlo no ha sido fácil.
Un ensamblador suele funcionar haciendo dos pasadas sobre el código fuente. La primera pasada sirve para calcular la dirección de memoria de las etiquetas que hay en el programa y la segunda para generar el código máquina.
Analizando el juego de instrucciones del Z80 vemos que hay de dos tipos: con parámetros y sin parámetros. Así que la primera decisión ha consistido en ordenar la lista de instrucciones en una matriz. Primero las que usan parámetro y después el resto.
Analizando las tablas de ensamblado vemos que se pueden agrupar ciertas instrucciones, ya que un mismo algoritmo puede tener un uso común. La segunda decisión ha consistido en agrupar las instrucciones en 15 grupos, poniendo primero las más usadas.
Para las instrucciones que no usan parámetro he creado una matriz donde guardo su código hexadecimal correspondiente, ya que siempre es el mismo. A la hora de ensamblar el proceso es directo ya que no han de usar ningún algoritmo.
En los listados hay dos tipos de líneas: las que usan etiquetas en algún parámetro y las que no. De esta forma, en la primera pasada del proceso de ensamblado ensamblo las líneas que no usan etiquetas mientras calculo la dirección de memoria de las etiquetas. El código de ensamblado de estas líneas que no usan etiquetas se guarda en una matriz y salvo que se modifiquen no se vuelven a calcular más. En la segunda pasada solo ensamblo las instrucciones que usan etiquetas. Esto ha sido básico para el buen rendimiento del ensamblador.
Las líneas pueden tener hasta dos parámetros y pueden ser de distintos tipos. Los he procesado según su importancia:
- ¿Va entre paréntesis? En caso afirmativo lo quito.
- ¿El valor es un número decimales o hexadecimales?
- ¿El parámetro es un registro?
- ¿El parámetro es una etiqueta?
- ¿El parámetro es un registro de índice?
Al introducir una línea en el programa identifico la instrucción. Si existe me guardo su identificado para evitar tener que identificarla posteriormente. Esto me ha servido para ahorrar memoria y acelerar el proceso de ensamblado. Si la instrucción no existe da un error y la línea no se guarda.
He creado una matriz donde indico la subrutina que usa cada una de las instrucciones con parámetro. De esta forma he podido usar un ON GOSUB en lugar de una lista de IF, y un RETURN se procesa más rápido que un GOTO y ocupa menos memoria.
Durante la ejecución de los algoritmos de ensamblado, que los hay más simples y más complicados, a la que se detecta el método correcto se fuerza un RETURN para evitar procesar más líneas de las necesarias.
La verificación de la línea que se intenta ensamblar está parcialmente desactivada. Esto quiere decir que si la instrucción es correcta el código generado es correcto, pero si la instrucción no es correcta (por ejemplo, “ADD B,A” no existe) el ensamblador puede generar un código incorrecto o aproximado (según el ejemplo anterior haría un “ADD A,B”).
Al ensamblar el programa hay la opción de indicar que solo se haga una pasada. Esto es especialmente útil si el programa no usa etiquetas o el código añadido no afecta a la dirección de memoria de éstas. De esta forma se mejora la velocidad.
Con todo esto, la primera vez que se ensambla el proceso es más lento porque realmente se ensamblan todas las líneas, a una velocidad de unas 100 líneas por minuto, pero a partir de entonces y en función de las instrucciones del programa, el uso o no de etiquetas y la opción de ensamblado, el proceso se acelera considerablemente pudiendo llegar a velocidades teóricas de hasta 350 líneas por minuto.
Buscando los algoritmos de ensamblado
No todas las instrucciones son igual de complejas de ensamblar. La instrucción más compleja es LD que requiere de múltiples líneas de código con múltiples condiciones. En cambio hay otras instrucciones que se ensamblan con una única línea de código, que además puede ser compartida con otras instrucciones.
Analizando la tabla de la instrucción LD de transferencias de 8 bits se puede ver que los códigos están comprendidos entre 40h (64) y 7Fh (127). Empieza en la columna “B” y acaba en la columna “A”, y cada fila que bajamos se incrementa en 8.
A partir de la codificación de registros que he usado, con la fórmula: “64 + 8 * IDreg1 + IDreg2” podemos ensamblar fácilmente 63 de los 132 mnemotécnicos distintos de la instrucción LD.
Para poder ensamblar el resto hace falta más código, aunque en general siempre siguen unos patrones definidos.
El entorno de desarrollo
Todo el ensamblador es muy sencillo de usar. El proceso de introducir líneas, modificar o borrarlas, ensamblar y ejecutar los programas se realiza sin ningún problema.
Con el comando “W” podemos adaptar el formato de pantalla a nuestro gusto, admitiendo un formato entre 32 y 80 columnas, y con el comando “P” podemos listar el código fuente por la impresora.
Por otro lado se han adaptado las 8 teclas de función con algunas de las instrucciones más habituales del Z80.
El programa en BASIC esta comprendido entre las líneas 100 y 950, pudiendo añadir nuevas líneas que pueden ser necesarias para la ejecución del programa en código máquina que estamos desarrollando.
Para terminar
He probado el ensamblado con distintas de rutinas en CM que he encontrado en revistas y libros, el código hexadecimal generado coincide y la ejecución es correcta. Además es bastante rápido. Así que… Prueba superada ¡!!
El código fuente es muy fácilmente adaptable a cualquier equipo de disponga de un Microsoft BASIC o compatible. Me atrevo a decir que casi tal cual se podría teclear en un AMSTRAD CPC y funcionaría perfectamente.
Mi próximo proyecto consiste en adaptar el programa a un ORIC y ensamblar las instrucciones del 6502. A ver si hay suerte…
Os invito a probarlo.