rc2014: adapt recipe to single stage xcomp

It's now much easier...
This commit is contained in:
Virgil Dupras 2020-05-14 11:32:51 -04:00
parent b0258f5bba
commit 0703da928e
4 changed files with 22 additions and 196 deletions

View File

@ -1,4 +1,4 @@
TARGET = stage1.bin
TARGET = os.bin
BASEDIR = ../..
FDIR = $(BASEDIR)/forth
EDIR = $(BASEDIR)/emul

View File

@ -40,7 +40,7 @@ device I use in this recipe.
### Gathering parts
* A "classic" RC2014 with Serial I/O
* [Forth's stage 2 binary][stage2]
* [Forth's stage binary][stage]
* [romwrite][romwrite] and its specified dependencies
* [GNU screen][screen]
* A FTDI-to-TTL cable to connect to the Serial I/O module
@ -50,24 +50,10 @@ device I use in this recipe.
Modules used in this build are configured through the `conf.fs` file in this
folder. There isn't much to configure, but it's there.
### Build stage 1
### Build the binary
Self-bootstrapping is in Forth's DNA, which is really nice, but it makes
cross-compiling a bit tricky. It's usually much easier to bootstrap a Forth
from itself than trying to compile it from a foreign host.
This makes us adopt a 2 stages strategy. A tiny core is built from a foreign
host, and then we run that tiny core on the target machine and let it bootstrap
itself, then write our full interpreter binary.
We could have this recipe automate that 2 stage build process all automatically,
but that would rob you of all your fun, right? Instead, we'll run that 2nd
stage on the RC2014 itself!
To build your stage 1, run `make` in this folder, this will yield `stage1.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.
Building the binary is as simple as running `make`. This will yield `os.bin`
which can then be written to EEPROM.
### Emulate
@ -100,131 +86,9 @@ identify the tty bound to it (in my case, `/dev/ttyUSB0`). Then:
screen /dev/ttyUSB0 115200
Press the reset button on the RC2014 to have Forth begin its bootstrap process.
Note that it has to build more than half of itself from source. It takes about
30 seconds to complete.
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. That brings us to
building stage 2.
### 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.
Wouldn't it be great if we could save that compiled binary in ROM and save the
system the trouble of recompiling itself on boot?
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.
Fortunately, inside the compiled source is the contents of the Linker (B120)
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 `0042`, 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. We already have `stage1.bin`, but this binary contains bootstrap
source code we don't need any more. To strip it, we'll need to `dd` it out to
`LATEST`, in my case `098b`:
dd if=stage1.bin bs=1 count=2443 > s1pre.bin
Now we can combine our binaries:
cat s1pre.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 initialization code. We've stripped it from our stage1 earlier,
we need to re-add it.
Look at `xcomp.fs`. You see that `," bla bla bla"` line? That's initialization
code. Copy it to a file like `run.fs` (without the `,"`) and build your final
binary:
cat stage2.bin 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.
Press the reset button on the RC2014 and the "ok" prompt should appear.
[rc2014]: https://rc2014.co.uk
[romwrite]: https://github.com/hsoft/romwrite
[stage2]: ../../emul
[stage]: ../../emul
[screen]: https://www.gnu.org/software/screen/

View File

@ -8,7 +8,6 @@ itself.
## Gathering parts
* A RC2014 Classic
* `stage2.bin` from the base recipe
* An extra AT28C64B
* 1x 40106 inverter gates
* Proto board, RC2014 header pins, wires, IC sockets, etc.
@ -33,46 +32,21 @@ in write protection mode, but I preferred building my own module.
I don't think you need a schematic. It's really simple.
### Assembling stage 3
### Building the binary
Stage 2 gives you a full interpreter, but it's missing the "Addressed devices"
module and the AT28 driver. We'll need to assemble a stage 3.
You build the binary by modifying the base recipe's `xcomp` unit. This binary
is missing 2 things: Addressed devices and the AT28 Driver.
When you'll have a system with function disk block system, you'll be able to
directly `LOAD` them, but for this recipe, we can't assume you have, so what
you'll have to do is to manually paste the code from the appropriate blocks.
Addressed devices are at B140. To know what you have to paste, open the loader
block (B142) and see what blocks it loads. For each of the blocks, copy/paste
the code in your interpreter.
Addressed devices are at B140. If you read that block, you'll see that it tells
you to load block 142. Open the `xcomp` unit and locate the ACIA driver loading
line. Insert your new load line after that one.
Do the same thing with the AT28 driver (B590)
If you're doing the real thing and not using the emulator, pasting so much code
at once might freeze up the RC2014, so it is recommended that you use
`/tools/exec` that let the other side enough time to breathe.
You also have to modify the initialization sequence at the end of the `xcomp`
unit to include `ADEV$`.
After your pasting, you'll have a compiled dict of that code in memory. You'll
need to relocate it in the same way you did for stage 2, but instead of using
`RLCORE`, which is a convenience word hardcoded for stage 1, we'll parametrize
`RLDICT`, the word doing the real work.
`RLDICT` takes 2 arguments, `target` and `offset`. `target` is the first word
of your relocated dict. In our case, it's going to be `' ADEVMEM+`. `offset` is
the offset we'll apply to every eligible word references in our dict. In our
case, that offset is the offset of the *beginning* of the `ADEVMEM+` entry (that
is, `' ADEVMEM+ WORD(` minus the offset of the last word (which should be a hook
word) in the ROM binary.
That offset can be conveniently fetched from code because it is the value of
the `LATEST` constant in stable ABI, which is at offset `0x08`. Therefore, our
offset value is:
' ADEVMEM+ WORD( 0x08 @ -
You can now run `RLDICT` and proceed with concatenation (and manual adjustments
of course) as you did with stage 2. Don't forget to adjust `run.fs` so that it
runs `ADEV$`.
Build again, write `os.com` to EEPROM.
## Writing contents to the AT28

View File

@ -70,35 +70,23 @@ instead.
## Building your binary
Your Collapse OS binary needs the SDC drivers which need to be inserted during
Cross Compilation, which needs you need to recompile it from stage 1. First,
look at B600. You'll see that it indicates a block range for the driver. That
needs to be loaded.
Open xcomp.fs from base recipe and locate acia loading. You'll insert a line
right after that that will look like:
The binary built in the base recipe doesn't have SDC drivers. Using the same
instructions as in the `eeprom` recipe, you'll need to insert those drivers.
The SDC driver is at B600. It gives you a load range. This means that what
you need to insert in `xcomp` will look like:
602 616 LOADR ( sdc )
Normally, that's all you need to do. However, you have a little problem: You're
busting the 8K ROM limit. But it's ok, you can remove the linker's XPACKing
line: because you'll have access to the blkfs from SD card, you can load it
from there!
Removing the linker from XPACKing will free enough space for your binary to fit
in 8K. You also have to add `BLK$` to initialization routine.
You also need to add `BLK$` to the init sequence.
Build it and write it to EEPROM.
If you want, once you're all set with the SD card, you can relink core words
like you did in the base recipe for optimal resource usage.
## Testing in the emulator
The RC2014 emulator includes SDC emulation. You can attach a SD card image to
it by invoking it with a second argument:
../../../emul/hw/rc2014/classic stage3.bin ../../../emul/blkfs
../../../emul/hw/rc2014/classic os.bin ../../../emul/blkfs
You will then run with a SD card having the contents from `/blk`.