diff --git a/recipes/rc2014/README.md b/recipes/rc2014/README.md index eccb3a3..5cf4c85 100644 --- a/recipes/rc2014/README.md +++ b/recipes/rc2014/README.md @@ -67,7 +67,9 @@ stage on the RC2014 itself! To build your stage 1, run `make` in this folder, this will yield `os.bin`. This will contain that tiny core and, appended to it, the Forth source code it needs to run to bootstrap itself. When it's finished bootstrapping, you will -get a prompt to a full Forth interpreter. +get a prompt to an almost-full Forth interpreter (there's not enough space in +8K to fit both link.fs and readln.fs, so we ditch readln. Our prompt is raw. No +backspace no buffer. Hardcore mode.) ### Emulate @@ -125,157 +127,92 @@ 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 - -We're now ready for a re-bootstrap. Here's what we're gonna do: - -1. Bring `CURRENT` and `HERE` back to `0x98f`. -2. Set `CINPTR` to `icore`'s `(c<)`. - -`(c<)` word is the main input of the interpreter. Right now, your `(c<)` comes -from the `readln` unit, which makes the main `INTERPRET` loop wait for your -keystrokes before interpreting your words. - -But this can be changed. At the moment where we change `CINPTR`, the interpret -loop will start reading from it, so we'll lose control. That is why we must -prepare things carefully before that. We'll re-gain control at the end of the -bootstrap source, in `run.fs`, where `(c<)` is set to `readln`'s `(c<)` - -`(c<)` word is the main input of the interpreter. Right now, your `(c<)` comes -from the `readln` unit, which makes the main `INTERPRET` loop wait for your -keystrokes before interpreting your words. - -But this can be changed. At the moment where we change `CINPTR`, the interpret -loop will start reading from it, so we'll lose control. That is why we must -prepare things carefully before that. We'll re-gain control at the end of the -bootstrap source, in `run.fs`, where `(c<)` is set to `readln`'s `(c<)`. - -At this moment, `icore`'s `(c<)` is shadowed by `readln`, but at the moment -`CURRENT` changes, it will be accessible again. However, this all has to change -in one shot, so we need to prepare a compiled word for it if we don't want to -lose access to our interpret loop in the middle of our operation. - - > : KAWABUNGA! - ( 60 == (c<) pointer ) - 0x9a0 0x60 RAM+ ! - 0x98f CURRENT ! - 0x98f HERE ! - ( 0c == CINPTR ) - (find) (c<) DROP 0x0c RAM+ ! - ; - -Ready? Set? KAWABUNGA! - -TODO: make this work... +Fortunately, inside the compiled source is the contents of link.fs which will +allow us to relink our compiled dictionary so that in can be relocated in ROM, +next to our boot binary. I won't go into relinking details. Look at the source. +For now, let's just use it: + + RLCORE + +That command will take the dict from `' H@` up to `CURRENT`, copy it in free +memory and then relocate it. It will print 3 addresses during its processing. + +The first address is the top copied address. The process didn't touch memory +above this point. The second address is the wordref of the last copied entry. +The 3rd is the bottom address of the copied dict. When that last address is +printed, the processing is over (because we don't have a `>` prompt, we don't +have any other indicator that the process is over). + +### Assembling the stage 2 binary + +At that point, we have a fully relocated binary in memory. Depending on our +situations, the next steps differ. + +* If we're on a RC2014 that has writing capabilities to permanent storage, + we'll want to assemble that binary directly on the RC2014 and write it to + permanent storage. +* If we're on a RC2014 that doesn't have those capabilities, we'll want to dump + memory on our modern environment using `/tools/memdump` and then assemble that + binary there. +* If we're in the emulator, we'll want to dump our memory using `CTRL+E` and + then assemble our stage 2 binary from that dump. + +In these instructions, we assume an emulated environment. I'll use actual +offsets of an actual assembling session, but these of course are only examples. +It is very likely that these will not be the same offsets for you. + +So you've pressed `CTRL+E` and you have a `memdump` file. Open it with a hex +editor (I like `hexedit`) to have a look around and to decide what we'll extract +from that memdump. `RLCORE` already gave you important offsets (in my case, +`9a3c`, `99f6` and `8d60`), but although the beginning of will always be the +same (`8d60`), the end offset depends on the situation. + +If you look at data between `99f6` and `9a3c`, you'll see that this data is not +100% dictionary entry material. Some of it is buffer data allocated at +initialization. To locate the end of a word, look for `0043`, the address for +`EXIT`. In my case, it's at `9a1a` and it's the end of the `INIT` word. + +Moreover, the `INIT` routine that is in there is not quite what we want, +because it doesn't contain the `HERE` adjustment that we find in `pre.fs`. +We'll want to exclude it from our binary, so let's go a bit further, at `99cf`, +ending at `99de`. + +So, the end of our compiled dict is actually `99de`. Alright, let's extract it: + + dd if=memdump bs=1 skip=36192 count=3198 > dict.bin + +`36192` is `8d60` and `3198` is `99de-8d60`. This needs to be prepended by the +boot binary. But that one, we already have. It's `z80c.bin` + + cat z80c.bin dict.bin > stage2.bin + +Is it ready to run yet? no. There are 3 adjustments we need to manually make +using our hex editor. + +1. We need to link `H@` to the hook word of the boot binary. In my case, it's + a matter of writing `02` at `08ec` and `00` at `08ed`, `H@`'s prev field. +2. We need to end our binary with a hook word. It can have a zero-length name + and the prev field needs to properly point to the previous wordref. In my + case, that was `RLCORE` at offset `1559` for a `stage2.bin` size of `1568`, + which means that I appended `0F 00 00` at the end of the file. +3. Finally, we need to adjust `LATEST` which is at offset `08`. This needs to + point to the last wordref of the file, which is equal to the length of + `stage2.bin` because we've just added a hook word. This means that we write + `6B` at offset `08` and `15` at offset `09`. + +Now are we ready yet? ALMOST! There's one last thing we need to do: add runtime +source. In our case, because we have a compiled dict, the only source we need +to include is `pre.fs` and `run.fs`: + + cat stage2.bin pre.fs run.fs > stage2r.bin + +That's it! our binary is ready to run! + + ../../emul/hw/rc2014/classic stage2r.bin + +And there you have it, a stage2 binary that you've assembled yourself. Now, +here's for your homework: use the same technique to add the contents of +`readln.fs` to stage2 so that you have a full-featured interpreter. [rc2014]: https://rc2014.co.uk [romwrite]: https://github.com/hsoft/romwrite