Compare commits

...

7 Commits

Author SHA1 Message Date
Virgil Dupras
79527976ab recipes/rc2014: stage2 WIP 2020-04-07 22:36:59 -04:00
Virgil Dupras
6652125d47 Remove link.fs
This was a dead end.
2020-04-07 21:23:42 -04:00
Virgil Dupras
ba918d0fef Add memory maps 2020-04-07 20:54:10 -04:00
Virgil Dupras
d4324292fb Merge remote-tracking branch 'origin/master' into forth 2020-04-07 19:32:30 -04:00
Virgil Dupras
e37f4c2551 bin2c: add missing stdtypes.h 2020-04-07 18:13:42 -04:00
Byron A. Grobe
217df20d77
Moved tools/bin2c to single-level with other tools. 2020-04-07 16:56:55 -05:00
Byron A. Grobe
f65c189e9b
Replace bin2c.sh with a more portable implementation.
`xxd' is not available on all systems, and on others does not support
the `-i' flag. Since bin2c.sh relied on a tool that I can't seem to find
a compatible version of, I have included a simple, portable replacement in C.

Usage remains the same:
bin2c ARRAYNAME < inputfile > outputfile.

This change is also reflected in emul/Makefile.
2020-04-05 13:04:05 -05:00
16 changed files with 271 additions and 195 deletions

View File

@ -89,6 +89,8 @@ I' -- n Copy RS second item to PS
J -- n Copy RS third item to PS
*** Memory ***
(mmap*) -- a Address of active memory mapper. 0 for none. See
"Memory maps" in notes.txt.
@ a -- n Set n to value at address a
! n a -- Store n in address a
? a -- Print value of addr a

6
emul/.gitignore vendored
View File

@ -1,14 +1,8 @@
/shell/shell
/forth/stage1
/forth/stage1dbg
/forth/stage2
/forth/stage2dbg
/forth/forth
/zasm/zasm
/zasm/avra
/runbin/runbin
/*/*-bin.h
/*/*.bin
/cfsin/zasm
/cfsin/ed
/cfsin/user.h

View File

@ -1,7 +1,7 @@
TARGETS = runbin/runbin forth/forth
BIN2C = ../tools/bin2c
# Those Forth source files are in a particular order
FORTHSRCS = core.fs cmp.fs print.fs str.fs parse.fs readln.fs fmt.fs z80a.fs \
link.fs
FORTHSRCS = core.fs cmp.fs print.fs str.fs parse.fs readln.fs fmt.fs z80a.fs
FORTHSRC_PATHS = ${FORTHSRCS:%=../forth/%} forth/run.fs
OBJS = emul.o libz80/libz80.o
SLATEST = ../tools/slatest
@ -12,6 +12,7 @@ all: $(TARGETS)
$(STRIPFC):
$(SLATEST):
$(BIN2C):
$(MAKE) -C ../tools
# z80c.bin and boot.bin are not in the prerequisites because they're bootstrap
@ -21,8 +22,8 @@ forth/forth0.bin: $(SLATEST)
$(SLATEST) $@
cat forth/emul.fs >> $@
forth/forth0-bin.h: forth/forth0.bin
./bin2c.sh KERNEL < forth/forth0.bin | tee $@ > /dev/null
forth/forth0-bin.h: forth/forth0.bin $(BIN2C)
$(BIN2C) KERNEL < forth/forth0.bin | tee $@ > /dev/null
forth/stage1: forth/stage.c $(OBJS) forth/forth0-bin.h
$(CC) forth/stage.c $(OBJS) -o $@
@ -39,8 +40,8 @@ forth/forth1.bin: forth/core.bin $(SLATEST)
cat forth/boot.bin forth/z80c.bin forth/core.bin > $@
$(SLATEST) $@
forth/forth1-bin.h: forth/forth1.bin
./bin2c.sh KERNEL < forth/forth1.bin | tee $@ > /dev/null
forth/forth1-bin.h: forth/forth1.bin $(BIN2C)
$(BIN2C) KERNEL < forth/forth1.bin | tee $@ > /dev/null
forth/stage2: forth/stage.c $(OBJS) forth/forth1-bin.h
$(CC) -DSTAGE2 forth/stage.c $(OBJS) -o $@
@ -67,3 +68,4 @@ updatebootstrap: forth/stage2
.PHONY: clean
clean:
rm -f $(TARGETS) emul.o forth/*-bin.h forth/forth?.bin
$(MAKE) -C ../tools clean

View File

@ -1,5 +0,0 @@
#!/bin/sh
echo "unsigned char $1[] = { "
xxd -i -
echo " };"

Binary file not shown.

18
forth/dict.fs Normal file
View File

@ -0,0 +1,18 @@
( Get word header length from wordref. That is, name length
+ 3. a is a wordref )
( a -- n )
: WHLEN
1 - C@ ( name len field )
0x7f AND ( remove IMMEDIATE flag )
3 + ( fixed header len )
;
( Get word addr, starting at name's address )
: '< ' DUP WHLEN - ;
( Get word's prev offset )
( a -- a )
: PREV
3 - DUP @ ( a o )
- ( a-o )
;

View File

@ -67,6 +67,23 @@
: (parse*) 0x0a _c RAM+ ;
: HERE 0x04 _c RAM+ ;
: CURRENT 0x02 _c RAM+ ;
: (mmap*) 0x51 _c RAM+ ;
( The goal here is to be as fast as possible *when there is
no mmap*, which is the most frequent situation. That is why
we don't DUP and we rather refetch. That is also why we
use direct literal instead of RAM+ or (mmap*). )
: (mmap)
[ RAMSTART 0x51 + LITN ] _c _@
IF
[ RAMSTART 0x51 + LITN ] _c _@ EXECUTE
THEN
;
: @ _c (mmap) _c _@ ;
: C@ _c (mmap) _c _C@ ;
: ! _c (mmap) _c _! ;
: C! _c (mmap) _c _C! ;
: QUIT
0 _c FLAGS _c ! _c (resRS)
@ -204,6 +221,7 @@
;
: BOOT
0 0x51 _c RAM+ _c _!
LIT< (parse) _c (find) _c DROP _c (parse*) _c !
( 60 == SYSTEM SCRATCHPAD )
_c CURRENT _c @ 0x60 _c RAM+ _c !
@ -233,7 +251,7 @@
( We cannot use LITN as IMMEDIATE because of bootstrapping
issues. Same thing for ",".
32 == NUMBER 14 == compiledWord )
[ 32 H@ ! 2 ALLOT 14 H@ ! 2 ALLOT ] _c ,
[ 32 H@ _! 2 ALLOT 14 H@ _! 2 ALLOT ] _c ,
BEGIN
_c WORD
_c (find)
@ -250,10 +268,10 @@
;
( Give ":" and ";" their real name and make them IMMEDIATE )
0x81 ' X 1 - C!
':' ' X 4 - C!
0x81 ' Y 1 - C!
';' ' Y 4 - C!
0x81 ' X 1 - _C!
':' ' X 4 - _C!
0x81 ' Y 1 - _C!
';' ' Y 4 - _C!
( Add dummy entry. we use CREATE because (entry) is, at this
point, broken. Adjust H@ durint port 2 ping. )

View File

@ -1,162 +0,0 @@
( depends: cmp, parse
Relink a dictionary by applying offsets to all word
references in words of the "compiled" type.
A typical usage of this unit would be to, right after a
bootstrap-from-icore-from-source operation, identify the
root word of the source part, probably "H@", and run
" ' thatword COMPACT ". Then, take the resulting relinked
binary, concatenate it to the boot binary, and write to
boot media.
)
( Skip atom, considering special atom types. )
( a -- a+n )
: ASKIP
DUP @ ( a n )
( ?br or br or NUMBER )
DUP <>{ 0x70 &= 0x58 |= 0x20 |= <>}
IF DROP 4 + EXIT THEN
( regular word )
0x22 = NOT IF 2 + EXIT THEN
( it's a lit, skip to null char )
( a )
1 + ( we skip by 2, but the loop below is pre-inc... )
BEGIN 1 + DUP C@ NOT UNTIL
( skip null char )
1 +
;
( Get word header length from wordref. That is, name length
+ 3. a is a wordref )
( a -- n )
: WHLEN
1 - C@ ( name len field )
0x7f AND ( remove IMMEDIATE flag )
3 + ( fixed header len )
;
( Get word addr, starting at name's address )
: '< ' DUP WHLEN - ;
( Relink atom at a, applying offset o with limit ol.
Returns a, appropriately skipped.
)
( a o ol -- a+n )
: RLATOM
ROT ( o ol a )
DUP @ ( o ol a n )
ROT ( o a n ol )
< IF ( under limit, do nothing )
SWAP DROP ( a )
ELSE
( o a )
SWAP OVER @ ( a o n )
-^ ( a n-o )
OVER ! ( a )
THEN
ASKIP
;
( Relink a word with specified offset. If it's not of the type
"compiled word", ignore. If it is, advance in word until a2
is met, and for each word that is above ol, reduce that
reference by o.
Arguments: a1: wordref a2: word end addr o: offset to apply
ol: offset limit. don't apply on refs under it.
)
( ol o a1 a2 -- )
: RLWORD
SWAP DUP @ ( ol o a2 a1 n )
( 0e == compiledWord )
0x0e = NOT IF
( unwind all args )
2DROP 2DROP
EXIT
THEN
( we have a compiled word, proceed )
( ol o a2 a1 )
2 + ( ol o a2 a1+2 )
BEGIN ( ol o a2 a1 )
2OVER ( ol o a2 a1 ol o )
SWAP ( ol o a2 a1 o ol )
RLATOM ( ol o a2 a+n )
2DUP < IF ABORT THEN ( Something is very wrong )
2DUP = ( ol o a2 a+n f )
IF
( unwind )
2DROP 2DROP
EXIT
THEN
AGAIN
;
( Get word's prev offset )
( a -- a )
: PREV
3 - DUP @ ( a o )
- ( a-o )
;
( Copy dict from target wordref, including header, up to HERE.
We're going to compact the space between that word and its
prev word. To do this, we're copying this whole memory area
in HERE and then iterate through that copied area and call
RLWORD on each word. That results in a dict that can be
concatenated to target's prev entry in a more compact way.
This copy of data doesn't allocate anything, so H@ doesn't
move. Moreover, we reserve 4 bytes at H@ to write our target
and offset because otherwise, things get too complicated
with the PSP.
This word prints the top copied address, so when comes the
time to concat boot binary with this relinked dict, you
can use H@+4 to printed addr.
)
( target -- )
: COMPACT
( First of all, let's get our offset. It's easy, it's
target's prev field, which is already an offset, minus
its name length. We expect, in COMPACT, that a target's
prev word is a "hook word", that is, an empty word. )
( H@ == target )
DUP H@ !
DUP 1 - C@ 0x7f AND ( t namelen )
SWAP 3 - @ ( namelen po )
-^ ( o )
( H@+2 == offset )
H@ 2 + ! ( )
( We have our offset, now let's copy our memory chunk )
H@ @ DUP WHLEN - ( src )
DUP H@ -^ ( src u )
DUP ROT SWAP ( u src u )
H@ 4 + ( u src u dst )
SWAP ( u src dst u )
MOVE ( u )
( Now, let's iterate that dict down )
( wr == wordref we == word end )
( To get our wr and we, we use H@ and CURRENT, which we
offset by u+4. +4 before, remember, we're using 4 bytes
as variable space. )
4 + ( u+4 )
DUP H@ + ( u we )
DUP .X LF
SWAP CURRENT @ + ( we wr )
BEGIN ( we wr )
DUP ROT ( wr wr we )
( call RLWORD. we need a sig: ol o wr we )
H@ @ ( wr wr we ol )
H@ 2 + @ ( wr wr we ol o )
2SWAP ( wr ol o wr we )
RLWORD ( wr )
( wr becomes wr's prev and we is wr-header )
DUP ( wr wr )
PREV ( oldwr newwr )
SWAP ( wr oldwr )
DUP WHLEN - ( wr we )
SWAP ( we wr )
( Are we finished? We're finished if wr-4 <= H@ )
DUP 4 - H@ <=
UNTIL
;

View File

@ -232,7 +232,7 @@ L2 FSET ( skip )
BC PUSHqq,
;CODE
CODE !
CODE _!
HL POPqq,
DE POPqq,
chkPS,
@ -241,7 +241,7 @@ CODE !
(HL) D LDrr,
;CODE
CODE @
CODE _@
HL POPqq,
chkPS,
E (HL) LDrr,
@ -250,14 +250,14 @@ CODE @
DE PUSHqq,
;CODE
CODE C!
CODE _C!
HL POPqq,
DE POPqq,
chkPS,
(HL) E LDrr,
;CODE
CODE C@
CODE _C@
HL POPqq,
chkPS,
L (HL) LDrr,

View File

@ -88,7 +88,8 @@ RAMSTART INITIAL_SP
+0e WORDBUF
+2e SYSVNXT
+4e INTJUMP
+51 RESERVED
+51 MMAPPTR
+53 RESERVED
+60 SYSTEM SCRATCHPAD
+80 RAMEND
@ -116,6 +117,9 @@ those slots...) in boot binaries are made to jump to this address. If you use
one of those slots for an interrupt, write a jump to the appropriate offset in
that RAM location.
MMAPPTR: Address behind (mmap), which is called before every !/C!/@/C@ world
to give the opportunity to change the address of the call.
SYSTEM SCRATCHPAD is reserved for temporary system storage or can be reserved
by low-level drivers. These are the current usages of this space throughout the
project:
@ -154,3 +158,29 @@ can't have comments. This leads to peculiar code in this area. If you see weird
whitespace usage, it's probably because not using those whitespace would result
in dict entry creation overwriting the code before it has the chance to be
interpreted.
*** Memory maps
We have a mechanism to map memory ranges to something else. We call this memory
maps. There is a reserved address in memory for a memory mapping routine. The
word (mmap*) returns that address. By default, it's zero which means no mapping.
Each call to @, C@, ! or C! call that word, if nonzero, before executing. This
allows you to do pretty much anything. Try to be efficient in your programming,
however, because those words are called *very* often.
Here's a toy example of memory map usage:
> 8 0x8000 DUMP
:00 0000 0000 0000 0000 ........
> : foo DUP 0x8000 = IF 2 + THEN ;
> ' foo (mmap*) !
> 8 0x8000 DUMP
:00 0000 0000 0000 0000 ........
> 0x1234 0x8000 !
> 8 0x8000 DUMP
:00 3412 3412 0000 0000 4.4.....
> 0 (mmap*) !
> 8 0x8000 DUMP
:00 0000 3412 0000 0000 ..4.....
>

View File

@ -6,6 +6,7 @@ STAGE2 = $(EDIR)/stage2
EMUL = $(BASEDIR)/emul/hw/rc2014/classic
PATHS = pre.fs \
$(FDIR)/core.fs \
$(FDIR)/cmp.fs \
$(FDIR)/str.fs \
$(FDIR)/parse.fs \
$(BASEDIR)/drv/acia.fs \

View File

@ -108,11 +108,135 @@ Once bootstrapping is done you should see the Collapse OS prompt. That's a full
Forth interpreter. You can have fun right now.
However, that long boot time is kinda annoying. Moreover, that bootstrap code
being in source form takes precious space from our 8K ROM. We already have our
compiled dictionary in memory. All we need to have a instant-booting Forth is
to combine our stage1 with our compiled dict in memory, after some relinking.
being in source form takes precious space from our 8K ROM. That brings us to
building stage 2.
TODO: write this, do this.
### Building stage 2
You're about to learn a lot about this platform and its self-bootstrapping
nature, but its a bumpy ride. Grab something. Why not a beer?
Our stage 1 prompt is the result of Forth's inner core interpreting the source
code of the Full Forth, which was appended to the binary inner core in ROM.
This results in a compiled dictionary, in RAM, at address 0x8000+system RAM.
Unfortunately, this compiled dictionary isn't usable as-is. Offsets compiled in
there are compiled based on a 0x8000-or-so base offset. What we need is a
0xa00-or-so base offset, that is, something suitable to be appended to the boot
binary, in ROM, in binary form.
We can't simply adjust offsets. For complicated reasons, that can't be reliably
done. We have to re-interpret that same source code, but from a ROM offset. But
how are we going to do that? After all, ROM is called ROM for a reason.
Memory maps.
What we're going to do is to set up a memory map targeting our ROM and point it
to our RAM. Then we can recompile the source as if we were in ROM, right after
our boot binary. Forth won't ever notice it's actually in RAM.
Alright, let's do this. First, let's have a look around. Where is the end of
our boot binary? To know, find the word ";", which is the last word of icore:
> ' ; .X
097d>
> 64 0x0970 DUMP
:70 0035 0958 00da ff43 .5.X...C
:78 003b 3500 810e 0020 .;5....
:80 0043 0093 07f4 03ef .C......
:88 0143 005f 0f00 0131 .C._...1
:90 3132 2052 414d 2b20 12 RAM+
:98 4845 5245 2021 0a20 HERE !.
:a0 3a20 4840 2048 4552 : H@ HER
:a8 4520 4020 3b0a 203a E @ ;. :
See that `_` at 0x98b? That's the name of our hook word. 4 bytes later is its
wordref. That's the end of our boot binary. 0x98f, that's an address to write
down.
Right after that is our appended source code. The first part is `pre.fs` and
can be ignored. What we want starts at the definition of the `H@` word, which
is at 0x9a0. Another address to write down.
So our memory map will target 0x98f. Where will we place it? It doesn't matter
much, we have plenty of RAM. Where's `HERE`?
> H@ .X
8c3f>
Alright, let's go wide and use 0xa000 as our map destination. But before we do,
let's copy the content of our ROM into RAM because there's our source code
there and if we don't copy it before setting up the memory map, we'll shadow it.
Let's be lazy and don't even check where the source stop. Let's assume it stops
at 0x1fff, the end of the ROM.
> 0x98f 0xa000 0x2000 0x98f - MOVE
> 64 0xa000 DUMP
:00 3131 3220 5241 4d2b 112 RAM+
:08 2048 4552 4520 210a HERE !.
:10 203a 2048 4020 4845 : H@ HE
:18 5245 2040 203b 0a20 RE @ ;.
:20 3a20 2d5e 2053 5741 : -^ SWA
:28 5020 2d20 3b0a 203a P - ;. :
:30 205b 2049 4e54 4552 [ INTER
:38 5052 4554 2031 2046 PRET 1 F
Looks fine. Now, let's create a memory map. A memory map word is rather simple.
It is called before each `@/C@/!/C!` operation and is given the opportunity to
tweak the address on PSP's TOS. Let's go with our map:
> : MMAP
DUP 0x98f < IF EXIT THEN
DUP 0x1fff > IF EXIT THEN
[ 0xa000 0x98f - LITN ] +
;
> 0x98e MMAP .X
098e> 0x98f MMAP .X
a000> 0xabc MMAP .X
a12b> 0x1fff MMAP .X
b66e> 0x2000 MMAP .X
2000>
This looks good. Let's apply it for real:
> ' MMAP (mmap*) !
> 64 0x980 DUMP
:80 0043 0093 07f4 03ef .C......
:88 0143 005f 0f00 0131 .C._...1
:90 3132 2052 414d 2b20 12 RAM+
:98 4845 5245 2021 0a20 HERE !.
:a0 3a20 4840 2048 4552 : H@ HER
:a8 4520 4020 3b0a 203a E @ ;. :
:b0 202d 5e20 5357 4150 -^ SWAP
:b8 202d 203b 0a20 3a20 - ;. :
But how do we know that it really works? Because we can write in ROM!
> 'X' 0x98f !
> 64 0x980 DUMP
:80 0043 0093 07f4 03ef .C......
:88 0143 005f 0f00 0131 .C._...X
:90 0032 2052 414d 2b20 .2 RAM+
:98 4845 5245 2021 0a20 HERE !.
:a0 3a20 4840 2048 4552 : H@ HER
:a8 4520 4020 3b0a 203a E @ ;. :
:b0 202d 5e20 5357 4150 -^ SWAP
:b8 202d 203b 0a20 3a20 - ;. :
> 64 0xa000 DUMP
:00 5800 3220 5241 4d2b X.2 RAM+
:08 2048 4552 4520 210a HERE !.
:10 203a 2048 4020 4845 : H@ HE
:18 5245 2040 203b 0a20 RE @ ;.
:20 3a20 2d5e 2053 5741 : -^ SWA
:28 5020 2d20 3b0a 203a P - ;. :
:30 205b 2049 4e54 4552 [ INTER
:38 5052 4554 2031 2046 PRET 1 F
TODO: continue
[rc2014]: https://rc2014.co.uk
[romwrite]: https://github.com/hsoft/romwrite

View File

@ -1 +1 @@
96 RAM+ HERE !
112 RAM+ HERE !

1
tools/.gitignore vendored
View File

@ -6,3 +6,4 @@
/pingpong
/slatest
/stripfc
/bin2c

View File

@ -6,8 +6,10 @@ TTYSAFE_TGT = ttysafe
PINGPONG_TGT = pingpong
SLATEST_TGT = slatest
STRIPFC_TGT = stripfc
BIN2C_TGT = bin2c
TARGETS = $(MEMDUMP_TGT) $(BLKDUMP_TGT) $(UPLOAD_TGT) $(FONTCOMPILE_TGT) \
$(TTYSAFE_TGT) $(PINGPONG_TGT) $(SLATEST_TGT) $(STRIPFC_TGT)
$(TTYSAFE_TGT) $(PINGPONG_TGT) $(SLATEST_TGT) $(STRIPFC_TGT) \
$(BIN2C_TGT)
OBJS = common.o
all: $(TARGETS)
@ -24,6 +26,7 @@ $(TTYSAFE_TGT): $(TTYSAFE_TGT).c
$(PINGPONG_TGT): $(PINGPONG_TGT).c
$(SLATEST_TGT): $(SLATEST_TGT).c
$(STRIPFC_TGT): $(STRIPFC_TGT).c
$(BIN2C_TGT): $(BIN2C_TGT).c
$(TARGETS): $(OBJS)
$(CC) $(CFLAGS) $@.c $(OBJS) -o $@

50
tools/bin2c.c Normal file
View File

@ -0,0 +1,50 @@
/*
* Copyright (c) 2020 Byron Grobe
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#define BUFSZ 32
static const char intro[] = "static const unsigned char %s[] = {\n ";
int main(int argc, char **argv) {
int n;
int col = 0;
uint8_t buf[BUFSZ];
if (argc < 2) {
fprintf(stderr, "Specify a name for the data structure...\n");
return 1;
}
printf(intro, argv[1]);
while(!feof(stdin)) {
n = fread(buf, 1, BUFSZ, stdin);
for(int i = 0; i < n; ++i) {
if (col+4 >= 76) {
printf("\n ");
col = 0;
}
printf("0x%.2x, ", buf[i]);
col += 6;
}
}
printf("};\n");
}