-
Notifications
You must be signed in to change notification settings - Fork 1
Home
El lenguaje Forth siempre me ha atraido mucho, sobre todo porque está ligado a los niveles más bajos del software. He encontrado muy inspirador a este proyecto: AlexForth6809, de Alexandre Dumont en el que está implementando, desde 0, un intérprete de Forth. Lo interesante es que lo está documentado día a día, de manera que es muy fácil aprender su funcionamiento
Esa es justo la mejor manera de entender Forth: programarte tu propio intérprete. Y eso es justo lo que voy a hacer yo usando un RISC-V. La implementación es la excusa. Durante el camino quiero ir aprendiendo todos los conceptos de forth, que son muchos (y las siglas!!!)
Para practicar con el lenguaje se puede utilizar el Forth de GNU: gforth
Desde ubuntu se instala muy fácilmente así:
sudo apt install gforth
Un programa muy sencillo que podemos hacer en forth es el que realice la suma 1+1, y muestre el resultado en la pantalla. El programa en cuestión es este:
1 1 + .Y desde el Gforth se prueba así:
$ gforth
Gforth 0.7.3, Copyright (C) 1995-2008 Free Software Foundation, Inc.
Gforth comes with ABSOLUTELY NO WARRANTY; for details type `license'
Type `bye' to exit
1 1 + . 2 okObservamos que a la derecha del . ha aparecido esto: 2 ok. El dos es el resultado de la suma y el ok es el mensaje de forth indicando que se han ejecutado las instrucciones correctamente
Este programa de Forth está formado por 4 palabras. Cada palabra hace una acción:
-
1: Introduce el numero 1 en la pila -
+: Lee de la pila los siguientes dos operandos, los suma y deja el resultado en la pila -
.: Saca de la pila el último valor y lo imprime en la consola
Vamos a empezar a construir nuestro intérprete forth desde 0, para el RISC-V. Vamos a utilizar inicialmente el simulador RARs. Tiene la ventaja de que nos premite validar nuestro código, y depurarlo fácilmente. Otra ventaja es que dispone de Entrada/salida. El principal inconveniente es que NO NOS PERMITE MEZCLAR DATOS CON INSTRUCCIONES, por lo se tienen que hacer en segmetnos separados. En muchas implementaciones de forth el código máquina de la instrucciones está dentro del propio diccionario, junto a los datos. Esto ya lo solventaremos más adelante
Nuestro objetivo inicial será implementar el programa forth 1 1 + . directamente en esamblador, intentando imitar lo que el intérprete hace. El programa, en realidad es sencillo, basta con realizar 4 llamadas consecutivas. Una a la subrutina que implementa la palabra 1, a la que habrá que llamar 2 veces seguidas, luego llamar a la subrutina de suma y por último a la que implementa la palabra .. Y aquí acaba el programa.
En esta primera versión tendríamos el siguiente programa:
.text
jal do_1 #-- Ejecutar la palabra 1
jal do_1 #-- Ejecutar la palabra 1 (otra vez)
jal do_add #-- Ejecutar la palabra +
jal do_point #-- Ejecutar la palabra .
#-- TerminarLa función do_1() mete el número 1 en la pila. Por ello tenemos que definir primero la pila. Vamos a utilizar una pila pequeña, inicialmente de 4 bytes, para poder depurar fácilmente. Esta pila metemos en el segmento de datos
.data
#-----------------------
#-- PILA
#----------------------
.space 4
stack:En nuestra implementación en el rars usamos el modelo compacto de memoria, donde el código se sitúa a partir de la dirección 0 y los datos a partir de la 0x2000. Por tanto la pila ocupa las posiciones 0x2000, 0x2001, 0x2002 y 0x2003. Inicialmente el puntero de pila apunta a la dirección 0x2004. Este valor indica que LA PILA ESTÁ VACIA
Esto es lo que se hace nada más comenzar el programa: inicializar el puntero de pila. Este es el código:
#-- Inicializar la pila
la sp, stackLa función do_1 inserta el número 1 en la pila. Como es el primer elemento, se situará en la dirección 0x2003. Para ello, esta función debe realizar las siguientes operaciones:
- Decrementar el puntero de pila (sp) en 1 byte
- Almacenar el valor 1 en la dirección indicada por sp
Esta es la implementación en RISC-V:
#---------------
#-- Palabra 1
#--
#-- Meter 1 en la pila (PUSH 1)
#---------------
do_1:
#-- Crear espacio en la pila para un byte
addi sp,sp,-1
#-- Guardar el 1 en la pila
li t0, 1
sb t0, 0(sp)
#-- Hemos terminado
retAhora implementamos la operación de suma. Las operaciones que debe hacer son:
- Obtener el siguiente elemento de la pila
- Incrementar el puntero de pila en 1 byte
- Obtener el siguiente elemento de la pila
- Incrementar el puntero de pila en 1 byte
- Realizar la suma de los elementos obtenidos
- Depositar el resultado en la pila, para lo cual debe decrementar primero sp en 1
Esta es la implementacion:
#---------------
#-- Palabra +
#--
#-- Obtener los dos ultimos elementos de la pila,
#-- sumarlos y depositar el resultado en la pila
#---------------
do_add:
#-- Leer el primer elemento
lb t0, 0(sp)
addi sp,sp,1
#-- Leer segundo elemento
lb t1, 0(sp)
addi sp,sp,1
#-- Realizar la suma
add t0, t0,t1
#-- Guardar resultado en la pila
addi sp,sp,-1
sb t0, 0(sp)
#-- Hemos terminado
retY ya por último, para imprimir el resultado basta con leer el elemento de la pila (y luego incrementar sp), y llamar al sistema operativo del simulador para imprimir el resultado. Esta es la implementación:
#-------------------------
#-- Palabra .
#--
#-- Sacar el ultimo elemento de la pila e
#-- imprimirlo
#-------------------
do_point:
#-- Sacar el elemento de la pila
lb t0, 0(sp)
addi sp,sp,1
#-- Imprimirlo
mv a0, t0
li a7, PRINT_INT
ecall
retEste es el programa final, que está almacenado el fichero 01-suma-1-1.s:
#--------------------------------------------------------------------
#-- INTERPRETE DE FORTH. Version 1
#--
#-- Implementación en ensamblador del programa Forth:
#-- 1 1 + .
#--
#-- El resultado se imprime en la consola del simulador
#-- Una vez completado, termina
#--------------------------------------------------------------------
#-- Servicios del sistema operativo
.eqv PRINT_INT 1
.eqv EXIT 10
.eqv PRINT_STRING 4
.text
#-- Inicializar la pila
la sp, stack
#-- Programa Forth: 1 1 + .
jal do_1
jal do_1
jal do_add
jal do_point
#-- Interprete de forth: Imprimir " ok"
la a0, ok_msg
li a7, PRINT_STRING
ecall
#-- Terminar
li a7, EXIT
ecall
#----------------------------------------------------------------
#-- Implementacion de las palabras primitivas
#----------------------------------------------------------------
#---------------
#-- Palabra 1
#--
#-- Meter 1 en la pila (PUSH 1)
#---------------
do_1:
#-- Crear espacio en la pila para un byte
addi sp,sp,-1
#-- Guardar el 1 en la pila
li t0, 1
sb t0, 0(sp)
#-- Hemos terminado
ret
#---------------
#-- Palabra +
#--
#-- Obtener los dos ultimos elementos de la pila,
#-- sumarlos y depositar el resultado en la pila
#---------------
do_add:
#-- Leer el primer elemento
lb t0, 0(sp)
addi sp,sp,1
#-- Leer segundo elemento
lb t1, 0(sp)
addi sp,sp,1
#-- Realizar la suma
add t0, t0,t1
#-- Guardar resultado en la pila
addi sp,sp,-1
sb t0, 0(sp)
#-- Hemos terminado
ret
#-------------------------
#-- Palabra .
#--
#-- Sacar el ultimo elemento de la pila e
#-- imprimirlo
#-------------------
do_point:
#-- Sacar el elemento de la pila
lb t0, 0(sp)
addi sp,sp,1
#-- Imprimirlo
mv a0, t0
li a7, PRINT_INT
ecall
ret
#---------------------------------
#-- SEGMENTO DE DATOS
#---------------------------------
.data
#-----------------------
#-- PILA
#----------------------
.space 4
stack:
#---- Mensajes del interprete
ok_msg: .string " ok\n"Este programa se prueba así desde la consola:
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-05-23
$ ./run.sh 01-suma_1_1.s
2 ok
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-05-23
$ Vemos que sale el mensaje 2 ok. ¡Ha funcionado!
Todavía no estamos ejecutando programas fuente en forth, sino que los estamos "compilando" a pelo e implementándolos directamente en ensamblador. Pero... ¡Ya podemos ejecutar programas forth! Siempre y cuando hayamos implementado previamente las palabras indicadas
Vamos a probar otro programa:
1 1 1 + + .Este programa suma los tres unos y muestra el resultado en la consola (3). Lo probamos primero en gforth:
$ gforth
Gforth 0.7.3, Copyright (C) 1995-2008 Free Software Foundation, Inc.
Gforth comes with ABSOLUTELY NO WARRANTY; for details type `license'
Type `bye' to exit
1 1 1 + + . 3 okPara implementarlo con nuestro intérprete lo "compilados" a pelo, realizando las llamadas necesarias. En nuestro caso, el programa forth traducido sería este (el resto de la implementación será igual que en 01-suma-1-1.s:
#-- Programa Forth: 1 1 1 + + .
jal do_1
jal do_1
jal do_1
jal do_add
jal do_add
jal do_pointEl programa completo está en el fichero 02-suma-1-1-1.s. Esto es lo que ocurre al ejecutarlo:
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-05-23
$ ./run.sh 02-suma_1_1_1.s
3 ok
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-05-23
$ En las implementaciones de Forth hay muchas instrucciones que se repiten, y que tienen una semántica específica en el intérprete de Forth, por lo que se agrupan en MACROS. Vamos a definir macros para las diferentes partes
Por un lado tenemos las macros usadas para acceder al sistema operativo:
- EXIT: Terminar
- PRINT_T0: Imprimir el valor del registro t0
- PRINT_STRING: Imprimir una cadena en la consola
Por otro lado están las macro usadas para la implementación de las palabras primitivas
- PUSH_BYTE (valor): Meter valor en la pila
- PUSH_T0: Meter el registro t0 en la pila
- POP_T0: Recuperar el registro t0 de la pila
Las implementaciones de las primitivas están en el fichero primitives.s. Usando las macros definidas anteriormente, quedan así:
#----------------------------------------------------------------
#-- Implementacion de las palabras primitivas
#----------------------------------------------------------------
.globl do_1, do_add, do_point
.include "macros.h"
.text
#---------------
#-- Palabra 1
#--
#-- Meter 1 en la pila (PUSH 1)
#---------------
do_1:
#-- Guardar el 1 en la pila
PUSH_BYTE (1)
#-- Hemos terminado
ret
#---------------
#-- Palabra +
#--
#-- Obtener los dos ultimos elementos de la pila,
#-- sumarlos y depositar el resultado en la pila
#---------------
do_add:
#-- Leer el primer elemento en t1
POP_T0
mv t1,t0
#-- Leer segundo elemento
POP_T0
#-- Realizar la suma
add t0, t0,t1
#-- Guardar resultado en la pila
PUSH_T0
#-- Hemos terminado
ret
#-------------------------
#-- Palabra .
#--
#-- Sacar el ultimo elemento de la pila e
#-- imprimirlo
#-------------------
do_point:
#-- Sacar el elemento de la pila
POP_T0
#-- Imprimirlo
PRINT_T0
retLo interesante es que ahora el programa principal, donde está nuestro programa "Forth compilado", es mucho más sencillo:
#--------------------------------------------------------------------
#-- INTERPRETE DE FORTH. Version 3
#--
#-- Implementación en ensamblador del programa Forth:
#-- 1 1 + .
#--
#-- El resultado se imprime en la consola del simulador
#-- Una vez completado, termina
#--------------------------------------------------------------------
.include "macros.h"
.text
#-- Inicializar la pila
la sp, stack
#-- Programa Forth: 1 1 + .
jal do_1
jal do_1
jal do_add
jal do_point
#-- Interprete de forth: Imprimir " ok"
PRINT_STRING (" ok\n")
#-- Terminar
EXIT
#---------------------------------
#-- SEGMENTO DE DATOS
#---------------------------------
.data
#-----------------------
#-- PILA
#----------------------
.space 4
stack:De esta forma, podemos ir definiendo TODAS las primitivas necesarias. En este enlace, hay una serie de artículos muy buenos sobre las implementaciones de Forth: http://www.bradrodriguez.com/papers/
En esta parte MOVING FORTH Part 5: The Z80 Primitives se explica una implementación para el Z80 de las primitivas. Todas las primitivas están definidas aquí: http://www.bradrodriguez.com/papers/camel80.txt
En esta tabla están resumidas: http://www.bradrodriguez.com/papers/glosslo.txt
Vamos a definir una nueva instrucción: add3, que suma los 3 elementos que haya en la pila. Esta es la definición en Forth:
: add3 + + ;Ahora creamos un programa nuevo que usa esta nueva instrucción:
1 1 1 add3 .
El resultado en gforh es este:
$ gforth
Gforth 0.7.3, Copyright (C) 1995-2008 Free Software Foundation, Inc.
Gforth comes with ABSOLUTELY NO WARRANTY; for details type `license'
Type `bye' to exit
: add3 + + ; ok
1 1 1 add3 . 3 okVamos a implementar ese programa en nuestro intérprete en ensamblador. Una posibilidad sería implementar add3 directamente como una primitiva. Pero dado que add3 en realidad lo que hace es llamar dos veces a la operación de suma, lo vamos a definir de esa forma
Add3 es ahora un subrutina intermedia, que llama a otras dos subrutinas. Por tanto, la direccion de retorno la tenemos que guardar en OTRA PILA (No es la pila de datos). Esta pila se llama rstack (return stack) y contiene las direcciones de retorno de las subrutinas superiores. Vamos a utilizar el registro s0 como puntero de esta pila
La definimos así:
.data
#-----------------------
#-- PILA de Datos
#----------------------
.space 4 #-- Tamaño 4 bytes
stack:
#-----------------------
#-- PILA de retorno
#-- Elementos de 32 bits
#-----------------------
.align 2 #-- Alinear a palabra
.space 16 #-- Tamaño: 4 palabras
rstack:El nuevo programa forth a ejecutar es este:
#-- Programa Forth: 1 1 1 add3 .
jal do_1
jal do_1
jal do_1
jal do_add3
jal do_pointPara que funcione hay que inicializar primero la pila de retorno:
.text
#-- Inicializar la pila de datos
la sp, stack
#-- Inicializar la pila de retorno
la s0, rstackY ahora hay que definir la función do_add3:
#--------------------------------
#-- Palabras de nivel superior
#--------------------------------
do_add3:
#-- Guardar direccion de retorno en la pila r
addi s0,s0,-4
sw ra, 0(s0)
#-- Llamar a las palabras + +
jal do_add
jal do_add
#-- Recuperar la direccion de retnor de la pila r
lw ra, 0(s0)
addi s0,s0,4
#-- Devolver control
ret El programa completo se encuentra en el fichero 04-add3-1-1-1.s. Y al ejecutarlo desde la consola esto es lo que se obtiene:
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-05-23
$ ./run.sh 02-suma_1_1_1.s
3 ok
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-05-23
$Vamos a hacer lo mismo que antes... definir macros para las instrucciones más comunes. También nos llevamos add3 al fichero higher_level.s
Estas son las nuevas macros definidas:
- PUSH_RA: Almacenar dirección de retorno en rstack
#-- Guardar direccion de retorno en rstack
.macro PUSH_RA
addi s0,s0,-4
sw ra,0(s0)
.end_macro- POP_RA: Recuperar la dirección de retorno de rstack
#-- Repucerar direccion de retorno de rstack
.macro POP_RA
lw ra,0(s0)
addi s0,s0,4
.end_macroEl nuevo programa queda así:
#--------------------------------------------------------------------
#-- INTERPRETE DE FORTH. Version 5
#--
#-- Implementación en ensamblador del programa Forth:
#-- 1 1 1 add3 .
#--
#-- Donde ahora add3 es una instrucción de nivel superior, definida
#-- a partir de palabras primitivas
#--
#-- El resultado se imprime en la consola del simulador
#-- Una vez completado, termina
#--------------------------------------------------------------------
.include "macros.h"
.text
#-- Inicializar la pila de datos
la sp, stack
#-- Inicializar la pila de retorno
la s0, rstack
#-- Programa Forth: 1 1 1 add3 .
jal do_1
jal do_1
jal do_1
jal do_add3
jal do_point
#-- Interprete de forth: Imprimir " ok"
PRINT_STRING (" ok\n")
#-- Terminar
EXIT
#---------------------------------
#-- SEGMENTO DE DATOS
#---------------------------------
.data
#-----------------------
#-- PILA de Datos
#----------------------
.space 4 #-- Tamaño 4 bytes
stack:
#-----------------------
#-- PILA de retorno
#-- Elementos de 32 bits
#-----------------------
.align 2 #-- Alinear a palabra
.space 16 #-- Tamaño: 4 palabras
rstack:Se encuentra en el fichero 05-add3-1-1-1.s
Con esta filosofía, si se implementan las funciones primitivas necesarioas y las de alto nivel, se puede tener un primer intérprete externo. Es lo que voy a intentar hacer. De momento este intérprete sólo ejecutará palabras de forth... no permitirá crear nuevas... Iré creando las cosas a medida que las vaya necesitando
Voy a ecar un vistazo al código del libro "Moving forth"....
La implementación comienza con este programa: http://www.bradrodriguez.com/papers/camel80.txt. El programa principal es este...
; CP/M ENTRY POINT
org 100h
reset: ld hl,(6h) ; BDOS address, rounded down
ld l,0 ; = end of avail.mem (EM)
dec h ; EM-100h
ld sp,hl ; = top of param stack
inc h ; EM
push hl
pop ix ; = top of return stack
dec h ; EM-200h
dec h
push hl
pop iy ; = bottom of user area
ld de,1 ; do reset if COLD returns
jp COLD ; enter top-level Forth word
Lo importante de este código es que la palabra de alto nivel que se invoca es COLD. Al probarla desde gforth efectivamente el intérprete se reinicia... y buscando información efectivamente sirva para reiniciar el sistema...
Voy a buscar la definición de COLD en el códig. En el listado anterior, de las primitivas NO está, porque claro, es una palabra de más alto nivel. Lo voy a buscar aquí: http://www.bradrodriguez.com/papers/camel80h.txt. Está definida justo al final:
;Z COLD -- cold start Forth system
; UINIT U0 #INIT CMOVE init user area
; 80 COUNT INTERPRET interpret CP/M cmd
; ." Z80 CamelForth etc."
; ABORT ;
head COLD,4,COLD,docolon
DW UINIT,U0,NINIT,CMOVE
DW LIT,80h,COUNT,INTERPRET
DW XSQUOTE
DB 35,'Z80 CamelForth v1.01 25 Jan 1995'
DB 0dh,0ah
DW TYPE,ABORT ; ABORT never returns
No soy capaz de entenderlo.... en fin... todavía me queda mucho por aprender...
Como me queda mucho por aprender, voy a hacerlo al revés: partir de los más pequeño e ir entendiéndolas e implementándolas. Esta es la lista completa de palabras primitivas:
TABLE 1. GLOSSARY OF WORDS IN CAMEL80.AZM
Words which are (usually) written in CODE.
NAME stack in -- stack out description
Guide to stack diagrams: R: = return stack,
c = 8-bit character, flag = boolean (0 or -1),
n = signed 16-bit, u = unsigned 16-bit,
d = signed 32-bit, ud = unsigned 32-bit,
+n = unsigned 15-bit, x = any cell value,
i*x j*x = any number of cell values,
a-addr = aligned adrs, c-addr = character adrs
p-addr = I/O port adrs, sys = system-specific.
Refer to ANS Forth document for more details.
ANS Forth Core words
These are required words whose definitions are
specified by the ANS Forth document.
! x a-addr -- store cell in memory
+ n1/u1 n2/u2 -- n3/u3 add n1+n2
+! n/u a-addr -- add cell to memory
- n1/u1 n2/u2 -- n3/u3 subtract n1-n2
< n1 n2 -- flag test n1<n2, signed
= x1 x2 -- flag test x1=x2
> n1 n2 -- flag test n1>n2, signed
>R x -- R: -- x push to return stack
?DUP x -- 0 | x x DUP if nonzero
@ a-addr -- x fetch cell from memory
0< n -- flag true if TOS negative
0= n/u -- flag return true if TOS=0
1+ n1/u1 -- n2/u2 add 1 to TOS
1- n1/u1 -- n2/u2 subtract 1 from TOS
2* x1 -- x2 arithmetic left shift
2/ x1 -- x2 arithmetic right shift
AND x1 x2 -- x3 logical AND
CONSTANT n -- define a Forth constant
C! c c-addr -- store char in memory
C@ c-addr -- c fetch char from memory
DROP x -- drop top of stack
DUP x -- x x duplicate top of stack
EMIT c -- output character to console
EXECUTE i*x xt -- j*x execute Forth word 'xt'
EXIT -- exit a colon definition
FILL c-addr u c -- fill memory with char
I -- n R: sys1 sys2 -- sys1 sys2
get the innermost loop index
INVERT x1 -- x2 bitwise inversion
J -- n R: 4*sys -- 4*sys
get the second loop index
KEY -- c get character from keyboard
LSHIFT x1 u -- x2 logical L shift u places
NEGATE x1 -- x2 two's complement
OR x1 x2 -- x3 logical OR
OVER x1 x2 -- x1 x2 x1 per stack diagram
ROT x1 x2 x3 -- x2 x3 x1 per stack diagram
RSHIFT x1 u -- x2 logical R shift u places
R> -- x R: x -- pop from return stack
R@ -- x R: x -- x fetch from rtn stk
SWAP x1 x2 -- x2 x1 swap top two items
UM* u1 u2 -- ud unsigned 16x16->32 mult.
UM/MOD ud u1 -- u2 u3 unsigned 32/16->16 div.
UNLOOP -- R: sys1 sys2 -- drop loop parms
U< u1 u2 -- flag test u1<n2, unsigned
VARIABLE -- define a Forth variable
XOR x1 x2 -- x3 logical XOR
ANS Forth Extensions
These are optional words whose definitions are
specified by the ANS Forth document.
<> x1 x2 -- flag test not equal
BYE i*x -- return to CP/M
CMOVE c-addr1 c-addr2 u -- move from bottom
CMOVE> c-addr1 c-addr2 u -- move from top
KEY? -- flag return true if char waiting
M+ d1 n -- d2 add single to double
NIP x1 x2 -- x2 per stack diagram
TUCK x1 x2 -- x2 x1 x2 per stack diagram
U> u1 u2 -- flag test u1>u2, unsigned
Private Extensions
These are words which are unique to CamelForth.
Many of these are necessary to implement ANS
Forth words, but are not specified by the ANS
document. Others are functions I find useful.
(do) n1|u1 n2|u2 -- R: -- sys1 sys2
run-time code for DO
(loop) R: sys1 sys2 -- | sys1 sys2
run-time code for LOOP
(+loop) n -- R: sys1 sys2 -- | sys1 sys2
run-time code for +LOOP
>< x1 -- x2 swap bytes
?branch x -- branch if TOS zero
BDOS DE C -- A call CP/M BDOS
branch -- branch always
lit -- x fetch inline literal to stack
PC! c p-addr -- output char to port
PC@ p-addr -- c input char from port
RP! a-addr -- set return stack pointer
RP@ -- a-addr get return stack pointer
SCAN c-addr1 u1 c -- c-addr2 u2
find matching char
SKIP c-addr1 u1 c -- c-addr2 u2
skip matching chars
SP! a-addr -- set data stack pointer
SP@ -- a-addr get data stack pointer
S= c-addr1 c-addr2 u -- n string compare
n<0: s1<s2, n=0: s1=s2, n>0: s1>s2
USER n -- define user variable 'n'
Voy a empezar por una muy necesaria lit. Con esta metemos un número en la pila. Tal y como estoy implementando el intérprete, el concept "literal" no tiene mucho sentido porque en realidad mis programas forth son llamadas a subrutinas.
Esta es la implementación:
#-----------------------------------
#-- Meter un literal en la pila
#-- a0: Literal a meter en la pila
#-----------------------------------
do_lit:
mv t0,a0
PUSH_T0
retY así queda el programa principal 06-lit.s
#-- Programa Forth: 2 3 + .
li a0, 2
jal do_lit #-- 2
li a0, 3
jal do_lit #-- 3
jal do_add #-- +
jal do_point #-- .La siguiente que voy a implementar es EMIT. En la implementación del Z80 lo tiene dividido en dos partes: Una genérica BDOS para hacer llamadas al sistema operativo, y luego otra que usa esa para imprimir el carácter.
Yo lo voy a hacer en una única función, por simplicidad
El programa en forth es este: 65 emit. Esto es lo que debe salir:
$ gforth
Gforth 0.7.3, Copyright (C) 1995-2008 Free Software Foundation, Inc.
Gforth comes with ABSOLUTELY NO WARRANTY; for details type `license'
Type `bye' to exit
65 emit A okHe aprovecharo para definir macros TAMBIEN para las instrucciones FORTH, en vez de usar jal. El programa con el emit queda ahora así (07-emit.s)
#-- Programa Forth: 65 emit
LIT(65)
EMITEste es el resultado al probarlo en consola:
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-05-23
$ ./run.sh 07-emit.s
A ok
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-05-23
$Con las nuevas macros ahora es mucho más fácil hacer los programas en Forth :-)
Este serie el programa para imprimir "HOLA":
72 emit 79 emit 76 emit 65 emit
Lo probamos en gforth:
$ gforth
Gforth 0.7.3, Copyright (C) 1995-2008 Free Software Foundation, Inc.
Gforth comes with ABSOLUTELY NO WARRANTY; for details type `license'
Type `bye' to exit
72 emit 79 emit 76 emit 65 emit HOLA okEste es el programa Forth en el RISC-V:
#-- Programa Forth:
LIT(72)
EMIT
LIT(79)
EMIT
LIT(76)
EMIT
LIT(65)
EMITY este el resultado de su ejecución:
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-05-23
$ ./run.sh 08-emit-hola.s
HOLA ok
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-05-23
$
Ya tengo la palabra KEY para leer un carácter del teclado, sin embargo funciona un poco distinto al del forth estándar porque el rars se comporta distinto. En forth, al llamar a key y que el usuario pulse la tecla NO SE HACE ECO de la tecla pulsada. Sin embargo en el RARs sí
También en el RARs, cuando se ejecuta en consola, para introducir 1 carácter hay que apretarlo y además pulsar ENTER
Pero por lo de más está funcionando...
Listo! Ya lo tengo implementado en el ejemplo 10-store.s. He aprovechado para hacer la pila de elementos de 32-bits, en vez de bytes, para poder almacenar direcciones y valores grandes
Para aprender más forth voy a leer este libro:
Este programa borra la pantalla (con el código ansi de borrar la pantalla)
74 50 91 27 emit emit emit emitEste es el mismo, pero puesto en orden:
27 emit 91 emit 50 emit 74 emitCon este comando cambiamos el color a verde
27 emit 91 emit 51 emit 50 emit 109 emitY con este se hace home del cursor:
27 emit 91 emit 72 emitEl comando home lo podemos redefinir así:
: home 27 emit 91 emit 72 emit ; oky el comando CLS como un borrado de pantalla y un home
: cls 27 emit 91 emit 50 emit 74 emit home ;Este es para cambiar el color a verde
: green 27 emit 91 emit 51 emit 50 emit 109 emit ;Lo metemos en un único programa para utilizarlo más fácilmente
( Definiciones para secuencias ANSI )
: home 27 emit 91 emit 72 emit ;
: cls 27 emit 91 emit 50 emit 74 emit home ;
: green 27 emit 91 emit 51 emit 50 emit 109 emit ;
green
cls
Sigo implementando palabras primitivas. Ya tengo todas las de comparación... Las que quedan de comparación no son primitiva, sino implementadas a partir de las otras. Lo implementaré más adelante... ahora me estoy centrando en la implementación en ensamblador
Sigo con más implementaciones de palabras primitivas
He re-implementado la palabra LIT para introducir literales, y que sea más parecida a la implementación del Z80 de CamelForth. Ahora se hace en dos partes:
- LIT --> Llamada a la funcion do_lit
- DW(3) --> Macro que inserta el numero 3 en el código
En realidad, en el Rars no se puede insertar datos dentro del código, sin embargo he hecho un HACK temporal. El dato (de 20-bits como máximo) está en los 20-bits de mayor peso de a instrucción lui.
¿Cómo sabe LIT cómo acceder a la constante? LIT es una llamada a la subrutina do_lit, que hace el trabajo. Bien, en ella sabemos que ra apunta a la siguiente instrucción a LIT, es decir, que ra contiene la dirección del literal. Basta con leer esa información y luego incrementar ra en 4 antes de retornar para que se ejecute la siguiente instrucción del Thread Forth
Lo he implementado y FUNCIONA GENIAL!!!!
Ya tengo implementado también el Branch!!
Ya tengo implementado el qbranch! Ya se pueden hacer saltos condicionales....
Ya tengo las instrucciones DO - LOOP! por lo que ya se pueden hacer bucles... de momento el código tiene que estar compilado Lo que hace DO es inicializar el bucle. Se leen de la pila el límite y el índice y se guardan en la pila R, que se usa de manera interna. Loop se encarga de incrementar el índice y repetir si indice < limite. Para realizar la repetición se lee de la siguiente literal la dirección de la primera instrucción del bucle (el DO NO forma parte del bucle, es sólo para la inicializacion. Es la dirección siguiente a DO la que hay que usar)
Para almacenar direcciones voy a hacer cambios. Para el branch voy a utilizar una literal diferente, que represente una dirección. Y por la limitación del RARs hay que guardara en dos instrucciones: lui y addi. La primera almacena los 20-bits de mayor peso. La siguiente los 12 de menor.
Esto lo hago porque necesito poder utilizar directamente las etiquetas del ensamblador para definir los saltos, o de lo contrario habría que estar calculando a pelo la dirección... lo que es tedioso y propenso a errores. Voy a intentar una solución universal para el rars... para el gnu asm será más fácil...
Empiezo por el ejemplo 51-branch.s en el que re-implementamos branch, pero usando las nuevas literales de direcciones, que llamaremos LIT_ADDR. Como argumento se le pasará LA ETIQUETA a donde realizar el salto... Voy a ver si lo consigo
Lo he solucionado con un apaño muy cutre, pero que funciona. La literal de salto, en vez de contener la dirección a la que saltar, contiene directamente la instrucción j con la etiqueta del salto. Branch en realidad no hace nada ahora... simplemente retorna y la siguiente instrucción es el jump
Ya está implementado el do-loop con este nuevo enfoque... es mucho más sencillo y funciona muy bien! ¡¡¡vaaaamos!!!
El comando +LOOP is igual a loop, pero incrementa el índice en n unidades, estando n en la pila. De momento lo voy a implementar de forma independiente a LOOP, pero hay mucho código que se podría reutilizar
Ya tengo implementadas TODAS las palabras primitivas para la implementación de LOOPs y BRANCHES....
Antes de seguir con la implementación de palabras primitivas, voy a implementar las palabras de prueba definidas en el archivo cameltst.azm. El camelforth está implementado (para z80) en este repositorio de github:
Ya tengo el DUMP !!!! Cómo mola!!!!
Ahora ejecutamos este programa forth:
0 64 DUMPEsto es lo obtenemos al ejecutarlo:
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-05-26
$ ./run.sh 64-dump.s
0000 17 21 00 00 13 01 01 02 17 24 00 00 13 04 84 03
0010 EF 00 C0 3A 37 00 00 00 EF 00 40 3A 37 00 04 00
0020 EF 00 40 77 17 25 00 00 13 05 05 03 93 08 40 00
0030 73 00 00 00 93 08 A0 00 73 00 00 00 93 02 10 00
ok
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-05-26
$Se ha hecho un volcado desde la dirección 0. Para comprobar que el volcado es correcto se puede compara con el código que hay en el fichero firmware.bin:
$ hd -n 64 firmware.bin
00000000 17 21 00 00 13 01 01 02 17 24 00 00 13 04 84 03 |.!.......$......|
00000010 ef 00 c0 3a 37 00 00 00 ef 00 40 3a 37 00 04 00 |...:7.....@:7...|
00000020 ef 00 40 77 17 25 00 00 13 05 05 03 93 08 40 00 |..@w.%........@.|
00000030 73 00 00 00 93 08 a0 00 73 00 00 00 93 02 10 00 |s.......s.......|
00000040
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-05-26
$¡Es lo mismo! (Aunque con formato diferente)
El intérprete ya empieza a funcionar.... aunque todavía sin modo interactivo
Ya tengo implementados todos los ejemplos de test (fichero cameltst.azm) También tengo constantes (compiladas a mano) (docon)
Ya tenemos variables compiladas a mano (dovar)
La implementación de la palabra UINIT ha sido complicada. Esta palabra devuelve la dirección de memoria donde están los valores iniciales de la zona de usuario: Fin del diccionario, dirección de la última palabra del diccionario, etc...
Hay que meterla en el segmento de datos, ya que tiene en total 9 palabras (de 4bytes). El problema está que hay que llamar a docreate... y como estamos en el segmento de dato el RARs NO ENSAMBLA... por ello hay que meter las instrucciones en ensamblador a pelo... También la dirección de docreate (dovar) tiene que estar fija... la hemos llevado a la parte inferior del segmento de codigo (en la dirección 4)
He empezado a implementar la palabra COLD, que es la primera que se ejecuta... para ello necesito implementar CMOVE.. así que voy a aprovechar para implementar las palabras relacionadas con el movimiento de bytes en la memoria
El FILL ya está operativo... y también el CMOVE
Ya tengo TYPE! Ya puedo imprimir cadenas literales!!!
Estoy empezando a trabajar en las conversiones. Estaban ocurriendo bastantes fallos, hasta que me he dado cuenta de que en forth los números dobles ocupan 2 células en memoria! El orden de almacenamiento es primero la célula de menor peso, y luego la de mayor (la de mayor en el TOS). De momento voy a utilizar el riscv como si fuese sólo de 16.... así que simularemos los números dobles como dos células (cada una de 32-bits), pero la de mayor peso hay que asegurarse que siempre sea 0 ó -1 (todo a unos) si es negativo...
Cuando entienda bien el funcionamiento, lo podré implementar usando sólo 1 célula de 32-bits...
Así que ahora toca rehacer todas las operaciones con dobles con este nuevo enfoque...
Implementadas las palabras de cambio de base HEX y DECIMAL
Joder... el RARs se me queda pequeño. Estoy usando el modelo de memoria pequeño, en el que el código se sitúa en la dirección 0. La dirección más alta es 0xFFC, es decir, 4KB...Y ya he llegado a ese límite....
Tendré que pasar a otro modelo... pero joder... tengo direcciones cableadas... va a ser complicado
ok... ya tengo una primera solución... usando saltos largos (meter en el registro t0 la dirección a la que saltar y llamar jalr)
Parece que está funcionando... el problema es que ahora las direcciones son mayores de 16-bits y la depuracińo se complica
Tengo que asegurarme que todo está funcionando bien con las nuevas direcciones
Por otro lado, el problema del tamaño ya se ha solucionado... el programa actual ocupa más de 4K y ensambla sin problemas
Ya tengo implementada la palabra .S (DOTS). Madre mía... me ha gustado un huevo.. he tenido que solucionar muchísimos bugs... pero ya está....
En esta web está definido el estandar de forth: http://lars.nocrew.org/forth2012/
Me la tengo que leer para tener un conocimiento más profundo
Aquí hay un programa de test en forth... lo tengo que estudiar:
Estoy implementando la palabra INTERPRET y sus palabras de las que depende. Con la palabra NFA>CFA me encuentro con el problema de la alineación. NFA es un campo de bytes y tras él está la palabra (alinada) con la dirección del código (o directamente con el código, según que se use RARs o GNU...)
En cualquier caso, necesito una palabra forth para detectar si una dirección está alineada o no. Y si no está alinearla, obtener la direccion alineada... vamos... una palabra que convierta una dirección en su alineada (si ya está alineada se deja como está...)
Voy a apovechar para hacer este programa en gforth... y luego lo paso al risv-forth
Dese gforth se añade un archivo (por ejemplo align.fs) con este comando:
S" align.fs" includedYa tengo el programa. Lo he dividido en dos palabras: ?align y align. La primera es para comprobar si una dirección está o no alineada, y la segunda es para calcular su dirección alineada (si ya estaba alineada se deja como está)
( addr -- flag )
( flag: -1: Dir alineada )
( flag: 0: Dir no alineada )
: ?ALIGN
( Obtener los 2 bits de menor peso )
( Estos determinan si es una direccion alineada )
( o no: bits == 00: Alineada )
3
and ( Obtener los 2 bits de menor peso)
0= ( Es 0?)
IF
0 invert ( Dir alineada: Dejar -1 )
ELSE
0 ( Dir no alineada: Dejar 0 )
THEN
;
( addr1 -- addr2 )
( Convertir addr1 en una direccion alineada addr2 )
( Si ya esta alineada se quea como esta: addr2 = addr1 )
: align
dup ?align
invert IF
( Sumar 4)
4 +
( Poner a 0 los 2 bits de menor peso )
3 invert
and
THEN
;
Ahora lo voy a integrar en el riscv-forth
Está página está muy bien:
https://forth-standard.org/
Ya tengo el primer ejemplo interactivo! De momento sólo lo he probado con números de un dígito. Tras el "ok" se imprime la pila, y se puede ver cómo efectivamente va a umentando al introducir números
El ejemplo es 153-quit.s
Este es un ejemplo de uso:
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-04
$ ./run.sh 153-quit.s
Z80 CamelForth v1.01 25 Jan 1995
3
ok
3
5
ok
3 5
7
ok
3 5 7
9
ok
3 5 7 9 Ahora es cuando se empiezan a a ver los resultados, y la cosa se pone interesante
Tengo que empezar a meter palabras en el diccionario. La primera que voy a meter es bye, y probar a salir del modo interactivo...
Ya tengo el BYE en el diccionario! mi primera palabra interactiva!!! Y FUNCIONA!!!
Ejemplo: 154-bye.s
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-04
$ ./run.sh 154-bye.s
Z80 CamelForth v1.01 25 Jan 1995
5
ok
5
7
ok
5 7
BYE
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-04
$Es el momento de llevarse el diccionario a un fichero separado: dicctionary.s... Listo!!!
Ahora tengo que ir metiendo poco a poco todas las palabras en el diccionario. La siguiente será + para comprobar que está funcionando todo
Ya he ejecutado la primera instrucción interpretada: La suma.... El ejemplo está en 156-plus.s
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-05
$ ./run.sh 156-plus.s
Z80 CamelForth v1.01 25 Jan 1995
1 1 +
ok
2
3 5 +
ok
2 8
+
ok
10
bye
bye?
ok
BYE
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-05
$Estoy empezando con el diccionario... Lo primero que voy a hacer es crear una entrada del diccionario para la palabra ".S". Primero la creo "manualmente" y compruebo que funciona
$ ./run.sh 166-create.s
Z80 CamelForth v1.01 25 Jan 1995
1 2 3
ok
.S
1 2 3 ok
BYE
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-06
$Ahora hago un volcado del diccionario:
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-06
$ ./run.sh 166-create.s
Z80 CamelForth v1.01 25 Jan 1995
0x100100cd 0x100100c8
00C8 C1 00 01 10 00 02 2E 53 E4 0F 40 00 00 00 00 00
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-06
$A partir de la dirección 0x100100c8 está la nueva entrada correspondiente a .S
El siguiente paso es quitar la palabra .S e irla creando poco a poco, para comprobar que entiendo perfectamente todo. Con eso ya podemos crear la palabra CREATE
Listo! La palabra ".S" NO está en el diccionario, pero se mete en tiempo de ejecución en el ejemplo 163-create.s. Primero se mete y luego se pasa al modo interactivo para comprobarlo:
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-06
$ ./run.sh 167-create.s
Z80 CamelForth v1.01 25 Jan 1995
0x100100c8
00C8 C1 00 01 10 00 02 2E 53 AC 0F 40 00 00 00 00 00
ok
1 2 3
ok
.S
1 2 3 ok
BYE
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-06
$ Llegó el momento de implementar : para crear definiciones interactivamente...
Voy a intentar primero que funcione esto:
: .. . ;
Es simplemente una llamada a la palabra `.`.
Ya lo llevo bastante avanzado, pero tengo un probelma que resolver: Tengo que generar el código máquina de la instrucción li t0,direccion
Pero como direccion es de 32 bits, esto hay que descomponerlo a su vez en dos instrucciones: lui y addi... tengo que estudiarlo...
Tengo una solución, pero no es eficiente. De momento la voy a implementar y cuando funcione todo bien, lo voy mejorando... Este es el código para guardar un valor de 32-bits en un registros (Ejemplo con el valor 0x12345FEC
```asm
lui t1,0x12345 #-- 20-bits de mayor peso
lui t0,0xFEC #-- 12-bits de menor peso
srli t0,t0,12 #-- Desplazar 12-bits a la derecha
or t1,t1,t0 #-- Unir
El siguiente problema es que hay que crear el código máquina de esas instrucciones directamente...
La compilación no es trivial, se está complicando. Así que para depurar mejor, voy a empezar primero por implementar la palabra WORDS para visualizar todas las que hay en el diccionario
De momento las que hay se han compilado a mano. Este es el resultado (ejemplo 173-words.s)
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-07
$ ./run.sh 172-words.s
Z80 CamelForth v1.01 25 Jan 1995
.S . + BYE lit EXIT ok
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-07
$ La palabra mínima sería la nula: que no hace nada, simplemente retorna. La definimos así:
: NULL ;Si la implementamos directamente el resultado sería este:
do_null:
DOCOLON
EXITY la estructura del diccionario así:
#-- Palabra 6
.align 2
.word link5
.byte 0
lastword:
link6:
.byte 4
.ascii "NULL"
.word do_null #-- CFAPrimero vamos a completar la palabra CREATE que crea esta cabecera con un CFA en blanco. De hecho se puede meter guardar un valor especial para diferenciarlo (-1 por ejemplo)
Voy a hacer palabras de utilidad para mostrar información del diccionario sobre la palabra actual, y poder navegar fácilmente entre los diferentes campos, para hacer las pruebas
La primera que voy a implementar es .WINFO: Imprimir información de la palabra. Recibo como argumento la dirección de una palabra e imprime TODA su información
Ya está implementada la palabra .dotwinfo. Este es el resultado:
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-07
$ ./run.sh 173-dotwinfo.s
Z80 CamelForth v1.01 25 Jan 1995
0x100100c8 Link: 0x100100c1
0x100100cc Inmd: 0
0x100100cd NLen: 2
0x100100ce Name: .S
0x100100d0 CFA: 0x00400f90
ok
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-07
$ En el ejemplo 174-create.s se crear la palabra .. que simplemente llama a .S:
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-07
$ ./run.sh 174-create.s
Z80 CamelForth v1.01 25 Jan 1995
0x100100c8 Link: 0x100100c1
0x100100cc Inmd: 0
0x100100cd NLen: 2
0x100100ce Name: .S
0x100100d0 CFA: 0x00400fa8
0x100100d4 Link: 0x100100cd
0x100100d8 Inmd: 0
0x100100d9 NLen: 2
0x100100da Name: ..
0x100100dc CFA: 0x00400fa8
ok
1 2 3 4
ok
..
1 2 3 4 ok
BYE
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-07
$ Todo parece funcionar bien, pero hay un bug... Si cambio el nombre y por ejemplo lo amplío a 3 bytes: ... algo peta... tengo que encontrar el error
Listo! ya se crean nombres de cualquier longitud y funciona... Este es el ejemplo que antes no funcionaba y ahora sí (175-create.s)
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-07
$ ./run.sh 175-create.s
Z80 CamelForth v1.01 25 Jan 1995
0x100100c8 Link: 0x100100c1
0x100100cc Inmd: 0
0x100100cd NLen: 2
0x100100ce Name: .S
0x100100d0 CFA: 0x00400fb0
0x100100d4 Link: 0x100100cd
0x100100d8 Inmd: 0
0x100100d9 NLen: 3
0x100100da Name: ...
0x100100e0 CFA: 0x00400fb0
0x100100e4
ok
1 2 3 4 5
ok
...
1 2 3 4 5 ok
BYE
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-07
$ He creado la palabra .WCODE para mostrar el código máquina de una palabra. Ejemplo: 176-dotwcode.s
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-07
$ ./run.sh 176-dotwcode.s
Z80 CamelForth v1.01 25 Jan 1995
0x100100d4 Link: 0x100100cd
0x100100d8 Inmd: 0
0x100100d9 NLen: 3
0x100100da Name: NOP
0x100100e0 CFA: 0x00401bd0
0x00401bd0 : 0xffc40413
0x00401bd4 : 0x00142023
0x00401bd8 : 0x00042083
0x00401bdc : 0x00440413
0x00401be0 : 0x00008067
ok
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-07
$Estoy empezando a implementar las variables. La coa de momento no funciona, hay que depurar... Estoy probando el ejemplo 186-variable.s. En el diccionario está metida la Variable A (Su dirección es la siguiente al campo CFA). Al ejecutar el ejemplo se muestra la información en la consola de la palabra A, y se pasa al modo interactivo:
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-08
$ ./run.sh 186-variable.s
Z80 CamelForth v1.01 25 Jan 1995
0x100100f4 Link: 0x100100e9
0x100100f8 Inmd: 0
0x100100f9 NLen: 01
0x100100fa Name: A
0x100100fc CFA: 0x0040008c
ok
A
ok
.S
4200388 ok
BYE
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-08
$ La dirección de la variale debería ser 0x10010100 (la siguiente a 0x100100fc) pero es 0x4017c4 (4200388)
Hay algo mal...
Igual pruebo a ejecutar directamente una llamada a dovar.. no se ... tengo que pensarlo
Ya tengo una primera solución. NO es muy óptima (todavía). Pero primero hay que hacer que funcione. La solución es meterlo en el código. En el código se almacena la variable y se llama a dovar. dovar lo que hace es leer la variable y meterla en la pila. Tiene mucha sobrecarga, pero de momento es la solución
De momento para que funcione la función dovar tiene que estar en la dirección fija 0x00400004
Esto es lo que ocurre al ejecuar el ejemplo anterior:
Z80 CamelForth v1.01 25 Jan 1995
0x100100f4 Link: 0x100100e9
0x100100f8 Inmd: 0
0x100100f9 NLen: 01
0x100100fa Name: A
0x100100fc CFA: 0x10010100
0x10010100 : 0xffc40413
0x10010104 : 0x00142023
0x10010108 : 0x004002b7
0x1001010c : 0x00428293
0x10010110 : 0x000280e7
0x10010114 : 0x00000000
ok
A
ok
.S
268501268 ok La dirección que hay en la pila es ahora 0x10010114 (268501268) que efectivamente es la de la variable A
El siguiente paso es hacerlo automáticamente, desde un programa... Eso es lo que hace la palabra VARIABLE
En el ejemplo 187-variable-B.s ya está solucionado:
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-08
$ ./run.sh 187-variable-B.s
Z80 CamelForth v1.01 25 Jan 1995
0x100100f4 Link: 0x100100e9
0x100100f8 Inmd: 0
0x100100f9 NLen: 01
0x100100fa Name: A
0x100100fc CFA: 0x10010100
0x10010100 : 0xffc40413
0x10010104 : 0x00142023
0x10010108 : 0x004002b7
0x1001010c : 0x00428293
0x10010110 : 0x000280e7
0x10010114 : 0x00000000
0x10010118 Link: 0x100100f9
0x1001011c Inmd: 0
0x1001011d NLen: 01
0x1001011e Name: B
0x10010120 CFA: 0x10010124
0x10010124 : 0xffc40413
0x10010128 : 0x00142023
0x1001012c : 0x004002b7
0x10010130 : 0x00428293
0x10010134 : 0x000280e7
0x10010138 : 0x00000000
ok
B
ok
.S
268501304 ok
A
ok
.S
268501304 268501268 ok
BYE
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-08
$ He implementado la constante ESC (con valor 0xCAFE) de forma manual. Se prueba en este ejemplo: 188-constant.s
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-08
$ ./run.sh 189-constant.s
Z80 CamelForth v1.01 25 Jan 1995
0x10010118 Link: 0x100100f9
0x1001011c Inmd: 0
0x1001011d NLen: 03
0x1001011e Name: ESC
0x10010124 CFA: 0x10010128
0x10010128 : 0xffc40413
0x1001012c : 0x00142023
0x10010130 : 0x004002b7
0x10010134 : 0x01c28293
0x10010138 : 0x000280e7
0x1001013c : 0x0000cafe
0x10010140 : 0x00000000
ok
ESC
ok
.S
51966 ok
BYE
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-08
$ El valor 51966 es 0xCAFE
El objetivo de hoy es lograr generar la primera palabra compilada. Su definicion será esta:
: TEST -- ;
Donde la palabra -- está implementada de manera manual y lo que hace es pintar una línea de algunos caracteres. Es decir, TEST pinta una linea llamando a --. Lo que me interesa es el código de TEST que está formado por la llamada a docolon, el jal a línea y por último el exit
Primero lo implementamos todo manualmente y lo probamos
Ya tengo el ejemplo: 193-test.s. La palabra TEST está metida en el diccionario. Primero se hace un volcado de esta palabra del diccionario junto a su código máquina. Luego se pasa al modo interactivo y se comprueba que funciona: se dibuja una línea
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-09
$ ./run.sh 193-test.s
Z80 CamelForth v1.01 25 Jan 1995
0x10010158 Link: 0x10010151
0x1001015c Inmd: 0
0x1001015d NLen: 04
0x1001015e Name: TEST
0x10010164 CFA: 0x00401e48
0x00401e48 : 0xffc40413
0x00401e4c : 0x00142023
0x00401e50 : 0xfa5ff0ef
0x00401e54 : 0x00042083
0x00401e58 : 0x00440413
0x00401e5c : 0x00008067
0x00401e60 : 0xcafebaca
ok
TEST
-----
ok
BYE
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-09
$ Para analizarlo mejor he repetido la ejecución pero desde el propio RARS. La salida es esta:
Z80 CamelForth v1.01 25 Jan 1995
0x10010158 Link: 0x10010151
0x1001015c Inmd: 0
0x1001015d NLen: 04
0x1001015e Name: TEST
0x10010164 CFA: 0x00402070
0x00402070 : 0xffc40413
0x00402074 : 0x00142023
0x00402078 : 0xfa5ff0ef
0x0040207c : 0x00042083
0x00402080 : 0x00440413
0x00402084 : 0x00008067
0x00402088 : 0xcafebaca
ok
TEST
-----
ok
BYE
-- program is finished running (0) --La rutina do_test está en la dirección 0x00402070. En esa dirección tenemos el siguiente código asm y máquina:
0xffc40413 addi s0,s0,-4
0x00142023 sw ra, 0(s0)
0xfa5ff0ef jal do_line
0x00042083 lw ra,0(s0)
0x00440413 addi s0,s0,4
0x00008067 retSi generásemos la rutina TEST nosotros, no podríamos meter exactamente ese código por el jal es relativo (y como la rutina generada estaría en la rama no habria sufientes bits para el desplazamiento relativo). Por tanto, de momento el jal hay que hacerlo absoluto. El siguiente paso sería modificar la rutina test, ó hacer otra que sea TEST2 pero que realice el salto absoluto
ok.. Ya tengo TEST2, que también he metido en el diccionario. La llamada a do_line se hace así:
#-- Leer la direccion de la rutina line
la t0, do_line
#-- Saltar a esa rutina
jalr ra,t0,0Es el ejemplo 194-test2.s y funciona. Se imprime primero una linea y luego se ejecuta en modo interactivo. Funciona en ambos caso:
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-09
$ ./run.sh 194-test2.s
Z80 CamelForth v1.01 25 Jan 1995
0x10010158 Link: 0x10010151
0x1001015c Inmd: 0
0x1001015d NLen: 05
0x1001015e Name: TEST2
0x10010164 CFA: 0x00401e60
0x00401e60 : 0xffc40413
0x00401e64 : 0x00142023
0x00401e68 : 0x00000297
0x00401e6c : 0xf8c28293
0x00401e70 : 0x000280e7
0x00401e74 : 0x00042083
0x00401e78 : 0x00440413
-----
ok
TEST2
-----
ok
TEST2
-----
ok
BYE
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-09
$ En el siguiente ejemplo vamos a llamar a do_line pero directamente usando su dirección: 0x40201C
El código es este:
#-- Meter la direccion "cableada" de line en t0
li t0, 0x40201C
#-- Saltar a esa rutina
jalr ra,t0,0Y desde el RARS funciona bien:
Z80 CamelForth v1.01 25 Jan 1995
0x10010158 Link: 0x10010151
0x1001015c Inmd: 0
0x1001015d NLen: 05
0x1001015e Name: TEST3
0x10010164 CFA: 0x004020a8
0x004020a8 : 0xffc40413
0x004020ac : 0x00142023
0x004020b0 : 0x004022b7
0x004020b4 : 0x01c28293
0x004020b8 : 0x000280e7
0x004020bc : 0x00042083
0x004020c0 : 0x00440413
-----
ok
TEST3
-----
ok
BYE
-- program is finished running (0) --Ahora pasamos a la siguiente versión: TEST4. La idea es ahora dividir la dirección en dos partes: Una alta de 20-bits y otra baja de 12-bits:
0x0040201c = 0x00402 y 0x01C
Cada trozo de dirección está en un registro diferente: t1 = 0x00402000 (parte alta). t0 = 0x01C (parte baja)
Esa dirección la calculamos con este código:
lui t1, 0x00402 #-- t1= parte alta: 0x00402000
li t0, 0x01C #-- t0= parte baja: 0x0000001C
#-- Creamos la direccion comple uniendo ambas con or
or t0, t1, t0
#-- Saltar a esa rutina
jalr ra,t0,0
Comprobamos y funciona... (en el rars)
Z80 CamelForth v1.01 25 Jan 1995
0x10010158 Link: 0x10010151
0x1001015c Inmd: 0
0x1001015d NLen: 05
0x1001015e Name: TEST4
0x10010164 CFA: 0x004020c8
0x004020c8 : 0xffc40413
0x004020cc : 0x00142023
0x004020d0 : 0x00402337
0x004020d4 : 0x01c00293
0x004020d8 : 0x005362b3
0x004020dc : 0x000280e7
0x004020e0 : 0x00042083
-----
ok
TEST4
-----
ok
BYE
-- program is finished running (0) --Este código tiene un problema. Al usar li para cargar los 12-bits de menor peso, se hace una extensión de signo (porque li es el realidad un andi). Con esta dirección no ocurre nada, pero si por ejemplo los 12-bits de menor peso tuviesen el bit más significativo a 1 (como por ejemplo con 0xF01), entonces lo trata como un número negativo y extiende el signo... el or entre t1 y t0 ya no funcionaría
Para evitar eso hay que cargar el valor en t0 usando dos instrucciones: lui y luego un shift a la derecha:
lui t1, 0x00402 #-- t1= parte alta: 0x00402000
lui t0, 0x01C #-- t0= parte baja: 0x0000001C
srli t0, t0, 12 #--- Desplazar 12 bits a la derecha
#-- Creamos la direccion comple uniendo ambas con or
or t0, t1, t0Confirmamos que este código funciona:
Z80 CamelForth v1.01 25 Jan 1995
0x10010158 Link: 0x10010151
0x1001015c Inmd: 0
0x1001015d NLen: 05
0x1001015e Name: TEST5
0x10010164 CFA: 0x004020ec
0x004020ec : 0xffc40413
0x004020f0 : 0x00142023
0x004020f4 : 0x00402337
0x004020f8 : 0x0001c2b7
0x004020fc : 0x00c2d293
0x00402100 : 0x005362b3
0x00402104 : 0x000280e7
-----
ok
TEST5
-----
ok
BYE
-- program is finished running (0) --Por tanto ESE ES EL CÓDIGO que hay que generar para realizar una llamada a subutina, y es por tanto el CODIGO que debe generar la rutina de compilación que añade una instrucción nueva de Forth
Es verdad que SON MUCHAS instrucciones para hacer un simple call.... Se podría optimizar, pero creo que la respuesta es que sería mejor implementar un intérprete de Forth de tipo DTC (como el de camelforth) en vez de un STC... ya que en el riscv generar las instrucciones de salto a suburtina (para direcciones absolutas) no es muy eficiente
Pero en cualquier caso, como lo estoy haciendo SDC, voy a seguir así aunque sea menos óptimo...
Por tanto, estas son las instrucciones (y su código máquina) que hay que añadir para realizar las llamadas a palabras forth:
(1) 0x00402337 lui t1, 0x00402
(2) 0x0001c2b7 lui t0, 0x01C
(3) 0x00c2d293 srli t0,t0, 12
(4) 0x005362b3 or t0,t1,t0
(5) 0x000280e7 jalr ra,0(s0)De las 5, las dos primeras (1 y 2) hay que generarlas teniendo en cuenta la dirección de la subrutina a saltar (que supondremos que está en un registro). Las tres últimas (3, 4 y 5) son FIJAS. Por lo que se pueden copiar desde otra región de memoria igual que hemos hecho con docolon y exit
Por tanto, ahora el reto es generar la instrucción TEST6 automáticamente, creando el diccionario a pelo
Empezamos probando en la consola. La dirección de do_line es: 0x401dec. Lo voy a imprimir siempre al comienzo de los ejemplos por si cambiase...
VAAAAMOS!!! Ya tengo el ejemplo 198-test6.s que genera la palabra TEST6 y funciona!!!! De momento el ejemplo no está limpio, pero ha funcionado!!
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-09
$ ./run.sh 198-test6.s
Z80 CamelForth v1.01 25 Jan 1995
0x0040209c
0x10010158 Link: 0x10010151
0x1001015c Inmd: 0
0x1001015d NLen: 05
0x1001015e Name: TEST5
0x10010164 CFA: 0x0040216c
0x0040216c : 0xffc40413
0x00402170 : 0x00142023
0x00402174 : 0x00402337
0x00402178 : 0x0001c2b7
0x0040217c : 0x00c2d293
0x00402180 : 0x005362b3
0x00402184 : 0x000280e7
0x10010168 Link: 0x1001015d
0x1001016c Inmd: 0
0x1001016d NLen: 05
0x1001016e Name: TEST6
0x10010174 CFA: 0x10010178
0x10010178 : 0xffc40413
0x1001017c : 0x00142023
0x10010180 : 0x00402337
0x10010184 : 0x0009c2b7
0x10010188 : 0x00c2d293
0x1001018c : 0x005362b3
0x10010190 : 0x000280e7
ok
TEST6
-----
ok
BYE
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-09
$ Ahora ya sí que sí... con esto ya pasamos a otro nivel... voy a consolidarlo!
NOTA RECORDATORIO: Este link de sectorforth es MUY BUENO!! Mirar el código!!!
Ahora ya sé que la parte de las llamadas está ok... sin embargo todavía no he conseguido crear la primera palabra compilada automáticamente, desde la línea de comandos... Ni siquiera funciona la palabra nula: : NULL ;... se queda en un estado raro...
Así que toca depurar más profundamente... No tengo clara la estrategia... voy a probar primero a ver si se ejecuta correctamente el comando : (colon)... Primero lo depuro en compilación manual, y luego comparo con la automática...
Este es el resultado cuando llamo sólo a COLON, manualmente. Se crea una palabra nula, y en el diccionario vemos la entrada creada y el código máquina de DOCOLON
$ ./run.sh 200-dotdot.s
Z80 CamelForth v1.01 25 Jan 1995
0x10010168 Link: 0x1001015d
0x1001016c Inmd: 0
0x1001016d NLen: 80
0x1001016e Name:
0x10010170 CFA: 0x10010174
0x10010174 : 0xffc40413
0x10010178 : 0x00142023
0x1001017c : 0x00000000
0x10010180 : 0x00000000
0x10010184 : 0x00000000
0x10010188 : 0x00000000
ok
Ahora voy a probar lo mismo pero ejecutando las instrucciones de depuración en el propio intérprete, a ver qué sale...
LISTO! Ya tengo el programa NULO funcionando! Había varios problemas, principalmente fallos míos de uso, porque tdoavía no lo entiendo del todo. El caso es que con el ejemplo 200-T.s hemos podido definir la palabra T en modo interactivo. Esta palabra No hace nada, sólo retorna. Este es el resultado:
$ ./run.sh 200-T.s
Z80 CamelForth v1.01 25 Jan 1995
ok
WORDS
TEST5 ; : ESC A WORDS NOP .S . + BYE lit EXIT ok
: T ;
ok
WORDS
T TEST5 ; : ESC A WORDS NOP .S . + BYE lit EXIT ok
T
ok
BYE
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-09
$El problema principal es que no tenía definida la palabra ; como INMEDIATA (el flag estaba a 0 en vez de 1). Lo bueno es que ya he comprendido lo que significa. Cuando NO es inmediato, si estamos en modo interpretación se ejecuta, pero si estamos en modo compilación se añade un salto a esa palabra en el diccionario. Peor, cuando es inmediato se ejecuta siempre, tanto en interpretación como en compilación. Y en compilación necesitamos que se ejecute el exit para que salir el modo compilación. Antes, al no ejecutarse, se metía su llamada pero nunca se salía del modo compilación, por lo que sucesivas ejecuciones de palabras no producian ningún efecto....
Otro problema que tenía es que estaba usando el nombre de palabra A, que YA ESTABA DEFINIDO EN EL DICCIONARIO, y por eso no se asignaba el nombre...
He estado haciendo más pruebas... y ¡ES LA LECHE! Ya puedo definir mis propias palabras!!!!!! Ahora es cuando es potente!!!!!!!
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-09
$ ./run.sh 200-T.s
Z80 CamelForth v1.01 25 Jan 1995
ok
1 2 3 4 5
ok
.S
1 2 3 4 5 ok
: T .S ;
ok
T
1 2 3 4 5 ok
: T2 T ;
ok
T2
1 2 3 4 5 ok
: T3 T2 T2 T2 ;
ok
T3
1 2 3 4 5 1 2 3 4 5 1 2 3 4 5 ok
BYE
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-09
$ He definido palabras a varios niveles... unas llamando a otras... y funciona muy bien! Forth es la leche!
El siguiente problema a solucionar son los literales... que en compilación no funcionan, así, en modo interpretado 1 1 + . da como resultado 2. Sin embargo en modo compilado, basta con meter un único número para que pete:
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-09
$ ./run.sh 200-T.s
Z80 CamelForth v1.01 25 Jan 1995
ok
1 1 + .
2 ok
: T 1 ;
Dobijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-09
$ El objetivo de hoy es conseguir compilar palabras con literales. Como ejemplo, voy a trabajar en la palabra L1 que simplemente mete el número 1 en la pila:
: L1 1 ;Con la versión actual ocurre esto:
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-11
$ ./run.sh 201-T.s
Z80 CamelForth v1.01 25 Jan 1995
ok
WORDS
TEST5 ; : ESC A WORDS NOP .S . + BYE lit EXIT ok
: L1 1 ;
Dobijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-11
$Voy a comenzar añadiendo el literal '1' directamente en el diccionario (que en realidad es como si fuese una constante). Después tengo que hacer que se añadida desde un programa compilado automáticamente, y finalmente desde un programa compilado automáticamente de forma interactiva
He metido a pelo en el diccionario la palabra 1 como una constante. En esas circunstancias sí que funciona crear la palabra L1 a partir de la palabra 1... sin embargo, no funcionoa con 2 porque no está definida en el diccionario:
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-11
$ ./run.sh 201-T.s
Z80 CamelForth v1.01 25 Jan 1995
ok
: L1 1 ;
ok
L1 .
1 ok
: L2 2 ;
Dobijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-11
$ Bien, lo que hay que hacer es depurar el intérprete para que al detectar un número lo meta como si fuese una constante.. Pero primero voy a reproducir el ejemplo anterior creando el literal 2 desde un programa forth...
Ya está solucionado en el ejemplo 202-L2.s:
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-11
$ ./run.sh 202-L2.s
Z80 CamelForth v1.01 25 Jan 1995
0x10010168 Link: 0x1001015d
0x1001016c Inmd: 0
0x1001016d NLen: 01
0x1001016e Name: 1
0x10010170 CFA: 0x10010174
0x10010174 : 0xffc40413
0x10010178 : 0x00142023
0x1001017c : 0x004002b7
0x10010180 : 0x01c28293
0x10010184 : 0x000280e7
0x10010188 : 0x00000001
0x1001018c : 0x00000000
0x10010190 : 0x00000000
0x1001018c Link: 0x1001016d
0x10010190 Inmd: 0
0x10010191 NLen: 01
0x10010192 Name: 2
0x10010194 CFA: 0x10010198
0x10010198 : 0xffc40413
0x1001019c : 0x00142023
0x100101a0 : 0x00400337
0x100101a4 : 0x0001c2b7
0x100101a8 : 0x00c2d293
0x100101ac : 0x005362b3
0x100101b0 : 0x000280e7
0x100101b4 : 0x00000002
0x100101c4 ok
: L2 2 ;
ok
L2 .
2 ok
BYE
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-11
$ Bien, ya está todo listo... ahora hay que depurar el intérprete para añadir la literal...
LISTO!!!! Ya se meten las literales en modo compilación! No estaba implementada esta parte en la palabra LITERAL (estaba sólo la parte de interpretación). Ya lo he añadido... y con este ejemplo sencillo funciona!
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-11
$ ./run.sh 202-L3.s
Z80 CamelForth v1.01 25 Jan 1995
ok
WORDS
1 TEST5 ; : ESC A WORDS NOP .S . + BYE lit EXIT ok
: L3 3 ;
ok
L3 .
3 ok
BYE
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-11
$ Ahora hay que hacer pruebas más exhaustivas...
He encontrado un bug. En el modo compilacion no se pueden más de 1 literal. Funciona con 1, pero al meter 2 seguidas la segunda NO se inserta en la pila. En este ejemplo se puede ver el problema:
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-11
$ ./run.sh 204-Literal.s
Z80 CamelForth v1.01 25 Jan 1995
ok
WORDS
1 TEST5 ; : ESC A WORDS NOP .S . + BYE lit EXIT ok
: T 2 3 4 ;
ok
T
ok
.S
2 ok
BYE
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-11
$ Al ejecutar .S lo que debería haberse viesto es 2 3 4, sin embargo sólo está metido el 2... es decir, sólo se ha metido la primera literal, y luego es como si se hubiese ejecutado un exit para salir de la palabra...
Tengo que depurar el código generado por la palabra T... para ello voy a meter en el diccionario .LWINFO
Voy a desensamblar el código máquina generado y comprobar qué es lo que hace. Para desensamblar voy a utilizar esta herramienta web:
https://luplab.gitlab.io/rvcodecjs/
Los programas que voy a estudiar son los siguientes:
-
: P0 ;: Es el programa NULO -
: P1 HI ;: Imprime un mensaje -
: P2 HI HI ;: Imprime dos mensajes iguales -
: P3 HI HI HI ;: Imprime tres mensajes iguales -
: L1 2 ;: Mete una literal en la pila -
: L2 2 2 ;: Mete dos literales en la pila -
: L3 2 2 2 ;: Mete tres literales en la pila
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-11
$ ./run.sh 205-P0.s
Z80 CamelForth v1.01 25 Jan 1995
ok
WORDS
.WLINFO 1 TEST5 ; : ESC A WORDS NOP .S . + BYE lit EXIT ok
: P0 ;
ok
WORDS
P0 .WLINFO 1 TEST5 ; : ESC A WORDS NOP .S . + BYE lit EXIT ok
P0
ok
.WLINFO
0x100101a0 Link: 0x10010191
0x100101a4 Inmd: 0
0x100101a5 NLen: 02
0x100101a6 Name: P0
0x100101a8 CFA: 0x100101ac
0x100101ac : 0xffc40413
0x100101b0 : 0x00142023
0x100101b4 : 0x00042083
0x100101b8 : 0x00440413
0x100101bc : 0x00008067
0x100101c0 : 0x4c572e07
0x100101c4 : 0x4f464e49
0x100101c8 : 0x00000020
ok
BYE
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-11
$ Vamos a analizar el código máquina de P0, desensamblando instrucción a instrucción
- P0:
- 0xffc40413 --> addi s0, s0, -4 (s0 = 8) # COLON
- 0x00142023 --> sw ra, 0(s0) (ra = x1)
- 0x00042083 --> lw ra, 0(s0) #--- EXIT
- 0x00440413 --> addi s0, s0, 4
- 0x00008067 --> jalr zero, ra, 0
OK. Está formado por COLON y luego EXIT. Es decir, se mete la dirección de retorno en la pila. En exit se saca la dirección de retorno de la pila y se salta a ella
Vamos a por P1. Funciona todo ok
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-11
$ ./run.sh 206-P1.s
Z80 CamelForth v1.01 25 Jan 1995
ok
WORDS
"HI .WLINFO 1 TEST5 ; : ESC A WORDS NOP .S . + BYE lit EXIT ok
"HI
HI!
ok
: P1 "HI ;
ok
WORDS
P1 "HI .WLINFO 1 TEST5 ; : ESC A WORDS NOP .S . + BYE lit EXIT ok
P1
HI!
ok
.WLINFO
0x100101b0 Link: 0x100101a5
0x100101b4 Inmd: 0
0x100101b5 NLen: 02
0x100101b6 Name: P1
0x100101b8 CFA: 0x100101bc
0x100101bc : 0xffc40413
0x100101c0 : 0x00142023
0x100101c4 : 0x00402337
0x100101c8 : 0x001982b7
0x100101cc : 0x00c2d293
0x100101d0 : 0x005362b3
0x100101d4 : 0x000280e7
0x100101d8 : 0x00042083
0x100101dc : 0x00440413
0x100101e0 : 0x00008067
0x100101e4 : 0x4c572e07
0x100101e8 : 0x4f464e49
0x100101ec : 0x00000020
0x100101f0 : 0x00000000
0x100101f4 : 0x00000000
0x100101f8 : 0x00000000
ok
BYE
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-11
$ - P1:
- 0xffc40413 --> addi s0, s0, -4 (s0 = 8) # COLON
- 0x00142023 --> sw ra, 0(s0) (ra = x1)
- 0x00402337 --> lui t1, 0x402 #-- Execucion de HI
- 0x001982b7 --> lui t0, 0x198
- 0x00c2d293 --> srli t0, t0, 12
- 0x005362b3 --> or t0, t1, t0
- 0x000280e7 --> jalr ra, t0, 0
- 0x00042083 --> lw ra, 0(s0) #-- EXIT
- 0x00440413 --> addi s0, s0, 4
- 0x00008067 --> jalr zero, ra, 0
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-11
$ ./run.sh 207-P2.s
Z80 CamelForth v1.01 25 Jan 1995
ok
WORDS
"HI .WLINFO 1 TEST5 ; : ESC A WORDS NOP .S . + BYE lit EXIT ok
: P2 "HI "HI ;
ok
P2
HI!
HI!
ok
.WLINFO
0x100101b0 Link: 0x100101a5
0x100101b4 Inmd: 0
0x100101b5 NLen: 02
0x100101b6 Name: P2
0x100101b8 CFA: 0x100101bc
0x100101bc : 0xffc40413
0x100101c0 : 0x00142023
0x100101c4 : 0x00402337
0x100101c8 : 0x001982b7
0x100101cc : 0x00c2d293
0x100101d0 : 0x005362b3
0x100101d4 : 0x000280e7
0x100101d8 : 0x00402337
0x100101dc : 0x001982b7
0x100101e0 : 0x00c2d293
0x100101e4 : 0x005362b3
0x100101e8 : 0x000280e7
0x100101ec : 0x00042083
0x100101f0 : 0x00440413
0x100101f4 : 0x00008067
0x100101f8 : 0x4c572e07
ok
BYE
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-11
$ - P2:
- 0xffc40413 --> addi s0, s0, -4 (s0 = 8) # COLON
- 0x00142023 --> sw ra, 0(s0) (ra = x1)
- 0x00402337 --> lui t1, 0x402 #-- Execucion de HI
- 0x001982b7 --> lui t0, 0x198
- 0x00c2d293 --> srli t0, t0, 12
- 0x005362b3 --> or t0, t1, t0
- 0x000280e7 --> jalr ra, t0, 0
- 0x00402337 --> lui t1, 0x402 #-- Execucion de HI
- 0x001982b7 --> lui t0, 0x198
- 0x00c2d293 --> srli t0, t0, 12
- 0x005362b3 --> or t0, t1, t0
- 0x000280e7 --> jalr ra, t0, 0
- 0x00042083 --> lw ra, 0(s0) #-- EXIT
- 0x00440413 --> addi s0, s0, 4
- 0x00008067 --> jalr zero, ra, 0
P3 es más de lo mismo. El patrón queda claro... vamos ahora a ver qué pasa con los literales...
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-11
$ ./run.sh 208-test-literal.s
Z80 CamelForth v1.01 25 Jan 1995
ok
WORDS
"HI .WLINFO 1 TEST5 ; : ESC A WORDS NOP .S . + BYE lit EXIT ok
: L1 2 ;
ok
L1
ok
.S
2 ok
.WLINFO
0x100101b0 Link: 0x100101a5
0x100101b4 Inmd: 0
0x100101b5 NLen: 02
0x100101b6 Name: L1
0x100101b8 CFA: 0x100101bc
0x100101bc : 0xffc40413
0x100101c0 : 0x00142023
0x100101c4 : 0x00400337
0x100101c8 : 0x0001c2b7
0x100101cc : 0x00c2d293
0x100101d0 : 0x005362b3
0x100101d4 : 0x000280e7
0x100101d8 : 0x00000002
0x100101dc : 0x00042083
0x100101e0 : 0x00440413
0x100101e4 : 0x00008067
0x100101e8 : 0x4c572e07
0x100101ec : 0x4f464e49
0x100101f0 : 0x00000020
0x100101f4 : 0x00000000
0x100101f8 : 0x00000000
ok
BYE
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-11
$ - L1
- 0xffc40413 --> addi s0, s0, -4 (s0 = 8) # COLON
- 0x00142023 --> sw ra, 0(s0) (ra = x1)
- 0x00402337 --> lui t1, 0x400 #-- Execucion de la literal
- 0x0001c2b7 --> lui t0, 0x01c
- 0x00c2d293 --> srli t0, t0, 12
- 0x005362b3 --> or t0, t1, t0
- 0x000280e7 --> jalr ra, t0, 0
- 0x100101d8 --> literal: 0x00000002
- 0x00042083 --> lw ra, 0(s0) #-- EXIT
- 0x00440413 --> addi s0, s0, 4
- 0x00008067 --> jalr zero, ra, 0
OK! Ahora ya puedo ver lo que ocurre. Se llama a docon2, pero ra se tiene que incrementar en 4 para saltar el literal. Así que he creado otra rutina: dolit que hace justo eso.... y ahora ya FUNCIONA!!!!
Esta es la prueba:
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-11
$ ./run.sh 208-test-literal.s
Z80 CamelForth v1.01 25 Jan 1995
ok
WORDS
"HI .WLINFO 1 TEST5 ; : ESC A WORDS NOP .S . + BYE lit EXIT ok
: L2 2 2 ;
ok
L2
ok
.S
2 2 ok
+ .
4 ok
BYE
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-11
$Aquí tengo una prueba más larga... joder... esto va tomando forma!!!!
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-11
$ ./run.sh 209-test.s
Z80 CamelForth v1.01 25 Jan 1995
ok
WORDS
"HI .WLINFO ONE TEST5 ; : ESC A WORDS NOP .S . + BYE lit EXIT ok
: +1 1 + ;
ok
WORDS
+1 "HI .WLINFO ONE TEST5 ; : ESC A WORDS NOP .S . + BYE lit EXIT ok
1 +1 .
2 ok
1 2 3 4 5 6 7 8
ok
.S
1 2 3 4 5 6 7 8 ok
+ .S
1 2 3 4 5 6 15 ok
+ .S
1 2 3 4 5 21 ok
+1
ok
.S
1 2 3 4 5 22 ok
+ + + + .S
1 36 ok
+ .
37 ok
.S
ok
BYE
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-11
$ Llega el momento de implementar el IF. Vamos a estudiar bien su funcionamiento e implementarlo de diferentes formas. Primero creamos un ejemplo mínimo para mostrar su funcionamiento, y lo probamos en gforth. Definimos ITRUE como una condición IF que se cumple siempre. Imprime el mensaje "HI". Definimos IFALSE como una condicón IF que NUNCA se cumple. No imprime ningún mensaje
: ITRUE -1 IF "HI THEN ; ok
: IFALSE 0 IF "HI THEN ; ok
ITRUE HI
ok
IFALSE okPrimero lo vamos a implementar directamente, para ver cómo debe ser el código máquina generado en ambos casos, y tener controladas las instrucciones privimitivas necesarias para su implementación. Después hay que ver cómo se hace para modificar la dirección de salto de la nueva palabra... vamos paso a paso...
El código para ejecutar IFALSE es el siguiente (compilado a mano):
LIT(-1)
#-- Si es falso no ejecutar (saltar)
QBRANCH
ADDR(end)
QUOTEHI
end:En el ejemplo completo se muestra la dirección donde comienza el programa main, y se hace un volcado del código máquina
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-11
$ ./run.sh 210-itrue.s
Z80 CamelForth v1.01 25 Jan 1995
0x004000c8
HI!
0x004000c8 : 0x4e8000ef
0x004000cc : 0xfffff037
0x004000d0 : 0x638000ef
0x004000d4 : 0x0080006f
0x004000d8 : 0x16c020ef
0x004000dc : 0x4d4000ef
0x004000e0 : 0x00008037
0x004000e4 : 0x7c1010ef
ok
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-11
$El ensamblador de programa compilado a mano sería este:
jal do_lit
lui zero,%valor
jal do_qbranch
j %labelHe hecho un ejemplo que crea el if a mano, y en el campo de salto se llama a do_hi (al final el IF no es más que un qbranch...). Sin embargo hay algún problema, porque peta. Esta es la salida:
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-11
$ ./run.sh 211-if.s
Z80 CamelForth v1.01 25 Jan 1995
0x00400760
0x0040229c
0x100101e0
0x100101b4 Link: 0x100101a9
0x100101b8 Inmd: 0
0x100101b9 NLen: 05
0x100101ba Name: ITRUE
0x100101c0 CFA: 0x100101c4
0x100101c4 : 0xffc40413
0x100101c8 : 0x00142023
0x100101cc : 0x00400337
0x100101d0 : 0x007602b7
0x100101d4 : 0x00c2d293
0x100101d8 : 0x005362b3
0x100101dc : 0x000280e7
0x100101e0 : 0x0040229c
0x100101e4 : 0x00042083
0x100101e8 : 0x00440413
0x100101ec : 0x00008067
0x100101f0 : 0x00000000
0x100101f4 : 0x00000000
0x100101f8 : 0x00000000
0x100101fc : 0x00000000
0x10010200 : 0x00000000
ok
WORDS
ITRUE "HI .WLINFO ONE TEST5 ; : ESC A WORDS NOP .S . + BYE lit EXIT ok
0
ok
.S
268501472 0 ok
ITRUE
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-11
$ El resultado esperado es que llamase a do_quotehi y que apareciese un "HI!" en la pantalla.... sin embargo peta... vamos a ver qué está pasando. Las dos primeras direcciones que aparecen son las siguientes:
- do_qbranch: 0x00400760
- do_quotehi: 0x0040229c
Vamos a desensamblar la palabra creada para saber lo que ocurre...
- IF
- 0xffc40413 --> addi s0, s0, -4 (s0 = 8) # COLON
- 0x00142023 --> sw ra, 0(s0) (ra = x1)
- 0x00400337 --> lui t1, 0x400 #-- Execucion de la literal
- 0x007602b7 --> lui t0, 0x760
- 0x00c2d293 --> srli t0, t0, 12
- 0x005362b3 --> or t0, t1, t0
- 0x000280e7 --> jalr ra, t0, 0
- 0x0040229c --> literal: 0x0040229c
- 0x00042083 --> lw ra, 0(s0) #-- EXIT
- 0x00440413 --> addi s0, s0, 4
- 0x00008067 --> jalr zero, ra, 0
ok, he encontrado el error. He hecho un do_qbranch2 nuevo. El anterior, do_qbranch, saltaba a la dirección indicada por ra, donde se supone que había un jump. Sin embargo, en qbranch2 se salta a la dirección que está almacenada en ra....
Ahora ya sale el HI! aunque se queda en bucle infinito (algún problema con la dirección de retorno...)
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-11
$ ./run.sh 211-if.s
Z80 CamelForth v1.01 25 Jan 1995
0x00400048
0x004022b8
0x100101e0
0x100101b4 Link: 0x100101a9
0x100101b8 Inmd: 0
0x100101b9 NLen: 05
0x100101ba Name: ITRUE
0x100101c0 CFA: 0x100101c4
0x100101c4 : 0xffc40413
0x100101c8 : 0x00142023
0x100101cc : 0x00400337
0x100101d0 : 0x000482b7
0x100101d4 : 0x00c2d293
0x100101d8 : 0x005362b3
0x100101dc : 0x000280e7
0x100101e0 : 0x004022b8
0x100101e4 : 0x00042083
0x100101e8 : 0x00440413
0x100101ec : 0x00008067
0x100101f0 : 0x00000000
0x100101f4 : 0x00000000
0x100101f8 : 0x00000000
0x100101fc : 0x00000000
0x10010200 : 0x00000000
ok
0
ok
.S
268501472 0 ok
WORDS
ITRUE "HI .WLINFO ONE TEST5 ; : ESC A WORDS NOP .S . + BYE lit EXIT ok
ITRUE
HI!
HI!
HI!
HI!
HI!
HI!
...Seguimos depurando el IF... En realidad la cosa es un poco más complicada... para que funcione correctamente hay que integrar también el THEN, ya que, al fin y al cabo, el salto es a Then... PERO es un salto directo, es decir, que nunca retornará al IF inicial... Posiblemente haya que modificar la dirección de retorno de la pila para que vuelva NO al IF, sino al término del Then...
Ya funciona! Lo he probado con esta palabra FORTH compilada a mano:
: ITRUE IF "HI THEN ;
Es un poco diferente a la indicadas ayer. Esta cubre los dos casos. Según lo que haya en la pila, bien no imprime nada o bien imprime "HI!"
Este es el resultado al probarla en el ejemplo 212-itrue.s:
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-12
$ ./run.sh 212-itrue.s
Z80 CamelForth v1.01 25 Jan 1995
0x100101b4 Link: 0x100101a9
0x100101b8 Inmd: 0
0x100101b9 NLen: 05
0x100101ba Name: ITRUE
0x100101c0 CFA: 0x100101c4
0x100101c4 : 0xffc40413
0x100101c8 : 0x00142023
0x100101cc : 0x00400337
0x100101d0 : 0x000482b7
0x100101d4 : 0x00c2d293
0x100101d8 : 0x005362b3
0x100101dc : 0x000280e7
0x100101e0 : 0x100101f8
0x100101e4 : 0x00402337
0x100101e8 : 0x002902b7
0x100101ec : 0x00c2d293
0x100101f0 : 0x005362b3
0x100101f4 : 0x000280e7
0x100101f8 : 0x00042083
0x100101fc : 0x00440413
0x10010200 : 0x00008067
0x10010204 : 0x00000000
ok
0 ITRUE
ok
-1 ITRUE
HI!
ok
BYE
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-12
$ Ahora tengo que hacer que funcione pero llamando a las palabras independientemente (porque ahora está todo en un único programa...)
ok, ya he refactorizado el código y tengo las subrutinas de IF y THEN por separado... Ahora es la prueba definitiva.... Qué pasa si lo hago en la línea de comandos? Vamos a probarlo... Primero hay que añadir IF y THEN en el diccionario (como inmediatos...)
FUNCIONA!!! VAAAAMOS!!!!! Ya puedo construir palabras con IF-THEN!!!!
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-12
$ ./run.sh 215-if-then.s
Z80 CamelForth v1.01 25 Jan 1995
ok
WORDS
THEN IF "HI .WLINFO ONE TEST5 ; : ESC A WORDS NOP .S . + BYE lit EXIT ok
: ?HI IF "HI THEN ;
ok
WORDS
?HI THEN IF "HI .WLINFO ONE TEST5 ; : ESC A WORDS NOP .S . + BYE lit EXIT ok
0 ?HI
ok
1 ?HI
HI!
ok
BYE
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-12
$ Increible...
Para probar el else voy a definir las palabras T (que imprime 'T') y F (que imprime 'F'), y ?BOOL que imprime T ó F en función del estado de la pila
obijuan@Hoth:~
$ gforth
Gforth 0.7.3, Copyright (C) 1995-2008 Free Software Foundation, Inc.
Gforth comes with ABSOLUTELY NO WARRANTY; for details type `license'
Type `bye' to exit
ok
: T 84 emit ; ok
ok
T T ok
: F 70 emit ; ok
ok
F F ok
ok
: ?BOOL IF T ELSE F THEN ; ok
ok
0 ?BOOL F ok
1 ?BOOL T ok
BYE
obijuan@Hoth:~
$
LISTO!!!! ya lo tengo implementado!! Vaaamos!!!
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-12
$ ./run.sh 216-else.s
Z80 CamelForth v1.01 25 Jan 1995
0x100101d0 Link: 0x100101c5
0x100101d4 Inmd: 0
0x100101d5 NLen: 05
0x100101d6 Name: ?BOOL
0x100101dc CFA: 0x100101e0
0x100101e0 : 0xffc40413
0x100101e4 : 0x00142023
0x100101e8 : 0x00400337
0x100101ec : 0x000482b7
0x100101f0 : 0x00c2d293
0x100101f4 : 0x005362b3
0x100101f8 : 0x000280e7
0x100101fc : 0x1001022c
0x10010200 : 0x00402337
0x10010204 : 0x003582b7
0x10010208 : 0x00c2d293
0x1001020c : 0x005362b3
0x10010210 : 0x000280e7
0x10010214 : 0x00400337
0x10010218 : 0x000642b7
0x1001021c : 0x00c2d293
0x10010220 : 0x005362b3
0x10010224 : 0x000280e7
0x10010228 : 0x10010240
0x1001022c : 0x00402337
0x10010230 : 0x0037c2b7
0x10010234 : 0x00c2d293
0x10010238 : 0x005362b3
0x1001023c : 0x000280e7
0x10010240 : 0x00042083
0x10010244 : 0x00440413
0x10010248 : 0x00008067
0x1001024c : 0x00000000
ok
WORDS
?BOOL THEN IF "HI .WLINFO ONE TEST5 ; : ESC A WORDS NOP .S . + BYE lit EXIT ok
0 ?BOOL
F ok
1 ?BOOL
T ok
BYE
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-12
$ Ahora hay que meterlo en el diccionario y probarlo en el modo interactivo
LISTO!!! Ya lo tenemos!!!! FUNCIONA!!!!
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-12
$ ./run.sh 217-qbool.s
Z80 CamelForth v1.01 25 Jan 1995
ok
WORDS
SPACE EMIT ELSE THEN IF "HI .WLINFO ONE TEST5 ; : ESC A WORDS NOP .S . + BYE lit EXIT ok
SPACE
ok
: T 84 EMIT SPACE ;
ok
T
T ok
: F 70 EMIT SPACE ;
ok
F
F ok
: ?BOOL IF T ELSE F THEN ;
ok
0 ?BOOL
F ok
1 ?BOOL
T ok
BYE
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-12
$ Llega el momento de implementar el DO-LOOP... La prueba la vamos a hacer con la palabra -- que dibuja una linea de 10 caracteres. Primero lo probamos con el gforth:
obijuan@Hoth:~
$ gforth
Gforth 0.7.3, Copyright (C) 1995-2008 Free Software Foundation, Inc.
Gforth comes with ABSOLUTELY NO WARRANTY; for details type `license'
Type `bye' to exit
: -- CR 10 0 DO 45 EMIT LOOP ; ok
--
---------- ok
-- -- --
----------
----------
---------- ok
bye
obijuan@Hoth:~
$ Y ahora seguimos el mismo proceso de siempre: Primero compilación manual. Luego al diccionario y por último compilación automática
Este me ha costado más, pero ya tengo una primra versión compilada manualmente que implementa el programa anterior:
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-12
$ ./run.sh 220-do-loop.s
Z80 CamelForth v1.01 25 Jan 1995
0
0
0x10010200 Link: 0x100101f5
0x10010204 Inmd: 0
0x10010205 NLen: 02
0x10010206 Name: --
0x10010208 CFA: 0x1001020c
0x1001020c : 0xffc40413
0x10010210 : 0x00142023
0x10010214 : 0x00401337
0x10010218 : 0x002642b7
0x1001021c : 0x00c2d293
0x10010220 : 0x005362b3
0x10010224 : 0x000280e7
0x10010228 : 0x00400337
0x1001022c : 0x000342b7
0x10010230 : 0x00c2d293
0x10010234 : 0x005362b3
0x10010238 : 0x000280e7
0x1001023c : 0x00000005
0x10010240 : 0x00400337
0x10010244 : 0x000342b7
0x10010248 : 0x00c2d293
0x1001024c : 0x005362b3
0x10010250 : 0x000280e7
0x10010254 : 0x00000000
0x10010258 : 0x00400337
0x1001025c : 0x008082b7
0x10010260 : 0x00c2d293
0x10010264 : 0x005362b3
0x10010268 : 0x000280e7
0x1001026c : 0x00400337
0x10010270 : 0x000342b7
0x10010274 : 0x00c2d293
0x10010278 : 0x005362b3
0x1001027c : 0x000280e7
0x10010280 : 0x0000002d
0x10010284 : 0x00400337
0x10010288 : 0x006b02b7
0x1001028c : 0x00c2d293
0x10010290 : 0x005362b3
0x10010294 : 0x000280e7
0x10010298 : 0x00400337
0x1001029c : 0x0006c2b7
0x100102a0 : 0x00c2d293
0x100102a4 : 0x005362b3
0x100102a8 : 0x000280e7
0x100102ac : 0x1001026c
0x100102b0 : 0x00042083
0x100102b4 : 0x00440413
0x100102b8 : 0x00008067
0x100102bc : 0x00000000
0x100102c0 : 0x00000000
0x100102c4 : 0x00000000
0x100102c8 : 0x00000000
0x100102cc : 0x00000000
0x100102d0 : 0x00000000
0x100102d4 : 0x00000000
0x100102d8 : 0x00000000
0x100102dc : 0x00000000
0x100102e0 : 0x00000000
0x100102e4 : 0x00000000
0x100102e8 : 0x00000000
0x100102ec : 0x00000000
0x100102f0 : 0x00000000
ok
WORDS
-- SPACE EMIT ELSE THEN IF "HI .WLINFO ONE TEST5 ; : ESC A WORDS NOP .S . + BYE lit EXIT ok
--
-----ok
-- -- -- --
-----
-----
-----
-----ok
BYE
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-12
$ Ya tengo factorizado el ejemplo 220-do-loop.s. Ya están implementadas las palabras DO y LOOP... Ahora toca el segundo paso: añadirlas al diccionario
Listo!
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-13
$ ./run.sh 221-do-loop.s
Z80 CamelForth v1.01 25 Jan 1995
ok
WORDS
CR LOOP DO SPACE EMIT ELSE THEN IF "HI .WLINFO ONE TEST5 ; : ESC A WORDS NOP .S . + BYE lit EXIT ok
: -- CR 10 0 DO 45 EMIT LOOP ;
ok
-- -- CR
----------
----------
ok
BYE
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-13
$ El siguiente operando importante es ." para imprimir cadenas de caracteres en la consola... vamos a ver cómo lo implementamos...
Antes de implementar ." hay que implementar el operador estándar S": https://forth-standard.org/standard/core/Sq
Su misión es Añadir la cadena de caracteres al diccionario. En nuestro caso lo que se almacena es una llamada a XSQuote y a continuación los bytes de la cadena. Se trata de una cadena contadora. En tiempo de ejecución, XSQuote devuelve la dirección del primer carácter y el número total de caracteres de la cadena. Los pone en la pila
El S" ya lo tengo listo....
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-13
$ ./run.sh 222-squote.s
Z80 CamelForth v1.01 25 Jan 1995
0x10010263
0x10010238 Link: 0x1001022d
0x1001023c Inmd: 0
0x1001023d NLen: 02
0x1001023e Name: S"
0x10010240 CFA: 0x10010244
0x10010244 : 0xffc40413
0x10010248 : 0x00142023
0x1001024c : 0x00400337
0x10010250 : 0x000a02b7
0x10010254 : 0x00c2d293
0x10010258 : 0x005362b3
0x1001025c : 0x000280e7
0x10010260 : 0x20323102
0x10010264 : 0x00042083
0x10010268 : 0x00440413
0x1001026c : 0x00008067
0x10010270 : 0x00000000
0x10010274 : 0x00000000
0x10010278 : 0x00000000
0x1001027c : 0x00000000
0x10010280 : 0x00000000
ok
.S
ok
S"
ok
.S
268501601 2 ok
TYPE
12ok
BYE
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-13
$ En este ejemplo se compila la palabra S" (se le da el valor 12 desde el código). Vemos que en la pila no hay nada. Ahora ejecutamos S". En la pila se deposita la dirección de la cadena, y su longitud. Al ejecutar el comando TYPE (que está metido en el diccionario) se imprime la cadena "12"....
¡FUNCIONA!!!!!!
El siguiente paso es meterlo en el diccionario y usarlo en la construcción de otra palabra. Esto es lo que hacemos en este ejemplo:
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-13
$ ./run.sh 224-squote.s
Z80 CamelForth v1.01 25 Jan 1995
ok
WORDS
S" TYPE CR LOOP DO SPACE EMIT ELSE THEN IF "HI .WLINFO ONE TEST5 ; : ESC A WORDS NOP .S . + BYE lit EXIT ok
.S
ok
: T S" 12" ;
ok
.S
ok
T
ok
.S
268501613 2 ok
TYPE
12ok
BYESe define la palabra de prueba T que no es más que una cadena de texto: "12". Al llamar a T se mete en la pila la dirección la cadena y su longitud. Si a continuación llamamos a TYPE entonces imprimimos la cadena...
Ahora ya podemos crear .". Como siempre. Primero compilado a mano, luego en el diccionario y finalmente en modo interactivo
LISTO! Ya está la palabra completa. OJO! Esta palabra, tal cual está definida en el CamelForth, NO ES INTERACTIVA. Es verdad que no tiene mucho sentido usarla en modo interactivo. Está pensada para usarse dentro de otras palabras.
En este ejemplo Definimos esta palabra de prueba T: : T ." HOLI!" ;
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-13
$ ./run.sh 225-dotquote.s
Z80 CamelForth v1.01 25 Jan 1995
ok
WORDS
." S" TYPE CR LOOP DO SPACE EMIT ELSE THEN IF "HI .WLINFO ONE TEST5 ; : ESC A WORDS NOP .S . + BYE lit EXIT ok
: T ." HOLI!" ;
ok
T
HOLI!ok
BYEAquí definimos otra palabra T, que indica si el valor de la pila es VERDADERO o FALSE:
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-13
$ ./run.sh 225-dotquote.s
Z80 CamelForth v1.01 25 Jan 1995
ok
1 1 + .
2 ok
: T IF ." TRUE" ELSE ." FALSE" THEN CR ;
ok
0 T
FALSE
ok
1 T
TRUE
ok
BYE
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-13
$ Lo que queda ahora es ir sistemáticamente implementando TODAS las palabras que quedan... junto con sus programas de prueba...
Pero antes voy a aprovechar para hacerme algunos programas mios de pruebas, usando secuencias ANSI...
Las puedo practicas primero con gforth, y luego las pruebo en el rv-forth
Me metido las siguientes palabras: EESC (Emit ESC), HOME y CLS
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-13
$ ./run.sh 226-esc.s
Z80 CamelForth v1.01 25 Jan 1995
ok
WORDS
HOME CLS EESC ." S" TYPE CR LOOP DO SPACE EMIT ELSE THEN IF "HI .WLINFO ONE TEST5 ; : ESC A WORDS NOP .S . + BYE lit EXIT ok
BYE
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-13
$Si queremos definir el color verde, una forma sería así:
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-13
$ ./run.sh 226-esc.s
Z80 CamelForth v1.01 25 Jan 1995
ok
: GREEN EESC ." [32m" ;
ok
GREEN
ok
Esto sale en verde
Esto?
sale?
en?
verde?
ok
BYE
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-13
$Para empezar a tener programas guardados en ficheros de texto y hacer "copy & paste" al intérprete, necesito primero implementar los comentarios
¡¡Ya tengo comentarios!!!!!!! Funcionan tanto en la compilacion como en el modo interpretado...
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-13
$ ./run.sh 227-paren.s
Z80 CamelForth v1.01 25 Jan 1995
ok
( Esto son comentarios )
ok
( no hace falta poner espacio antes del ultimo)
ok
: T ( palabra de prueba: -- ) ." TEST!" ;
ok
T
TEST! ok
T T
TEST!TEST! ok
( vaaaaaamos!!!!)
ok
BYE
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-13
$
Ya sólo me quedan 19 palabras para completar el Camelforth! Esto ya empieza a tomar forma!!
La palabra RECURSE se utiliza para hacer recursividad en Forth. Lo que hace es añadir una llamada a la propia funcion que se está definiendo. Así por ejemplo si estamos definiendo la palabra T de esta forma: : T RECURSE ;, traducido a ensamblador lo que se genera es:
T: jal TEs decir, una llamada a la propia función...
Este programa mete en la pila una cuenta recursiva 5 4 3 2 1 0...
: T DUP 0 > IF DUP 1- RECURSE THEN ;
5 T .SEl resultado devuelto es: <6> 5 4 3 2 1 0 ok
Este será el programa de pruebas para comprobar que RECURSE funciona
Primero comprobamos que recurse añadie la llamada a la propia función:
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-14
$ ./run.sh 255-recurse.s
Z80 CamelForth v1.01 25 Jan 1995
0x1001028c Link: 0x10010285
0x10010290 Inmd: 0
0x10010291 NLen: 01
0x10010292 Name: T
0x10010294 CFA: 0x10010298
0x10010298 : 0xffc40413
0x1001029c : 0x00142023
0x100102a0 : 0x10010337
0x100102a4 : 0x002982b7
0x100102a8 : 0x00c2d293
0x100102ac : 0x005362b3
0x100102b0 : 0x000280e7
0x100102b4 : 0x00042083
0x100102b8 : 0x00440413
0x100102bc : 0x00008067
0x100102c0 : 0x00000000
ok
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-14
$Ahora implementamos el programa de prueba, compilado a mano...
Listo.. con compilación manual ya está. Este es el resultado:
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-14
$ ./run.sh 256-recurse.s
Z80 CamelForth v1.01 25 Jan 1995
4 3 2 1 0
ok
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-14
$
Ahora lo metemos en el diccionario y lo probamos en el modo interpretado
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-14
$ ./run.sh 257-recurse.s
Z80 CamelForth v1.01 25 Jan 1995
ok
WORDS
RECURSE 1- > DUP ( HOME CLS EESC ." S" TYPE CR LOOP DO SPACE EMIT ELSE THEN IF "HI .WLINFO ONE TEST5 ; : ESC A WORDS NOP .S . + BYE lit EXIT ok
ok
: T DUP 0 > IF DUP 1- RECURSE THEN ;
ok
5 T
ok
.S
5 4 3 2 1 0 ok
BYE
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-14
$ VAAAAAAAAMOS!!! FUNCIONA!!!!! UUUEEEEEEEEEE
Este comando no lo llego a entender bien... me falta práctica... así que de momento lo dejo, será de los pocos que no implemente...
Empezamos por el Begin ... UNTIL... que es un bucle que se repite hasta que se cumpla una condición de terminación. En este ejemplo se tiene un contador que empieza en 5, y se va decrementando hasta llegar a 0, en ese momento se sale del bucle
: T BEGIN DUP . 1- DUP 0 = UNTIL ;
5 T 5 4 3 2 1 okYa funciona...
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-14
$ ./run.sh 259-begin.s
Z80 CamelForth v1.01 25 Jan 1995
0x100102dc
0x100102c8 Link: 0x100102b9
0x100102cc Inmd: 0
0x100102cd NLen: 01
0x100102ce Name: T
0x100102d0 CFA: 0x100102d4
0x100102d4 : 0xffc40413
0x100102d8 : 0x00142023
0x100102dc : 0x00400337
0x100102e0 : 0x004b02b7
0x100102e4 : 0x00c2d293
0x100102e8 : 0x005362b3
0x100102ec : 0x000280e7
0x100102f0 : 0x00401337
0x100102f4 : 0x005b02b7
0x100102f8 : 0x00c2d293
0x100102fc : 0x005362b3
0x10010300 : 0x000280e7
0x10010304 : 0x00400337
0x10010308 : 0x003542b7
0x1001030c : 0x00c2d293
0x10010310 : 0x005362b3
0x10010314 : 0x000280e7
0x10010318 : 0x00400337
0x1001031c : 0x004b02b7
0x10010320 : 0x00c2d293
0x10010324 : 0x005362b3
0x10010328 : 0x000280e7
0x1001032c : 0x00400337
0x10010330 : 0x000342b7
0x10010334 : 0x00c2d293
0x10010338 : 0x005362b3
0x1001033c : 0x000280e7
0x10010340 : 0x00000000
0x10010344 : 0x00400337
0x10010348 : 0x004342b7
0x1001034c : 0x00c2d293
0x10010350 : 0x005362b3
0x10010354 : 0x000280e7
0x10010358 : 0x00400337
0x1001035c : 0x000482b7
0x10010360 : 0x00c2d293
0x10010364 : 0x005362b3
0x10010368 : 0x000280e7
0x1001036c : 0x100102dc
0x10010370 : 0x00042083
0x10010374 : 0x00440413
0x10010378 : 0x00008067
0x1001037c : 0x00000000
ok
5 T
5 4 3 2 1 ok
BYE
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-14
$ Podemos ver cómo por haber hecho una implementación STC, el codigo es largo... la próxima implementación será DTC... el código máquina del forth será mucho más corto...
Esta es la estructura para hacer bucles infinitos. Est es un ejemplo de uno que imprime constantemente la letra 'A':
: INF BEGIN 65 EMIT AGAIN ;obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-14
$ ./run.sh 260-again.s
Z80 CamelForth v1.01 25 Jan 1995
0x100102dc
0x100102c8 Link: 0x100102b9
0x100102cc Inmd: 0
0x100102cd NLen: 01
0x100102ce Name: T
0x100102d0 CFA: 0x100102d4
0x100102d4 : 0xffc40413
0x100102d8 : 0x00142023
0x100102dc : 0x00400337
0x100102e0 : 0x000342b7
0x100102e4 : 0x00c2d293
0x100102e8 : 0x005362b3
0x100102ec : 0x000280e7
0x100102f0 : 0x00000041
0x100102f4 : 0x00400337
0x100102f8 : 0x006842b7
0x100102fc : 0x00c2d293
0x10010300 : 0x005362b3
0x10010304 : 0x000280e7
0x10010308 : 0x00400337
0x1001030c : 0x000642b7
0x10010310 : 0x00c2d293
0x10010314 : 0x005362b3
0x10010318 : 0x000280e7
0x1001031c : 0x100102dc
0x10010320 : 0x00042083
0x10010324 : 0x00440413
0x10010328 : 0x00008067
0x1001032c : 0x00000000
ok
T
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
...Este es nuestro programa de pruebas:
: T 1 BEGIN 65 EMIT DUP 5 < WHILE 66 EMIT 1+ REPEAT ; ok
ok
T ABABABABA okY aquí está en funcionamiento, con compilado manual
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-14
$ ./run.sh 261-while.s
Z80 CamelForth v1.01 25 Jan 1995
0x100102dc
0x100102c8 Link: 0x100102b9
0x100102cc Inmd: 0
0x100102cd NLen: 01
0x100102ce Name: T
0x100102d0 CFA: 0x100102d4
0x100102d4 : 0xffc40413
0x100102d8 : 0x00142023
0x100102dc : 0x00400337
0x100102e0 : 0x000342b7
0x100102e4 : 0x00c2d293
0x100102e8 : 0x005362b3
0x100102ec : 0x000280e7
0x100102f0 : 0x00000041
0x100102f4 : 0x00400337
0x100102f8 : 0x006ec2b7
0x100102fc : 0x00c2d293
0x10010300 : 0x005362b3
0x10010304 : 0x000280e7
0x10010308 : 0x00400337
0x1001030c : 0x004dc2b7
0x10010310 : 0x00c2d293
0x10010314 : 0x005362b3
0x10010318 : 0x000280e7
0x1001031c : 0x00400337
0x10010320 : 0x000342b7
0x10010324 : 0x00c2d293
0x10010328 : 0x005362b3
0x1001032c : 0x000280e7
0x10010330 : 0x00000005
0x10010334 : 0x00400337
0x10010338 : 0x0048c2b7
0x1001033c : 0x00c2d293
0x10010340 : 0x005362b3
0x10010344 : 0x000280e7
0x10010348 : 0x00400337
0x1001034c : 0x000482b7
0x10010350 : 0x00c2d293
0x10010354 : 0x005362b3
0x10010358 : 0x000280e7
0x1001035c : 0x100103b8
0x10010360 : 0x00400337
0x10010364 : 0x000342b7
0x10010368 : 0x00c2d293
0x1001036c : 0x005362b3
0x10010370 : 0x000280e7
0x10010374 : 0x00000042
0x10010378 : 0x00400337
0x1001037c : 0x006ec2b7
0x10010380 : 0x00c2d293
0x10010384 : 0x005362b3
0x10010388 : 0x000280e7
0x1001038c : 0x00400337
0x10010390 : 0x003682b7
0x10010394 : 0x00c2d293
0x10010398 : 0x005362b3
0x1001039c : 0x000280e7
0x100103a0 : 0x00400337
0x100103a4 : 0x000642b7
0x100103a8 : 0x00c2d293
0x100103ac : 0x005362b3
0x100103b0 : 0x000280e7
0x100103b4 : 0x100102dc
0x100103b8 : 0x00042083
0x100103bc : 0x00440413
0x100103c0 : 0x00008067
0x100103c4 : 0x00000000
ok
T
ABABABABABA ok
ok
Este es el ejemplo para LEAVE. Es un bucle que se tiene que repetir 100 veces (imprimir 100 As), pero cuando la variable llega a 5, se llama a LEAVE para salir... por lo que sólo se imprimen las primeras As (y no las 100)
: T 100 0 DO 65 EMIT I 5 = IF LEAVE THEN LOOP ; redefined T ok
ok
T AAAAAA okYa está funcionando...
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-14
$ ./run.sh 262-leave.s
Z80 CamelForth v1.01 25 Jan 1995
0x100103f4
0x100102c8 Link: 0x100102b9
0x100102cc Inmd: 0
0x100102cd NLen: 01
0x100102ce Name: T
0x100102d0 CFA: 0x100102d4
0x100102d4 : 0xffc40413
0x100102d8 : 0x00142023
0x100102dc : 0x00400337
0x100102e0 : 0x000342b7
0x100102e4 : 0x00c2d293
0x100102e8 : 0x005362b3
0x100102ec : 0x000280e7
0x100102f0 : 0x00000064
0x100102f4 : 0x00400337
0x100102f8 : 0x000342b7
0x100102fc : 0x00c2d293
0x10010300 : 0x005362b3
0x10010304 : 0x000280e7
0x10010308 : 0x00000000
0x1001030c : 0x00400337
0x10010310 : 0x0082c2b7
0x10010314 : 0x00c2d293
0x10010318 : 0x005362b3
0x1001031c : 0x000280e7
0x10010320 : 0x00400337
0x10010324 : 0x000342b7
0x10010328 : 0x00c2d293
0x1001032c : 0x005362b3
0x10010330 : 0x000280e7
0x10010334 : 0x00000041
0x10010338 : 0x00400337
0x1001033c : 0x006d42b7
0x10010340 : 0x00c2d293
0x10010344 : 0x005362b3
0x10010348 : 0x000280e7
0x1001034c : 0x00400337
0x10010350 : 0x008cc2b7
0x10010354 : 0x00c2d293
0x10010358 : 0x005362b3
0x1001035c : 0x000280e7
0x10010360 : 0x00400337
0x10010364 : 0x000342b7
0x10010368 : 0x00c2d293
0x1001036c : 0x005362b3
0x10010370 : 0x000280e7
0x10010374 : 0x00000005
0x10010378 : 0x00400337
0x1001037c : 0x004482b7
0x10010380 : 0x00c2d293
0x10010384 : 0x005362b3
0x10010388 : 0x000280e7
0x1001038c : 0x00400337
0x10010390 : 0x000482b7
0x10010394 : 0x00c2d293
0x10010398 : 0x005362b3
0x1001039c : 0x000280e7
0x100103a0 : 0x100103d0
0x100103a4 : 0x00400337
0x100103a8 : 0x008ec2b7
0x100103ac : 0x00c2d293
0x100103b0 : 0x005362b3
0x100103b4 : 0x000280e7
0x100103b8 : 0x00400337
0x100103bc : 0x000642b7
0x100103c0 : 0x00c2d293
0x100103c4 : 0x005362b3
0x100103c8 : 0x000280e7
0x100103cc : 0x100103e8
0x100103d0 : 0x00400337
0x100103d4 : 0x0006c2b7
0x100103d8 : 0x00c2d293
0x100103dc : 0x005362b3
0x100103e0 : 0x000280e7
0x100103e4 : 0x10010320
0x100103e8 : 0x00042083
0x100103ec : 0x00440413
0x100103f0 : 0x00008067
0x100103f4 : 0x00000000
ok
T
AAAAAA ok
BYE
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-14
$ Esta es la última instrucción!! (bueno, me queda POSTPONE, pero de momento lo dejo....)
: T 10 0 DO I . 2 +LOOP ; ok
ok
T 0 2 4 6 8 okLISO!! Ya está implementado!!!
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-14
$ ./run.sh 263-plusloop.s
Z80 CamelForth v1.01 25 Jan 1995
0x10010384
0x100102c8 Link: 0x100102b9
0x100102cc Inmd: 0
0x100102cd NLen: 01
0x100102ce Name: T
0x100102d0 CFA: 0x100102d4
0x100102d4 : 0xffc40413
0x100102d8 : 0x00142023
0x100102dc : 0x00400337
0x100102e0 : 0x000342b7
0x100102e4 : 0x00c2d293
0x100102e8 : 0x005362b3
0x100102ec : 0x000280e7
0x100102f0 : 0x0000000a
0x100102f4 : 0x00400337
0x100102f8 : 0x000342b7
0x100102fc : 0x00c2d293
0x10010300 : 0x005362b3
0x10010304 : 0x000280e7
0x10010308 : 0x00000000
0x1001030c : 0x00400337
0x10010310 : 0x008442b7
0x10010314 : 0x00c2d293
0x10010318 : 0x005362b3
0x1001031c : 0x000280e7
0x10010320 : 0x00400337
0x10010324 : 0x008e42b7
0x10010328 : 0x00c2d293
0x1001032c : 0x005362b3
0x10010330 : 0x000280e7
0x10010334 : 0x00401337
0x10010338 : 0x005dc2b7
0x1001033c : 0x00c2d293
0x10010340 : 0x005362b3
0x10010344 : 0x000280e7
0x10010348 : 0x00400337
0x1001034c : 0x000342b7
0x10010350 : 0x00c2d293
0x10010354 : 0x005362b3
0x10010358 : 0x000280e7
0x1001035c : 0x00000002
0x10010360 : 0x00400337
0x10010364 : 0x000a02b7
0x10010368 : 0x00c2d293
0x1001036c : 0x005362b3
0x10010370 : 0x000280e7
0x10010374 : 0x10010320
0x10010378 : 0x00042083
0x1001037c : 0x00440413
0x10010380 : 0x00008067
0x10010384 : 0x00000000
ok
T
0 2 4 6 8 ok
BYE
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-14
$ Ahora hay que meter todas las palabras en el diccionario... sin embargo lo voy a ir haciendo a la vez que aprendo más forth... voy a re-leer el libro de forth haciendo los ejemplos en el rv-forth...
Es el momento de llevar lo que tengo hasta ahora a un repositorio nuevo, y empezar a organizarlo. El proyecto lo voy a llamar RiscvForth (RvForth para abreviar). Lo voy a separar en dos ficheros: primitivas y secundarios. El diccionario está en otro fichero separado. También necesito uno de inicialización (init?) que contenga la estructura inicial de todo en memoria
Esta primera versión es para el RARS-1.6, y es de tipo STC (Subrutine Threaded code)
Para imprimir caracteres unicode desde forth. Primero nos vamos a la tabla de caracteres unicode. Hay muchas:
- Tabla en wikipedia: https://en.wikipedia.org/wiki/List_of_Unicode_characters
- Otra tabla: https://www.tamasoft.co.jp/en/general-info/unicode.html
- Este buscador está muy bien: https://symbl.cc/en/
Vamos a fijarnos por ejemplo en la letra ñ. Su código unicode es U+00F1. Para decodificarlo podemos usar este programa en python:
"ñ".encode()Que nos devuelve la cadena en bytes: b'\xc3\xb1'. Por tanto, la ñ se codifica como 0xC3B1
Ahora imprimimos esos dos caracteres en forth:
$ gforth
Gforth 0.7.3, Copyright (C) 1995-2008 Free Software Foundation, Inc.
Gforth comes with ABSOLUTELY NO WARRANTY; for details type `license'
Type `bye' to exit
hex ok
c3 emit b1 emit ñ ok
byey tachan!!! Sale la ñ
Definimos la palabra UHLINE. Es el caracter unicode ─:
: UHLINE 226 EMIT 148 EMIT 128 EMIT ;Si lo probamos en gforth, efectivamente sale el caracter de linea:
226 emit 148 emit 128 emit ─ okSin embargo, al probarlo en el riscvForth salen caracteres raros. Tanto para la línea como para la ñ. Vamos a probarlo con la ñ por facilidad: (Metemos los números de la ñ en decimal, 0xc3=195, 0xb1=177
195 EMIT 177 EMIT
ñ okHay que solucionar este bug...
Para solucionar el bug he probado este programa directamente en el RARs, para imprimir una cadena con la letra ñ:
.data
msg: .string "ñ"
msg2: .byte 0xc3, 0xb1, 0 #-- ñ en bytes
.text
#-- Imprimir ñ como cadena
la a0, msg
li a7, 4
ecall
#-- Imprimir ñ como cadena, pero con bytes separados
la a0, msg2
li a7, 4
ecall
#-- Imprimir la ñ imprimiendo sus dos caracteres aislados
li a0, 0xc3
li a7, 11
ecall
li a0, 0xb1
li a7, 11
ecall
li a7, 10
ecallY este es el resultado:
ñññ
-- program is finished running (0) --
Conclusión: para impriir caracteres unicode hay que hacerlo llamando al servicio de impresión de cadenas, y NO carácter a carácter
Así que voy a definir la palabra XEMIT para emitir todos los bytes del carácter unicode. En el forth estándar, se le pasa como parámetro el código unicode... sin embargo, en mi implementación voy a meter directamente los códigos ya traducidos. Para la ñ el número a enviar será el 0xc3b1 (enviando primero el c3 y luego el b1)
Para unificar voy a hacer pruebas también con el carácter de línea horizontal:
.data
msg: .string "─"
msg2: .byte 0xe2, 0x94, 0x80, 0
.text
#-- Imprimi como cadena
la a0, msg
li a7, 4
ecall
#-- Imprimir como cadena, pero con bytes separados
la a0, msg2
li a7, 4
ecall
li a7, 10
ecallEste es el resultado:
──
-- program is finished running (0) --
Los bytes los voy a imprimir empezando por el menos significativo. Así, para imprimir los siguienets caracteres hay que enviar los códigos indicados:
- Letra ñ: 0x00b1c3
- Caracter ─: 0x008094e2
Sin embargo, como con el RARs tengo el problema de no poder meter números junto con el código, la llamada a xemit la vamos a hacer metiendo el número como bytes. Dado que el unicode es de longitud variable, siepre el primer numero será un 0 (que indica el final), así, para impmrimir los caracteres anteriores las llamadas a xemit serán:
- Letra ñ: 0 b1 c3 xemit
- Caracter ─: 00 80 94 e2 xemit
¡Vamos a implementarlo!
¡Listo!
obijuan@Hoth:~/Develop/RiscvForth
$ ./run.sh prueba.s
RiscvForth v0.1 15 Jun 2023
ñ─
ok
obijuan@Hoth:~/Develop/RiscvForth
$
Esta es la nueva palabra UHLINE que imprime el caracter de línea horizontal:
: UHLINE 0 128 148 226 XEMIT ;Lo probamos:
obijuan@Hoth:~/Develop/RiscvForth
$ ./riscvforth.sh
RiscvForth v0.1 15 Jun 2023
ok
: UHLINE 0 128 148 226 XEMIT ;
ok
UHLINE
─ ok
BYE
obijuan@Hoth:~/Develop/RiscvForth
$ ¡FUNCIONA! uueeeeeeeee
La putada es que el XEMIT del riscvForth es diferente del estándar... y por tanto la palabra UHLINE hay que definirla de manera diferente en riscvForth y en gforth...
Estoy pensando que tal vez pueda modificar el comportamiento de emit de riscv para pueda imprimir tanto caracteres normales como unicode. Si en el gforth ejecuto este comando, se imprime la línea horizontal:
226 emit 148 emit 128 emit 0 emit ─ ok(Nótese que he añadido el 0 al final)
Bien, puedo incluir una máquina de estados en el emit del riscvForth tal que permita enviar los dos tipos de caracteres: ASCII y unicode. El comportamiento sería algo así:
- Inicialment estamos en modo ascii
- Si el caracter recibido es menor a 128, se imprime como caracter ascii normal
- Si el caracter recibido es mayor o igual a 128 se pasa a modo unicode y este caracter se guarda en la zona unicode (y no se emite nada todavia)
- Si ahora llega el caracter 0, se gudarda en la zona unicode, se imprime como cadena y se retorna al modo ascii. Usamos el 0 para delimitar el fin del unicode
- Si estando en modo unicode llega cualquier byte, se almacena en la zona unicode
Esto funcionará siempre y cuando en los caracteres unicodes a imprimir no haya 0s intermedios...
Creo que lo voy a implementar...
Siiiii..... Esto ya funciona enel riscvForth:
$ ./riscvforth.sh
RiscvForth v0.1 15 Jun 2023
ok
226 EMIT 148 EMIT 128 EMIT 0 EMIT
─ ok
BYE
obijuan@Hoth:~/Develop/RiscvForth
$ Por tanto, la siguiente definición de UHLINE debería funcionar en ambos forths:
: UHLINE 226 EMIT 148 EMIT 128 EMIT 0 EMIT ;Vamos a coprobarlo...
obijuan@Hoth:~
$ gforth
Gforth 0.7.3, Copyright (C) 1995-2008 Free Software Foundation, Inc.
Gforth comes with ABSOLUTELY NO WARRANTY; for details type `license'
Type `bye' to exit
: UHLINE 226 EMIT 148 EMIT 128 EMIT 0 EMIT ; ok
UHLINE CR ─
ok
BYE
obijuan@Hoth:~
$ obijuan@Hoth:~/Develop/RiscvForth
$ ./riscvforth.sh
RiscvForth v0.1 15 Jun 2023
ok
: UHLINE 226 EMIT 148 EMIT 128 EMIT 0 EMIT ;
ok
UHLINE CR
─
ok
BYE
obijuan@Hoth:~/Develop/RiscvForth
$ Siiiiiii!!!!!!!
Antes de seguir implementando más cosas en el RiscForth, voy a frikear un poco con Forth, para aprender. Voy a definir la palabra HLINE para dibujar una línea de x caracteres, donde x está en la pila. Lo hago primero en gforth y luego compruebo en rvForth
: UHLINE 226 EMIT 148 EMIT 128 EMIT 0 EMIT ;
: HLINE ( x -- ) 0 DO UHLINE LOOP ;
10 HLINEEste es el resultado:
obijuan@Hoth:~
$ gforth
Gforth 0.7.3, Copyright (C) 1995-2008 Free Software Foundation, Inc.
Gforth comes with ABSOLUTELY NO WARRANTY; for details type `license'
Type `bye' to exit
: UHLINE 226 EMIT 148 EMIT 128 EMIT 0 EMIT ; ok
UHLINE ─ ok
: HLINE ( x -- ) 0 DO UHLINE LOOP ; ok
10 HLINE ────────── ok
BYE
obijuan@Hoth:~
$obijuan@Hoth:~/Develop/RiscvForth
$ ./riscvforth.sh
RiscvForth v0.1 15 Jun 2023
ok
: UHLINE 226 EMIT 148 EMIT 128 EMIT 0 EMIT ;
ok
: HLINE ( x -- ) 0 DO UHLINE LOOP ;
ok
10 HLINE
────────── ok
BYE
obijuan@Hoth:~/Develop/RiscvForth
$ Definimos la palabra HEADER:
: UHLINE 226 EMIT 148 EMIT 128 EMIT 0 EMIT ;
: HLINE ( x -- ) 0 DO UHLINE LOOP ;
: HEADER ( addr u len -- ) CR DUP HLINE CR ROT ROT TYPE CR HLINE CR ;
S" HOLI-1" 30 HEADER CR
──────────────────────────────
HOLI-1
──────────────────────────────
okEn el rvForth funciona, pero no S" no se puede usar directamente en el intérprete (todavía) sino compilado como parte de otra palabra. Usamos la palabra de prueba T: : T S" HOLI-1" 30 HEADER CR ;. Y ahora sí que funciona bien:
obijuan@Hoth:~/Develop/RiscvForth
$ ./riscvforth.sh
RiscvForth v0.1 15 Jun 2023
ok
: UHLINE 226 EMIT 148 EMIT 128 EMIT 0 EMIT ;
: HLI NE ( x -- ) 0 DO UHLINE LOOP ; ok
ok
: HEADER ( addr u len -- ) CR DUP HLINE CR ROT ROT TYPE CR HLINE CR ;
ok
: T S" HOLI-1" 30 HEADER CR ;
ok
T
──────────────────────────────
HOLI-1
──────────────────────────────
ok
T
──────────────────────────────
HOLI-1
──────────────────────────────
okEstoy implementando las variables, haciendo refactorización. He encontrado un problema con la implementacińo actual. Variables aisladas se pueden crear y usar con normalidad, pero si ahora queremos por ejemplo crear un array de 10 celdas no es posible. Esto es debido a que al reservar espacio, la variable debe estar justo al final del diccionario... pero en la implementación actual se meten las instrucciones de retorno... Hay que modificar el doval actual para que tras el jal sólo haya variables...
Ya tengo las varibles y constantes implementadas. Para comprobar que está todo ok, voy a ir migrando el programa de test ttester.fs al rvForth. El programa completo está aquí: https://forth-standard.org/standard/testsuite
Voy a empezar por la palabra T{ y añadiendo sus dependencias. Lo voy a compilar a mano, más que meterlo en el modo interactivo...
LISTO!!!!!!
obijuan@Hoth:~/Develop/RiscvForth
$ ./riscvforth.sh
RiscvForth v0.1 15 Jun 2023
ok
T{ 1 2 -> }T
WRONG NUMBER OF RESULTS ok
T{ 1 2 -> 1 2 }T
ok
T{ 1 2 -> 1 1 }T
INCORRECT RESULT ok
T{ 1 1 + -> 2 }T
ok
BYE
obijuan@Hoth:~/Develop/RiscvForth
$ A través de Mastodón encontré hace unas semanas esta implementación de Forth para RiscV:
Pero lo más interesante NO es eso, SINO QUE ES UN TUTORIAL!!!. Lo que quiero hacer es reproducirlo para entenderlo bien, e implementarlo en mi riscv. A ver si consigo de una vez entenderlo todo bien
El tutorial está aquí:
Está hecho para un riscv64... El RARs lo permite... así que igual lo pruebo tal cual, adaptándolo al RARS, y luego lo migro a 32 bits para ejecutarlo en un RISCV de verdad
He encontrado este mismo forth para un risv32:
Aunque el tutorial que voy a hacer es para el RV64... lo voy a ir migrando a RV32 según lo hago
Siguiendo la idea del tutorial, vamos a realizar primero un programa que simplemente llame a 3 funciones. La última de ellas llama a EXIT y termina
Empezamos por un programa que simplemente llama a 3 subrutinas y termina. La cuarta subrutina llama directamente al servicio EXIT del sistema operativo
- Fichero:
01-main.s
#-- JonesForth. Fundamentos de Forth
#-- En este primer ejemplo se llama a 3 subrutinas
#-- Servicios del sistema operativo del RARs
.eqv EXIT 10 #-- Terminar
.eqv PRINT_CHAR 11 #-- Imprimir un caracter
.text
#-- Ejecutar las subrutinas
jal pa
jal pb
jal pc
jal exit
#-- Nunca se retorna
#-------------------------------
#-- pa: Imprimir el caracter A
#-------------------------------
pa:
#-- Imprimir caracter A
li a0, 'A'
li a7, PRINT_CHAR
ecall
ret
#-------------------------------
#-- pb: Imprimir el caracter B
#-------------------------------
pb:
#-- Imprimir caracter B
li a0, 'B'
li a7, PRINT_CHAR
ecall
ret
#-------------------------------
#-- pc: Imprimir el caracter C
#-------------------------------
pc:
#-- Imprimir caracter C
li a0, 'C'
li a7, PRINT_CHAR
ecall
ret
#--------------------
#-- Terminar
#--------------------
exit:
li a0, '\n'
li a7, PRINT_CHAR
ecall
#-- Terminar
li a7, EXIT
ecallAl ejecutarlo se obtiene lo esperado:
obijuan@JANEL:~/Develop/Learn-forth/Log/2024-10-29$ ./run.sh 01-main.s
ABC
obijuan@JANEL:~/Develop/Learn-forth/Log/2024-10-29$ La clave son estas llamadas a subrutina:
jal pa
jal pb
jal pc
jal exitAsí es como desarrollé el intérprete de forth en el rvforth... está muy bien, se pueden hacer las cosas muy fáciles.. pero me encontré con problemas... y sobre todo al final quedó un código oscuro cuando quería hacer que el propio forth compilase
El siguiente paso es meter esas direcciones en una tabla en memoria, y hacer que se llemen unas a otras. Para ello hay que añadir código al final de cada subrutina, para saltar a la siguiente. Ese salto se logra definiendo un puntero, s1, a la siguiente instruccuón
Este es el nuevo código:
- Fichero:
02-main.s
#-- JonesForth. Fundamentos de Forth
#-- En este primer ejemplo se llama a 3 subrutinas
#-- Servicios del sistema operativo del RARs
.eqv EXIT 10 #-- Terminar
.eqv PRINT_CHAR 11 #-- Imprimir un caracter
.data
#-- Lista con las subrutinas a ejecutar
#-- Es NUESTRO PROGRAMA!
prog: .word pa, pb, pc, exit
#-- 1 2 3 4
.text
#-- Inicializar registro S1
#-- Hacemos que apunte a la siguiente subrutina
la s1, prog
addi s1,s1,4 #-- S1 apunta a la instruccion 2
#-- Ejecutar la primera instrucciones!
jal pa
#-- Nunca se retorna
#-------------------------------
#-- pa: Imprimir el caracter A
#-------------------------------
pa:
#-- Imprimir caracter A
li a0, 'A'
li a7, PRINT_CHAR
ecall
#-- Grupo de instrucciones para saltar a la siguiente subrutina
lw a0, 0(s1) #-- a0: Direccion de la siguiente subrutina
addi s1, s1, 4 #-- s1: Apunta a la siguiente
mv t0,a0 #lw t0, 0(a0)
jalr t0 #-- Ejecutarla!
#-------------------------------
#-- pb: Imprimir el caracter B
#-------------------------------
pb:
#-- Imprimir caracter B
li a0, 'B'
li a7, PRINT_CHAR
ecall
#-- Grupo de instrucciones para saltar a la siguiente subrutina
lw a0, 0(s1) #-- a0: Direccion de la siguiente subrutina
addi s1, s1, 4 #-- s1: Apunta a la siguiente
mv t0,a0 #lw t0, 0(a0)
jalr t0 #-- Ejecutarla!
#-------------------------------
#-- pc: Imprimir el caracter C
#-------------------------------
pc:
#-- Imprimir caracter C
li a0, 'C'
li a7, PRINT_CHAR
ecall
#-- Grupo de instrucciones para saltar a la siguiente subrutina
lw a0, 0(s1) #-- a0: Direccion de la siguiente subrutina
addi s1, s1, 4 #-- s1: Apunta a la siguiente
mv t0,a0 #lw t0, 0(a0)
jalr t0 #-- Ejecutarla!
#--------------------
#-- Terminar
#--------------------
exit:
#-- Terminar
li a7, EXIT
ecallAl ejecutarlo se obtiene el mismo resultado que con el programa anterior:
obijuan@JANEL:~/Develop/Learn-forth/Log/2024-10-29$ ./run.sh 02-main.s
ABC
obijuan@JANEL:~/Develop/Learn-forth/Log/2024-10-29$La clave ahora es que el mismo programa de antes se puede representar mediante una lista de direcciones (etiquetas). Cada etiqueta es el nombre de la "instrucción" (Palabra) que se quiere ejecutar
prog: .word pa, pb, pc, exitPara implementar cada una de las funciones lo hacemos en ensamblador, y hay que terminar con una serie de instrucciones que tienen el efecto de pasar a la siguiente instrucciones: ejecutar la siguiente instrucción
lw a0, 0(s1) #-- a0: Direccion de la siguiente subrutina
addi s1, s1, 4 #-- s1: Apunta a la siguiente
mv t0,a0 #lw t0, 0(a0)
jalr t0 #-- Ejecutarla!Estas instrucciones la metemos en la macro NEXT
- Fichero:
03-main.s
#-- JonesForth. Fundamentos de Forth
#-- Servicios del sistema operativo del RARs
.eqv EXIT 10 #-- Terminar
.eqv PRINT_CHAR 11 #-- Imprimir un caracter
#-- Macro para ejecutar la siguiente instruccion
.macro NEXT
lw a0, 0(s1) #-- a0: Direccion de la siguiente subrutina
addi s1, s1, 4 #-- s1: Apunta a la siguiente
mv t0,a0 #lw t0, 0(a0)
jalr t0 #-- Ejecutarla!
.end_macro
.data
#-- Lista con las subrutinas a ejecutar
#-- Es NUESTRO PROGRAMA!
prog: .word pa, pb, pc, exit
#-- 1 2 3 4
.text
#-- Inicializar registro S1
#-- Hacemos que apunte a la siguiente subrutina
la s1, prog
addi s1,s1,4 #-- S1 apunta a la instruccion 2
#-- Ejecutar la primera instrucciones!
jal pa
#-- Nunca se retorna
#-------------------------------
#-- pa: Imprimir el caracter A
#-------------------------------
pa:
#-- Imprimir caracter A
li a0, 'A'
li a7, PRINT_CHAR
ecall
#-- Ejecutar siguiente instruccion
NEXT
#-------------------------------
#-- pb: Imprimir el caracter B
#-------------------------------
pb:
#-- Imprimir caracter B
li a0, 'B'
li a7, PRINT_CHAR
ecall
NEXT
#-------------------------------
#-- pc: Imprimir el caracter C
#-------------------------------
pc:
#-- Imprimir caracter C
li a0, 'C'
li a7, PRINT_CHAR
ecall
NEXT
#--------------------
#-- Terminar
#--------------------
exit:
li a0, '\n'
li a7, PRINT_CHAR
ecall
#-- Terminar
li a7, EXIT
ecall
La ejecución es exactamente igual, pero ahora las funciones se escriben de forma más clara
obijuan@JANEL:~/Develop/Learn-forth/Log/2024-10-29$ ./run.sh 03-main.s
ABC
obijuan@JANEL:~/Develop/Learn-forth/Log/2024-10-29$ Las funciones que estamos escribiendo se llaman funciones primitivas (porque están implementadas en ensamblador). TODAS las funciones primitivas tienen que terminar con NEXT. El registro s1 es nuestro Puntero de instrucción (IP)
Esto es lo que se denomina direct threaded code (DTC): Código enhebrado directo.
Ahora imaginemos el programa anterior escrito en forth (omitimos la llamada al sistema exit)
: TEST pa pb pc ;
Una posible compilación de este programa sería precisamente como una lista de las direcciones de pa, pb y pc.. y la propia implementación de estas funciones primitivas en ensamblador. Esto es lo que hace que el compilador de forth sea fácil: Basta con recopilar las direcciones de las instrucciones primitivas. ¡Cualquiera puede hacer esta compilación! ¡Se puede hacer a mano facilmente!
La técnica DTC es muy útil para precisamente eso: si queremos ejecutar cualquier conjunto de funciones primitivas, escritras en ensablador PERO, si lo que queremos es poder ejecutar también código en Forth: es decir, ejecutar palabras primitivas (en asm) y también palabras definidas en forth, necesitamos incluir una doble indirección. Es la técnica que se conoce como ITC (Indirect Threaded Code)
En este caso la dirección a poner no es directamente la del código asm, sino que hay que ejecutar el intérprete que se encargará de ejecutar esas instrucciones. En el caso de código primitivo, NO HAY INTÉRPRETE. Por lo que la dirección del intérprete es el propio código. Por ello, para nostros la palabra PA lo que tiene es la dirección hacia su propio código. El código de PA está situado en la etiqueta code_PA
Así es como queda el programa
- Fichero:
04-main.s
#-- JonesForth. Fundamentos de Forth
#-- Servicios del sistema operativo del RARs
.eqv EXIT 10 #-- Terminar
.eqv PRINT_CHAR 11 #-- Imprimir un caracter
#-- Macro para ejecutar la siguiente instruccion
.macro NEXT
lw a0, 0(s1) #-- a0: Direccion del codeword de la palabra
addi s1, s1, 4 #-- s1: Apunta a la siguiente palabra
lw t0, 0(a0) #-- Leer direccion del interprete
jalr t0 #-- Ejecutar interprete
.end_macro
.data
#-- Palabra definida en FORTH
TEST: .word PA, PB, PC, EX
#-- 1 2 3 4
#-- Palabras primitivas
PA: .word code_pa
PB: .word code_pb
PC: .word code_pc
EX: .word code_ex
.text
#-- Inicializar registro S1
#-- Hacemos que apunte a la siguiente subrutina
la s1, TEST
addi s1,s1,4 #-- S1 apunta a la instruccion 2
#-- Ejecutar la primera instrucciones!
jal code_pa
#-- Nunca se retorna
#-------------------------------
#-- pa: Imprimir el caracter A
#-------------------------------
code_pa:
#-- Imprimir caracter A
li a0, 'A'
li a7, PRINT_CHAR
ecall
#-- Ejecutar siguiente instruccion
NEXT
#-------------------------------
#-- pb: Imprimir el caracter B
#-------------------------------
code_pb:
#-- Imprimir caracter B
li a0, 'B'
li a7, PRINT_CHAR
ecall
NEXT
#-------------------------------
#-- pc: Imprimir el caracter C
#-------------------------------
code_pc:
#-- Imprimir caracter C
li a0, 'C'
li a7, PRINT_CHAR
ecall
NEXT
#--------------------
#-- Terminar
#--------------------
code_ex:
li a0, '\n'
li a7, PRINT_CHAR
ecall
#-- Terminar
li a7, EXIT
ecallEl funcionamiento sigue siendo igual que antes:
obijuan@JANEL:~/Develop/Learn-forth/Log/2024-10-29$ ./run.sh 04-main.s
ABC
obijuan@JANEL:~/Develop/Learn-forth/Log/2024-10-29$Refactorizamos el código para juntar las partes de las palabras. Ahora, cada palabra primitiva tiene una parte en el segmento de datos (el puntero a su código), y el código en el segmento de código
- Fichero:
05-main.s
#-- JonesForth. Fundamentos de Forth
#-- Servicios del sistema operativo del RARs
.eqv EXIT 10 #-- Terminar
.eqv PRINT_CHAR 11 #-- Imprimir un caracter
#-- Macro para ejecutar la siguiente instruccion
.macro NEXT
lw a0, 0(s1) #-- a0: Direccion del codeword de la palabra
addi s1, s1, 4 #-- s1: Apunta a la siguiente palabra
lw t0, 0(a0) #-- Leer direccion del interprete
jalr t0 #-- Ejecutar interprete
.end_macro
.data
#-- Palabra definida en FORTH
TEST: .word PA, PB, PC, EX
#-- 1 2 3 4
.text
#-- Inicializar registro S1
#-- Hacemos que apunte a la siguiente subrutina
la s1, TEST
addi s1,s1,4 #-- S1 apunta a la instruccion 2
#-- Ejecutar la primera instrucciones!
jal code_pa
#-- Nunca se retorna
#-------------------------------
#-- PA: Imprimir el caracter A
#-------------------------------
.data
PA: .word code_pa
.text
code_pa:
li a0, 'A'
li a7, PRINT_CHAR
ecall
NEXT
#-------------------------------
#-- PB: Imprimir el caracter B
#-------------------------------
.data
PB: .word code_pb
.text
code_pb:
li a0, 'B'
li a7, PRINT_CHAR
ecall
NEXT
#-------------------------------
#-- PC: Imprimir el caracter C
#-------------------------------
.data
PC: .word code_pc
.text
code_pc:
#-- Imprimir caracter C
li a0, 'C'
li a7, PRINT_CHAR
ecall
NEXT
#--------------------
#-- Terminar
#--------------------
.data
EX: .word code_ex
.text
code_ex:
li a0, '\n'
li a7, PRINT_CHAR
ecall
#-- Terminar
li a7, EXIT
ecallMismo funcionamiento:
obijuan@JANEL:~/Develop/Learn-forth/Log/2024-10-29$ ./run.sh 05-main.s
ABC
obijuan@JANEL:~/Develop/Learn-forth/Log/2024-10-29$ Las palabras escritas en FORTH necesitan su propio intérprete. Una de las cosas que tiene que hacer este intérprete es gestionar la pila de retorno (pila R). S1 apunta a la siguiente instrucción, pero si la instrucción actual está en Forth, a su vez estará formada por otra lista de palabras, porque necesita usar S1 como puntero. Por ello hay que guardarlo en la pila
Utilizamos el registro fp para la pila R
El intérprete para instrucciones FORTH se llama DOCOL. Todas las palabras FORTH empiezan llamando a DOCOL (su interprete)
- Fichero:
06-main.s
#-- JonesForth. Fundamentos de Forth
#-- Servicios del sistema operativo del RARs
.eqv EXIT 10 #-- Terminar
.eqv PRINT_CHAR 11 #-- Imprimir un caracter
#-- Macro para ejecutar la siguiente instruccion
.macro NEXT
lw a0, 0(s1) #-- a0: Direccion del codeword de la palabra
addi s1, s1, 4 #-- s1: Apunta a la siguiente palabra
lw t0, 0(a0) #-- Leer direccion del interprete
jalr t0 #-- Ejecutar interprete
.end_macro
.data
#-- Palabra definida en FORTH
TEST: .word DOCOL # codeword
.word PA
.word PB
.word PC
.word EX
#-- Arranque del interprete
#-- Se indica la primera palabra a ejecutar
cold_start:
.word TEST #-- Caso especial: No tiene codeword
.text
#-- Inicializar la pila R
la fp, return_stack_top
#-- Arrancar el interprete
la s1, cold_start
NEXT
#-- Nunca se retorna
#---------------------------------------
#- DOCOL: Interprete de palabras Forth
#---------------------------------------
#- a0: Direccion del codeword (interprete)
.text
DOCOL:
#-- Insertar s1 en la pila R
addi fp, fp, -4
sw s1, 0(fp)
#-- a0: que apunte a la siguiente palabra
addi a0,a0, 4
#-- s1: Apuntar a la siguiente palabra
mv s1, a0
#-- Ejecutar siguiente palabra
NEXT
#-------------------------------
#-- PA: Imprimir el caracter A
#-------------------------------
.data
PA: .word code_pa #-- codeword
.text
code_pa:
li a0, 'A'
li a7, PRINT_CHAR
ecall
NEXT
#-------------------------------
#-- PB: Imprimir el caracter B
#-------------------------------
.data
PB: .word code_pb
.text
code_pb:
li a0, 'B'
li a7, PRINT_CHAR
ecall
NEXT
#-------------------------------
#-- PC: Imprimir el caracter C
#-------------------------------
.data
PC: .word code_pc
.text
code_pc:
#-- Imprimir caracter C
li a0, 'C'
li a7, PRINT_CHAR
ecall
NEXT
#--------------------
#-- Terminar
#--------------------
.data
EX: .word code_ex
.text
code_ex:
li a0, '\n'
li a7, PRINT_CHAR
ecall
#-- Terminar
li a7, EXIT
ecall
.data
#-- PILA R
.space 40
return_stack_top:
.data
#-- Pila de datos
var_S0:
El resultado es el mismo:
obijuan@JANEL:~/Develop/Learn-forth/Log/2024-10-29$ ./run.sh 06-main.s
ABC
obijuan@JANEL:~/Develop/Learn-forth/Log/2024-10-29$ Modificamos el programa para añadir el diccionario. Cada palabra tiene una cabecera formada por un enlace a la siguiente palabra la longitud del nombre + flags, el nombre ASCII y luego el codeword. En el caso de las instrucciones primitivas, el codeword apunta al código en asm, que se pone a continuación. En el caso de las palabras FORTH el codeword es DOCOL y a continuación se ponen el resto de palabras que componen la palabra
- Fichero:
07-main.s
#-- JonesForth. Fundamentos de Forth
#-- Servicios del sistema operativo del RARs
.eqv EXIT 10 #-- Terminar
.eqv PRINT_CHAR 11 #-- Imprimir un caracter
#-- Macro para ejecutar la siguiente instruccion
.macro NEXT
lw a0, 0(s1) #-- a0: Direccion del codeword de la palabra
addi s1, s1, 4 #-- s1: Apunta a la siguiente palabra
lw t0, 0(a0) #-- Leer direccion del interprete
jalr t0 #-- Ejecutar interprete
.end_macro
.text
#-- Inicializar la pila R
la fp, return_stack_top
#-- Arrancar el interprete
la s1, cold_start
NEXT
#-- Nunca se retorna
.data
#-- Arranque del interprete
#-- Se indica la primera palabra a ejecutar
cold_start:
.word TEST #-- Caso especial: No tiene codeword
#---------------------------------------------------
#-- Diccionario
#---------------------------------------------------
#-- Ejemplo de palabra primitiva
.data
name_DROP:
.word 0 #-- NULL (Puntero a siguiente palabra)
.byte 4 #-- Longitud del nombre + flags
.ascii "DROP" #-- Nombre de la palabra FORTH
.align 2
DROP:
.word code_DROP #-- Codeword
.text
code_DROP: #-- Codigo en asm
nop
nop
NEXT
#----------------------------
#-- PA. Imprimir caracter A
#----------------------------
.data
name_PA:
.word name_DROP #-- NULL (Puntero a siguiente palabra)
.byte 2 #-- Longitud del nombre + flags
.ascii "PA" #-- Nombre de la palabra FORTH
.align 2
PA:
.word code_PA #-- Codeword
.text
code_PA: #-- Codigo en asm
li a0, 'A'
li a7, PRINT_CHAR
ecall
NEXT
#----------------------------
#-- PB. Imprimir caracter B
#----------------------------
.data
name_PB:
.word name_PA #-- NULL (Puntero a siguiente palabra)
.byte 2 #-- Longitud del nombre + flags
.ascii "PB" #-- Nombre de la palabra FORTH
.align 2
PB:
.word code_PB #-- Codeword
.text
code_PB: #-- Codigo en asm
li a0, 'B'
li a7, PRINT_CHAR
ecall
NEXT
#----------------------------
#-- PC. Imprimir caracter C
#----------------------------
.data
name_PC:
.word name_PB #-- NULL (Puntero a siguiente palabra)
.byte 2 #-- Longitud del nombre + flags
.ascii "PC" #-- Nombre de la palabra FORTH
.align 2
PC:
.word code_PC #-- Codeword
.text
code_PC: #-- Codigo en asm
li a0, 'C'
li a7, PRINT_CHAR
ecall
NEXT
#-------------------------------
#-- EX. Servicio EXIT del RARs
#-------------------------------
.data
name_EX:
.word name_PC #-- NULL (Puntero a siguiente palabra)
.byte 2 #-- Longitud del nombre + flags
.ascii "EX" #-- Nombre de la palabra FORTH
.align 2
EX:
.word code_EX #-- Codeword
.text
code_EX: #-- Codigo en asm
li a0, '\n'
li a7, PRINT_CHAR
ecall
#-- Terminar
li a7, EXIT
ecall
#--------------------------------------
#-- TEST. Definida en FORTH
#-------------------------------------
.data
name_TEST:
.word name_EX
.byte 4
.ascii "TEST"
.align 2
TEST:
.word DOCOL # Codeword
.word PA
.word PB
.word PC
.word EX
#---------------------------------------
#- DOCOL: Interprete de palabras Forth
#---------------------------------------
#- a0: Direccion del codeword (interprete)
.text
DOCOL:
#-- Insertar s1 en la pila R
addi fp, fp, -4
sw s1, 0(fp)
#-- a0: que apunte a la siguiente palabra
addi a0,a0, 4
#-- s1: Apuntar a la siguiente palabra
mv s1, a0
#-- Ejecutar siguiente palabra
NEXT
#-- PILA R
.data
.space 40
return_stack_top:
#-- Pila de datos
.data
var_S0:El funcionamiento es el mismo que antes:
obijuan@JANEL:~/Develop/Learn-forth/Log/2024-10-29$ ./run.sh 07-main.s
ABC
obijuan@JANEL:~/Develop/Learn-forth/Log/2024-10-29$ Voy a escribir código rápido y sucio para organizar las ideas... quiero tenerlo todo claro, e ir pasándolo a limpio en otra página de la wiki: una especie de tutorial, que iré refinando a medida que aprenda cosas... En este pongo las cosas en bruto de momento
De momento estoy haciendo los ejemplos xx-test.s, empezando por el 01-test.s... Estoy escribiendo en los comentarios una especie de tutorial incremental. En cada nuevo programa de test se añade alguna mejora. Creo que se entiende muy muy bien. Esto lo escribiré en la wiki, pero de momento lo dejo en el código
Tras hacer todos los ejemplos de test, con sus tutoriales... tenemos en 21-test.s un MOTOR MINIMO DE FORTH
- Fichero:
21-test.s
#-- Cambio/mejora:
#-- En este ejemplo no se ha añadido nada nuevo en el interprete
#-- Lo que se hace es definir cuatro palabras que se recorren en
#-- profundidad. TEST llama a TEST2 que llama a TEST3 que a su vez
#-- llama a TEST4 y retorna. Se vuelve atrás hasta que se imprime A y B
#-- desde la primera palabra, tras ejecutar TEST
#--
#-- La cadena impresa es AB, y luego se termina OK
#--
#-- Llegados a estsa punto YA tenemos un intérprete de FORTH
#-- capaz de ejecutar palabras primitivas y palabras FORTH
#-- Aunque en realidad NO hay palabras definidas del propio FORTH
#-- todavía. Tenemos un MOTOR FORTH mínimo!!
.include "so.s"
#---------------------------------------------------
#-- Definimos las palabras de nuestro sistema
#---------------------------------------------------
.data
#-- Primera Palabra de prueba a Ejecutar
#-- Como es la primera es "especial". Su configuracion
#-- es diferente a la de las demas
#-- No tiene codeword propio
TEST:
.word TEST2
.word W1
.word W2
.word EX
TEST2:
.word DOCOL
.word TEST3
.word EXIT
TEST3:
.word DOCOL
.word TEST4
.word EXIT
TEST4:
.word DOCOL
.word EXIT
#------------------------
#-- W1: Imprimir A
#------------------------
.data
W1: .word code_W1 #-- Codeword: Direccion codigo ejecutable
#-- Implementacion de W1
.text
code_W1:
SO_PRINT_CHAR('A')
NEXT #-- Ejecutar siguiente instruccion
#-----------------------
# W2: Imprimir B
#-----------------------
.data
W2: .word code_W2 #-- Codeword
.text
code_W2:
SO_PRINT_CHAR('B')
NEXT
#-----------------------
# EX: Terminar
#-----------------------
.data
EX: .word code_EX #-- Codeword
.text
code_EX:
SO_PRINT_CHAR('\n')
SO_EXIT
#-- Es una instruccion especial
#-- Se termina, por lo que NO se llama a NEXT
#--------------------------------------------------------------------
# EXIT. Palabra que se tiene que ejecutar al final de la definicion
# de una palabra NO primitiva
#--------------------------------------------------------------------
.data
EXIT: .word code_EXIT #-- Codeword
.text
code_EXIT:
#-- Recuperar s1 de la pila
lw s1, 0(fp)
#-- Restaurar pila R
addi fp,fp,4
#-- Ejecutar siguiente instruccion!
NEXT
#-----------------------
#-- DOCOL
#-- NO es una palabra de Forth. Es directamente codigo
#-- maquina que dice como ejecutar una palabra no primitiva
#-----------------------
.text
DOCOL:
addi fp,fp,-4 #-- Generar espacio en la pila R
sw s1, 0(fp) #-- Almacenar s1 en la pila R
#-- a0 apunta a DOCOL, cuando se empieza a ejecutar
#-- Si le sumamos 4, apunta a la siguiente palabra
#-- (Una palabra que empieza por DOCOL esta formada por varias
#-- palabras. Es decir, que tras DOCOL hay una palabra seguro)
#-- Hacemos que s1 apunte a esa siguiente palabra
addi s1, a0, 4 #-- S1 apunta a la siguiente palabra tras DOCOL
#-- Ejecutar la siguiente instruccion!
NEXT
#-------------------------------------------
#-- Programa principal
#-- ARRANCA AQUI!!!!
#-------------------------------------------
.text
.global main
main:
#-- Inicializar el puntero de pila R
la fp, return_stack_top
#-- S1 apunta la primera palabra a ejecutar (W1)
la s1,TEST #-- s1 apunta a la Variable con la palabra a ejecutar
#-- s1 es el IP (Puntero de instruccion)
#-- Ejecutar primera instruccion (W1)
NEXT #-- Ejecuta la instruccion apuntada por s1
#-- y Apuntar a la siguiente
#-- Nunca llega aqui!!!!!
.data
.space 40
return_stack_top:El conocimiento que tenemos hasta ahora se resume en muy poco código. Me dan ganas de escribir una cheat-sheet de Forth...
- Definicion de palabras FORTH
.data
WORD1: .word DOCOL #-- Codeword
.word WORD2
.word WORD3
...
.word EXITDonde WORD2 y WORD3 son bien palabras primitivas o palabras FOTH. Da igual
- Definición de palabras primitivas
.data
W1: .word code_W1 #-- Codeword
.text
code_word:
#-- Instruccion asm 1
#-- Instruccion asm 2
#-- ....
NEXT- MACRO NEXT
lw a0,0(s1)
addi s1,s1,4
lw t0,0(a0)
jalr t0- DOCOL: Código en asm
.text
DOCOL: addi fp,fp,-4
sw s1,0(fp)
add s1,a0,4
NEXT- Palabra primitiva EXIT
.data
EXIT: .word code_exit
.text
code_exit:
lw s1, 0(fp)
addi fp,fp,4
NEXT- Palabra inicial
Esta palabra se define de manera distinta al resto. No tiene DOCOL ni EXIT
.data
TEST: #-- No tiene codeword!!
.word WORD1
.word WORD2
...- Arranque del intérprete
.text
#-- Configurar pila R
la fp, return_stack_top
#-- Comenzar ejecucion de palabra especial TEST
la s1, TEST
NEXT
Me apetece implementar la E/S, para que los programas empiecen a ser interactivos... Estoy implementando KEY. Para simplificar elimino el buffer de entrada y llamo directamente al servicio READ_CHAR del RARS. Dentro del simulador funciona muy bien: Cada vez que se aprieta una tecla se devuelve el control... PERO no es así cuando se ejecuta desde la consola de linux. Por cada caracter hay que darle al ENTER, lo cual es una mierda...
De momento puedo tirar con el simulador, pero voy a pensar en una solución para lo de la consola... Tal vez, antes de llamar al rars hay que configurar la consola para que devuelva el control al recibir un caracter... Existe algun comando linux para hacerlo?
He encontrado el comando, pero no funciona:
stty -icanon min 1 -echoTambién he probado con la biblioteca que hice en C hace mucho: consola_io. Configura la consola para acceso directo, pero nada. Viendo el código veo que hay metido un timeout en select... vamos que la magia no está en la configuración de stdin, sino en el select...
Por ese camino nada... sólo me queda una cosa por probar.. utilizar la llamada al sistema read del rars, que es la misma que usa el forth... a ver si saco algo en claro...
Siiiiiiiiiiiiiii!! Joder... siiiiiiiiiiiiiiiiii!!!! Con el servidio READ (63) del RARS sí que funciona. Le he puesto que la cantidad máxima de caracteres a leer sea 1, y me devuelve el control... cojonudo!! Ya puedo seguir implementando KEY!
He tenido que cerrar el terminal y abrir uno nuevo y... el ejemplo 26 se ha vuelto bloqueante!!! Así que en realidad el comando stty sí que es necesario!!!. Lo he añadido en el run.sh
Voy a empezar a ver la parte del compilador. La idea la verdad es que la explica Jones muy bien en su tutorial... Tenemos que convertir un trozo de texto como este:
: DOUBLE DUP + ; En una entrada en el diccionario como esta:
Puntero a la siguiente palabra
^
|
+--|------+---+---+---+---+---+---+---+---+------------+------------+------------+------------+
| LINK | 6 | D | O | U | B | L | E | 0 | DOCOL | DUP | + | EXIT |
+---------+---+---+---+---+---+---+---+---+------------+--|---------+------------+------------+
^ len pad codeword |
| V
LATEST Apunta a la codeword de DUP
Lo podemos representar así:
+-------------------------------+
+------->| Inst. Codigo maquina DOCOL |
| +-------------------------------+
+---------+---+---+---+---+---+---+---+---+------|-----+------------+------------+------------+
| LINK | 6 | D | O | U | B | L | E | 0 | DOCOL | DUP | + | EXIT |
+---------+---+---+---+---+---+---+---+---+------------+--|---------+------------+------------+
+----------------------+
|
V
+---------+---+---+---+---+---+------------+ ------------------------------------+
| LINK | 3 | D | U | P | 0 | Code_Dup | | Inst. codigo maquina DUP |
+---------+---+---+---+---+---+----|-------+ --^---------------------------------+
| |
+------------+
Todo está en el segmento de datos, salvo los trozos separados que contienen el código máquina, que están en el segmento de código
En el caso de compilar una palabra Forth, el único código máquina que hay es el de DOCOL
Para continuar con la implementación del compilador hay que implementar primero las variables y constantes.
Ya tengo las variables...
Toca implementar las constantes... vamos al lío!
Listo! Ya tengo todas las constantes del sistema implementadas. Faltan las llamadas al sistema, pero eso lo haré más adelante
Ya se podría implementar el compilador, pero prefiero implementar primero el intérprete, para probar directamente en la línea de comandos, y no tener que estar añadiendo casos de prueba en el propio código... así que... ¡vamos a por ello! Aquí es cuando las cosas se empiezan a poner muy interesantes
El intérprete básico ya está listo: palabra INTERPRET. Para terminar de funcionar se necesita la palabra QUIT que es la que ejecuta en un bucle infinito INTERPRET. Pero para implementar QUIT hay que implementar primero BRANCH
Listo! Ya está funcionando el primer intérprete! De momento hay pocas palabras en el diccionario por lo que se puede hacer poco
obijuan@JANEL:~/Develop/Learn-forth/Log/2024-11-02$ ./run.sh 37-test.s
1
2
HI
<3> 1 2 5
BYEEs el momento de ampliar el diccionario
La entrada no me funciona muy bien... La he implementando de una manera distinta a como estaba en el JoneForth, pero creo que ahora ya entiendo la implementación inicial... así que la voy a re-implementar!
Listo! Ahora ya funciona la entrada estándard! Los programas de prueba los puedo poner en ficheros externos. He creado test.f con pruebas iniciales
\\-- Probando DOTS
1 2 3 .S
\\-- Probando DROP
DROP DROP DROP .S
\\-- Probando SWAP
5 6 .S SWAP .S DROP DROP .S
\\-- Prueba de DUP
5 .S DUP .S DROP DROP .S
\\-- Prueba de OVER
1 2 3 .S OVER .S DROP DROP DROP DROP .S Así es como se prueba:
obijuan@JANEL:~/Develop/Learn-forth/Log/2024-11-03$ ./run.sh 39-test.s < test.f
<3> 1 2 3
<0>
<2> 5 6
<2> 6 5
<0>
<1> 5
<2> 5 5
<0>
<3> 1 2 3
<4> 1 2 3 2
<0>El programa de prueba va creciendo:
\\-- Probando DOTS
1 2 3 .S
\\-- Probando DROP
DROP DROP DROP .S
\\-- Probando SWAP
5 6 .S SWAP .S DROP DROP .S
\\-- Prueba de DUP
5 .S DUP .S DROP DROP .S
\\-- Prueba de OVER
1 2 3 .S OVER .S DROP DROP DROP DROP .S
\\-- Prueba de ROT
10 20 1 2 3 .S ROT .S DROP DROP DROP DROP DROP .S
\\-- Prueba de -ROT
10 20 1 2 3 .S -ROT .S DROP DROP DROP DROP DROP .S
\\-- Prueba de 2DROP
10 1 2 .S 2DROP .S DROP .S
\\-- Prueba de 2DUP
10 1 2 .S 2DUP .S DROP DROP DROP DROP DROP .S
\\-- Prueba de 2WAP
10 1 2 3 4 .S 2SWAP .S DROP DROP DROP DROP DROP .S
\\-- Prueba de ?DUP
10 2 .S ?DUP .S DROP DROP DROP .S \\-- Hay duplicacion
10 0 .S ?DUP .S DROP DROP .S \\-- No hay duplicacion
\\-- Prueba de 1+
5 .S 1+ .S DROP .S
\\-- Prueba de 1-
5 .S 1- .S DROP .S
\\-- Prueba de 4+
10 .S 4+ .S DROP .S
\\-- Prueba de 4-
12 .S 4- .S DROP .S
\\-- Prueba de 8+
8 .S 8+ .S DROP .S
\\-- Prueba de 8-
16 .S 8- .S DROP .S
\\-- Operacion +
1 1 .S + .S DROP .S
\\-- Operacion -
10 10 .S - .S DROP .S
\\-- Operacion *
7 3 .S * .S DROP .SY esta es su ejecución:
obijuan@JANEL:~/Develop/Learn-forth/Log/2024-11-03$ ./run.sh 40-test.s < test.f
<3> 1 2 3
<0>
<2> 5 6
<2> 6 5
<0>
<1> 5
<2> 5 5
<0>
<3> 1 2 3
<4> 1 2 3 2
<0>
<5> 10 20 1 2 3
<5> 10 20 2 3 1
<0>
<5> 10 20 1 2 3
<5> 10 20 3 1 2
<0>
<3> 10 1 2
<1> 10
<0>
<3> 10 1 2
<5> 10 1 2 1 2
<0>
<5> 10 1 2 3 4
<5> 10 3 4 1 2
<0>
<2> 10 2
<3> 10 2 2
<0>
<2> 10 0
<2> 10 0
<0>
<1> 5
<1> 6
<0>
<1> 5
<1> 4
<0>
<1> 10
<1> 14
<0>
<1> 12
<1> 8
<0>
<1> 8
<1> 16
<0>
<1> 16
<1> 8
<0>
<2> 1 1
<1> 2
<0>
<2> 10 10
<1> 0
<0>
<2> 7 3
<1> 21
<0>
Program terminated by calling exitMás palabras añadidas al diccionario, y pruebas en ficheros Forth externos
Todas las variables y constantes añadidas
El compilador ya empieza a funcionar!!!!
obijuan@JANEL:~/Develop/Learn-forth/Log/2024-11-06$ ./run.sh 48-test.s
: UNO 1 ;
: DOS UNO UNO + ;
: TRES DOS UNO + ;
: CUATRO TRES UNO + ;
CUATRO .S
<1> 4
BYE
Program terminated by calling exit
obijuan@JANEL:~/Develop/Learn-forth/Log/2024-11-06$Esto quiere decir que ya puedo empezar a probar programas en Forth!!! Todavía hay mucho que pulir y probar, pero pinta muy bien
Ya está todo el fichero jonesforth.S implementado!
obijuan@JANEL:~/Develop/Learn-forth/Log/2024-11-07$ ./run.sh 49-test.s < test-05.fs
<1> 49
<0>
<1> 268502696
<0>
<1> 4194304
<0>
<1> 128
<0>
<1> 32
<0>
<1> 31
<0>
<1> 1973
<0>
<1> 0
<0>
<1> 268502696
<0>
<1> 268502736
<0>
<1> 65
<0>
<2> 268502040 4
<0>
<1> 268502088
<0>
<1> 0
<0>
<1> 268502100
<0>
<1> 268502104
<0>
<0>
<0>
<0>
<1> 268501140
<0>
<1> 65
<0>
<1> 134068006
<0>
5<0>Así que ahora ya pasamos a FORTH!!!
El interprete en ensamblador es el 50-test.s. La ampliacion en Foth está en el fichero jonesforth.fs. Los ficheros de prueba siguen a partir de test-06.fs
Para realizar las prueba usamos este comando:
obijuan@JANEL:~/Develop/Learn-forth/Log/2024-11-07$ cat jonesforth.fs test-06.fs | ./run.sh 50-test.s
<2> 10 5
<1> 2
<0>
<2> 10 3
<1> 1
<0>
Program terminated by calling exit
obijuan@JANEL:~/Develop/Learn-forth/Log/2024-11-07$ - Ya funciona el IF-THEN / IF-ELSE-THEN
- Listo el BEGIN-UNTIL
Tengo pendiente hacer una recopilación de bibliografía interesante de Forth... de momento pongo lo que voy usando cada día.
Este documento PDF está el libro "Introducción a Forth" de F.J Gil Chica, de la Universidad de Alicante:
El texto está muy bien, y se aclaran un montón de conceptos que tenía un poco difusos. Lo interesante de estes libro es que implementa su propio Forth (en C), y luego lo extiende con el propio Forth. Esto es algo típico de los sistemas Forth: se crea un núcleo mínimo implementando a bajo nivel (típicamente en ensamblador del procesador a utilizar). Con este núcleo mínimo ya se tiene un intérprete y compilador minimos, con los que se puede extender el lenguaje para crear cosas más complejas
El Forth creado por el auto es no estándar, y lo ha llamado Lézar. El núcleo contiene lo siguiente:
- El compilador
- El intérprete
- Palabras primitivas. En total tiene definidas 66
+ - * / mod and or = < > dup
drop swap >r r> : , create
does> immediate postpone [ ]
tick execute if else then
do loop +loop begin while
repeat I break exit key char
emit @ c@ ! c! c, date time
open-file close-file parse
read-line write-line channel
restart rebuild ." allot M N
. .s depth dd dc mark backEn el fichero lezar.f tiene definida la extensión del núcleo, implementado en FORTH. Estas son las palabras que contiene, 86:
cell cells rot -rot over nip
tuck 2dup 3dup 2drop ndrop
2swap cls plas 0= ?dup 0< 0>
>= <= != 1+ 1- 2* negate abs
^ fact << >> max min /mod
variable constant +! n->dd
[char] ESC[ at-xy cr cursor^
cursor_ cursor> <cursor
cursor^^ cursor__ cursor>>
<<cursor page <# # #s #>
hold sign type .r fill erase
copy 2c@ compare accept <-X
string strlen upcase? strup
lowcase? strlow strscan used
.time .date channels fr fw
fa q+ q- q* q/ mcd reduce q.En total tenemos un vocabulario de 66 + 86 = 152 palabras. Esto es muy interesante porque se pueden tener diferentes extensiones según lo que se quiera hacer... así podríamos tener diferentes FORTHs para hacer diferentes cosas...
Voy a renombrar los nombres de los ficheros siguiendo con esta filosofía. El nuevo fichero con el nucleo mínimo lo voy a llamar kernel.s, y está implementado para el RISCV-32-bits. El fichero kernel-ext.fs es la extensión con el resto de palabras, ya definidas en Forth
Para lanzar el Forth con el kernel extendido se ejecuta este comando:
obijuan@JANEL:~/Develop/Learn-forth/Log/2024-11-08$ ./forth.sh
.S
<0>
*
Program terminated by calling exit
obijuan@JANEL:~/Develop/Learn-forth/Log/2024-11-08$Se carga el kernel extendido y se pasa a modo intérprete. Para salir hay que apretar Ctrl-D
El script redirecciona el fichero kernel-ext.fs y activa la salida estándard normal:
#!/bin/sh
#-- Ejecutar el forth como interprete, incluyendo
#-- todas sus palabras del nucleo y de la extension
cat kernel-ext.fs $1 - | ./run_rars.sh kernel.sSe le puede pasar opcionalmente como argumento el programa de test, que se cargará despues de haber cargado el kernel extendido:
obijuan@JANEL:~/Develop/Learn-forth/Log/2024-11-08$ ./forth.sh test-06.fs
<2> 10 5
<1> 2
<0>
<2> 10 3
<1> 1
<0>
<0>
<1> 73
<1> -73
<0>
<2> 1 0
<0>
:<0>
00AA<0>
A<0>
AB
<0>
B
<0>
A
<0>
B
<0>
AAAAA
<0>
AAAAAAAAAA
<0>
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
<0>En la version 50 del nucleo hay un bug, que tengo que estudiar y solucionar. La versión 50 la dejamos como esta, y pasamos a la 51 para hacer las modificaciones necesarias para solucionarlo
El bug se reproduce así:
obijuan@JANEL:~/Develop/Learn-forth/Log/2024-11-10$ ./forth.sh
PARSE ERROR
s A..Z )
'A'
THEN
+
EMIT
;
...
Aparece un Parse ERROR. Hay que descubrir de donde sale. El script forth carga el núcleo y su extensión. Así que el problema está en la extensión. Ponemos un BYE para salir, y detectar exactamente a partir de donde sale el PARSE ERROR
ok... había dejado una parte sin comentar... ya está solucionado y al ejecutar Forth se carga el kernel extendido y vuelve al intérprete:
obijuan@JANEL:~/Develop/Learn-forth/Log/2024-11-10$ ./forth.sh
1 1 + .S
2
BYE
Program terminated by calling exit
obijuan@JANEL:~/Develop/Learn-forth/Log/2024-11-10$En principio parece que funciona PERO... si leemos una variable entonces PETA:
obijuan@JANEL:~/Develop/Learn-forth/Log/2024-11-10$ ./forth.sh
STATE
.S
26850172Error in /home/obijuan/Develop/Learn-forth/Log/2024-11-10/kernel.s line 90->45: Runtime exception at 0x00400024: Load address not aligned to word boundary 0x0000000a
Simulation terminated due to errors.Parece que el error esta bien en la lectura de STATE (que es la primera variable definida), o bien en la ejecucion de .S. Lo primero que vamos a hacer es repetir el experimento pero SIN cargar la extension del kernel. Es decir, directamente en el nucleo mínimo. Allí utilizaremos el .S primitivo, implementado en ensamblador:
obijuan@JANEL:~/Develop/Learn-forth/Log/2024-11-10$ ./run_rars.sh kernel.s
STATE
.S
<1> 268501720
BYE
Program terminated by calling exit
obijuan@JANEL:~/Develop/Learn-forth/Log/2024-11-10Vemos que sí funciona. Parece que el error está en .S. Pero podría ser que la propia ejecución de STATE sea la que joda todo. Voy a renombrar .S como _.S para diferenciar al .S ejecutado en el nucleo mínimo del que se ejecuta en el nucleo externo
obijuan@JANEL:~/Develop/Learn-forth/Log/2024-11-10$ ./run_rars.sh kernel.s
STATE
_.S
<1> 268501720
BYE
Program terminated by calling exit
obijuan@JANEL:~/Develop/Learn-forth/Log/2024-11-10$ Voy a cambiar el _.S a hexadecimal, así podemos comprobar si las direcciones de las variables están alineadas, y las podemos buscar en el simulador
obijuan@JANEL:~/Develop/Learn-forth/Log/2024-11-10$ ./run_rars.sh kernel.s
STATE
_.S
<1> 0x100102d8
BYE
Program terminated by calling exit
obijuan@JANEL:~/Develop/Learn-forth/Log/2024-11-10$ Ahora probamos con el nucleo extendido:
obijuan@JANEL:~/Develop/Learn-forth/Log/2024-11-10$ ./forth.sh
STATE
_.S
<1> 0x100102d8
.S
26850172Error in /home/obijuan/Develop/Learn-forth/Log/2024-11-10/kernel.s line 90->50: Runtime exception at 0x00400024: Load address not aligned to word boundary 0x0000000a
Simulation terminated due to errors.
obijuan@JANEL:~/Develop/Learn-forth/Log/2024-11-10$ La variable STATE funciona: deja el valor en la pila, y con _.S se lee correctamente. Sin embargo con .S PETA, así que el error parece estar ahí...
La implementación de .S depende de U. Así que vamos a probar con U., que imprime un numero sin signo
obijuan@JANEL:~/Develop/Learn-forth/Log/2024-11-10$ ./forth.sh
1
U.
1
STATE
U.
268501720 Error in /home/obijuan/Develop/Learn-forth/Log/2024-11-10/kernel.s line 90->50: Runtime exception at 0x00400024: Load address not aligned to word boundary 0x0000000a
Simulation terminated due to errors.
obijuan@JANEL:~/Develop/Learn-forth/Log/2024-11-10$ Vemos que peta!!!!. Así que el error está en U. Parece que algo se imprime primero... Vamos a comprobarlo imprimiendo en hexadecimal con _.S
obijuan@JANEL:~/Develop/Learn-forth/Log/2024-11-10$ ./forth.sh
STATE
_.S
<1> 0x100102d8
U.
268501720 Error in /home/obijuan/Develop/Learn-forth/Log/2024-11-10/kernel.s line 90->50: Runtime exception at 0x00400024: Load address not aligned to word boundary 0x0000000a
Simulation terminated due to errors.
obijuan@JANEL:~/Develop/Learn-forth/Log/2024-11-10$ Efectivamente es el numero correcto en decimal:
In [1]: hex(268501720)
Out[1]: '0x100102d8'Parece que U. funciona: imprime el numero correcto PERO al terminar hay un problema... Vamos a examinar el código de U.
Antes un experimento... el error se reproduce si se introduce un numero de 9 dígitos:
obijuan@JANEL:~/Develop/Learn-forth/Log/2024-11-10$ ./forth.sh
123456789
U.
123456789 Error in /home/obijuan/Develop/Learn-forth/Log/2024-11-10/kernel.s line 90->50: Runtime exception at 0x00400024: Load address not aligned to word boundary 0x0000000a
Simulation terminated due to errors.
obijuan@JANEL:~/Develop/Learn-forth/Log/2024-11-10$ Si el numero es de 8 cifras, la cosa NO peta
obijuan@JANEL:~/Develop/Learn-forth/Log/2024-11-10$ ./forth.sh
12345678
U.
12345678
*
Program terminated by calling exit
obijuan@JANEL:~/Develop/Learn-forth/Log/2024-11-10$ En la implementación de U. se utiliza RECURSION, mediante a la llamada RECURSE
El tamaño de la pila R es de 40 bytes... como cada posicion es de 4 bytes... nos da un tamaño de 10!!! Hay que hacer una pila R MAYOR! Vamos a probar...
La he aumentado una posición más (44 bytes) para comprobar....
obijuan@JANEL:~/Develop/Learn-forth/Log/2024-11-10$ ./forth.sh
123456789
U.
123456789
BYE
Program terminated by calling exit
obijuan@JANEL:~/Develop/Learn-forth/Log/2024-11-10$
SIIIII!!!!! Ha funcionado!!!! Voy a meter un tamaño mayor.... por ejemplo de 20 posiciones: 80 bytes!
Ahora ya sí que se ejecuta el test-06.fs completo, sin dar errores:
obijuan@JANEL:~/Develop/Learn-forth/Log/2024-11-10$ ./forth.sh test-06.fs
5 10 2 3 10 1
73 )- 0 1 :00AAAAB
B
A
B
AAAAA
AAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAA
AAAAAAAAAA
B
AB
A
FF 255 2
3
5
255
4000
-543
23
12 0
4
VAAAAAAAAAAAMOS!!!!!!!
A partir de la versión 52 ya tenemos la capacidad de imprimir cadenas!
obijuan@JANEL:~/Develop/Learn-forth/Log/2024-11-10$ ./forth.sh test-06.fs
5 10 2 3 10 1
73 )- 0 1 :00AAAAB
B
A
B
AAAAA
AAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAA
AAAAAAAAAA
B
AB
A
FF 255 2
3
5
255
4000
-543
23
12 0
4
4 268505744
HOLA
HOLA2
VERSION: 52
BYE
Program terminated by calling exit
obijuan@JANEL:~/Develop/Learn-forth/Log/2024-11-10$ En la implementación de SEE (del decompilador) se produce un runtime error
obijuan@JANEL:~/Develop/Learn-forth/Log/2024-11-10$ ./forth.sh test-07.fs
------------ VERSION 61 ------------------------------
* WORDS
SEE CFA> ENDCASE ENDOF OF CASE DUMP FORGET WORDS ?IMMEDIATE ?HIDDEN ID. +TO TO VALUE VARIABLE CELLS ALLOT CONSTANT ." S" C, ALIGN ALIGNED DEPTH WITHIN ? U. . .R U.R UWIDTH .S U. HEX DECIMAL SPACES PICK TUCK NIP ( UNLESS REPEAT WHILE AGAIN UNTIL BEGIN ELSE THEN IF RECURSE [COMPILE] '.' '-' '0' 'A' '"' ')' '(' ';' ':' LITERAL NOT FALSE TRUE NEGATE SPACE CR BL '\n' MOD / BYE SYSCALL0 SYSCALL1 SYSCALL2 SYSCALL3 EXECUTE CHAR INTERPRET QUIT TELL LITSTRING 0BRANCH BRANCH ' HIDE HIDDEN IMMEDIATE ; : ] [ , CREATE >DFA >CFA FIND NUMBER WORD EMIT KEY DSP! DSP@ RSP! RSP! RSP@ R> >R F_LENMASK F_HIDDEN F_IMMED DOCOL R0 VERSION BASE S0 HERE LATEST STATE CMOVE C@C! C@ C! -! +! @ ! INVERT XOR OR AND 0>= 0<= 0> 0< 0<> 0= >= <= > < <> = /MOD * - + 8- 8+ 4- 4+ 1- 1+ ?DUP 2SWAP 2DUP 2DROP -ROT ROT OVER DUP SWAP DROP _.S LIT EXIT
* DUMP
10011714 -48 16 1 10 3 53 45 45 0 0 40 0 28 4 1 10 .....SEE..@.(...
10011724 68 4 1 10 -4 2 1 10 74 2 1 10 -18 2 1 10 h.......t.......
10011734 74 2 1 10 78 0 1 10 2 0 0 0 -38 C 1 10 t...x...........
10011744 -40 0 1 10 -50 1 1 10 -68 5 1 10 18 0 0 0 ................
10011754 -70 C 1 10 -50 0 1 10 74 2 1 10 -78 5 1 10 ........t.......
10011764 -2C -1 -1 -1 -6C 0 1 10 -5C 0 1 10 2C 9 1 10 ............,...
10011774 14 4 1 10 -80 8 1 10 -50 0 1 10 -78 13 1 10 ................
10011784 -80 8 1 10 -50 0 1 10 10 14 1 10 -68 5 1 10 ................
* CASE
Valor 0
Valor 1
Caso por defecto: 5
: TEN 10 ;
SEE TEN
Error in /home/obijuan/Develop/Learn-forth/Log/2024-11-10/kernel.s line 905: Runtime exception at 0x004006e4: address out of range 0x00000000
Simulation terminated due to errors.Hay que depurar SEE a ver qué ocurre... Tener un decompilador es muy interesante...
Para detectar el problema vamos a ir re-implementando SEE palabra por palabra, hasta que pete
De momento he encontrado un bug y ya no peta, pero no tengo claro si SEE está funcionando bien:
obijuan@JANEL:~/Develop/Learn-forth/Log/2024-11-10$ ./forth.sh test-07.fs
------------ VERSION 61 ------------------------------
* WORDS
SEE CFA> ENDCASE ENDOF OF CASE DUMP FORGET WORDS ?IMMEDIATE ?HIDDEN ID. +TO TO VALUE VARIABLE CELLS ALLOT CONSTANT ." S" C, ALIGN ALIGNED DEPTH WITHIN ? U. . .R U.R UWIDTH .S U. HEX DECIMAL SPACES PICK TUCK NIP ( UNLESS REPEAT WHILE AGAIN UNTIL BEGIN ELSE THEN IF RECURSE [COMPILE] '.' '-' '0' 'A' '"' ')' '(' ';' ':' LITERAL NOT FALSE TRUE NEGATE SPACE CR BL '\n' MOD / BYE SYSCALL0 SYSCALL1 SYSCALL2 SYSCALL3 EXECUTE CHAR INTERPRET QUIT TELL LITSTRING 0BRANCH BRANCH ' HIDE HIDDEN IMMEDIATE ; : ] [ , CREATE >DFA >CFA FIND NUMBER WORD EMIT KEY DSP! DSP@ RSP! RSP! RSP@ R> >R F_LENMASK F_HIDDEN F_IMMED DOCOL R0 VERSION BASE S0 HERE LATEST STATE CMOVE C@C! C@ C! -! +! @ ! INVERT XOR OR AND 0>= 0<= 0> 0< 0<> 0= >= <= > < <> = /MOD * - + 8- 8+ 4- 4+ 1- 1+ ?DUP 2SWAP 2DUP 2DROP -ROT ROT OVER DUP SWAP DROP _.S LIT EXIT
* DUMP
10011714 -48 16 1 10 3 53 45 45 0 0 40 0 28 4 1 10 .....SEE..@.(...
10011724 68 4 1 10 -4 2 1 10 74 2 1 10 -18 2 1 10 h.......t.......
10011734 74 2 1 10 78 0 1 10 2 0 0 0 -38 C 1 10 t...x...........
10011744 -40 0 1 10 -50 1 1 10 -68 5 1 10 18 0 0 0 ................
10011754 -70 C 1 10 -50 0 1 10 74 2 1 10 -78 5 1 10 ........t.......
10011764 -2C -1 -1 -1 -6C 0 1 10 -5C 0 1 10 2C 9 1 10 ............,...
10011774 14 4 1 10 -80 8 1 10 -50 0 1 10 -78 13 1 10 ................
10011784 -80 8 1 10 -50 0 1 10 10 14 1 10 -68 5 1 10 ................
* CASE
Valor 0
Valor 1
Caso por defecto: 5
------- Testing SEE
SEE TEN
: TEN 268501100 ;
HEX
SEE TEN
: TEN 1001006C ;SIIIII!!!!! Ya lo tengo!!!! Había muchos 8s que había que sustituir por 4s en SEE. Todavía no descarto que haya más bugs, pero de momento TEN lo decompila de puta madre:
------------ VERSION 61 ------------------------------
* WORDS
SEE CFA> ENDCASE ENDOF OF CASE DUMP FORGET WORDS ?IMMEDIATE ?HIDDEN ID. +TO TO VALUE VARIABLE CELLS ALLOT CONSTANT ." S" C, ALIGN ALIGNED DEPTH WITHIN ? U. . .R U.R UWIDTH .S U. HEX DECIMAL SPACES PICK TUCK NIP ( UNLESS REPEAT WHILE AGAIN UNTIL BEGIN ELSE THEN IF RECURSE [COMPILE] '.' '-' '0' 'A' '"' ')' '(' ';' ':' LITERAL NOT FALSE TRUE NEGATE SPACE CR BL '\n' MOD / BYE SYSCALL0 SYSCALL1 SYSCALL2 SYSCALL3 EXECUTE CHAR INTERPRET QUIT TELL LITSTRING 0BRANCH BRANCH ' HIDE HIDDEN IMMEDIATE ; : ] [ , CREATE >DFA >CFA FIND NUMBER WORD EMIT KEY DSP! DSP@ RSP! RSP! RSP@ R> >R F_LENMASK F_HIDDEN F_IMMED DOCOL R0 VERSION BASE S0 HERE LATEST STATE CMOVE C@C! C@ C! -! +! @ ! INVERT XOR OR AND 0>= 0<= 0> 0< 0<> 0= >= <= > < <> = /MOD * - + 8- 8+ 4- 4+ 1- 1+ ?DUP 2SWAP 2DUP 2DROP -ROT ROT OVER DUP SWAP DROP _.S LIT EXIT
* DUMP
10011714 -48 16 1 10 3 53 45 45 0 0 40 0 28 4 1 10 .....SEE..@.(...
10011724 68 4 1 10 -4 2 1 10 74 2 1 10 -18 2 1 10 h.......t.......
10011734 74 2 1 10 78 0 1 10 2 0 0 0 -38 C 1 10 t...x...........
10011744 -40 0 1 10 -50 1 1 10 -68 5 1 10 18 0 0 0 ................
10011754 -70 C 1 10 -50 0 1 10 74 2 1 10 -78 5 1 10 ........t.......
10011764 -2C -1 -1 -1 -6C 0 1 10 -5C 0 1 10 2C 9 1 10 ............,...
10011774 14 4 1 10 -80 8 1 10 -50 0 1 10 -78 13 1 10 ................
10011784 -80 8 1 10 -50 0 1 10 10 14 1 10 -68 5 1 10 ................
* CASE
Valor 0
Valor 1
Caso por defecto: 5
------- Testing SEE
SEE TEN
: TEN 10 ;Ya tengo una primera versión lista. No están todas las palabras del kernel extendido. Faltan las relacionadas con las llamadas al sistema, y constantes relacioandas. Tampoco he metido las relacionadas con el código ensamblador. Esto es muy interesante... y sobre todo, muy importante para entender J1a forth...
obijuan@JANEL:~/Develop/Learn-forth/Log/2024-11-10$ ./forth.sh
JONESFORTH VERSION 63
14927 CELLS REMAINING
OK
BYE
Program terminated by calling exit
obijuan@JANEL:~/Develop/Learn-forth/Log/2024-11-10$Para practivar Forth voy a hacer un desensamblador del RISCV, así como herramientas de bajo nivel para trabajar con el RISCV
Comenzamos por la instrucción addi x1, x0, 1, que es de tipo I, y cuyo código máquina es: 0x00100093. El problema es partir de ese número y obtener los diferentes campos de la instrucción. El formato de las instrucciones de tipo I es:
| imm (12-bits) | rs1 (5) | func3 | rd (5) | opcode (7) |
El tema de los ensambladores en Forth me resulta muy interesante desde que vi el del swapforth del procesador J1a... He encontrado este artículo que habla sobre el tema:
Así que he pensado en intentar hacer uno para el riscv.. Los avances los voy a ir poniendo en este log:
🚧 TODO 🚧
(DEBUG)