1
0
mirror of https://github.com/hsoft/collapseos.git synced 2024-11-02 10:20:55 +11:00

Compare commits

..

5 Commits

Author SHA1 Message Date
Virgil Dupras
d4cdb659b4 tools: add blkpack 2020-04-13 22:05:03 -04:00
Virgil Dupras
7292d486dc z80a: add stuctured flow words
Allow us to rely a little less on labels. We now only need 4.
2020-04-13 20:34:53 -04:00
Virgil Dupras
7d184b9e70 Use SUBHLss, macro more 2020-04-13 19:31:23 -04:00
Virgil Dupras
d919a10265 Remove runbin
It's not used
2020-04-13 19:25:28 -04:00
Virgil Dupras
b536d3bfd6 rc2014: complete the EEPROM recipe 2020-04-13 14:41:02 -04:00
17 changed files with 217 additions and 186 deletions

View File

@ -41,11 +41,12 @@ path to giving Collapse OS a try.
from this folder.
* `recipes`: collection of recipes that assemble parts together on a specific
machine.
* `blk`: Collapse OS filesystem's content. See `000` for intro.
* `doc`: User guide for when you've successfully installed Collapse OS.
* `tools`: Tools for working with Collapse OS from "modern" environments. For
example, tools for facilitating data upload to a Collapse OS machine
through a serial port.
* `emul`: Emulated applications, such as zasm and the shell.
* `emul`: Emulated applications.
* `tests`: Automated test suite for the whole project.
## Status

15
blk/000 Normal file
View File

@ -0,0 +1,15 @@
Collapse OS file system
This is a Forth-style filesystems which is very simple. It is a
list of 1024 bytes block, organised in 16 lines of 64 columns
each. You refer to blocks by numbers. You show them with LIST.
You interpret them with LOAD.
Conventions: When you see "(cont.)" at the bottom right of a
block, it means that the next block continues the same kind of
contents. This of course only work for informational text.
Block numbers are abbreviated with prefix "B". "BX" means
"block X".
The master index of this filesystem is at B1.

2
blk/001 Normal file
View File

@ -0,0 +1,2 @@
MASTER INDEX

View File

@ -2,12 +2,12 @@
operation while doing the right thing. Checks data integrity
and ABORT on mismatch.
)
( a n -- )
( n a -- )
: AT28!
2DUP C! SWAP
( as long as writing operation is running, IO/6 will toggle at each
read attempt. We know that write is finished when we read the same
value twice. )
2DUP C!
( as long as writing operation is running, IO/6 will toggle at each
read attempt. We know that write is finished when we read the same
value twice. )
BEGIN ( n1 a )
DUP C@ ( n1 a n2 )
OVER C@ ( n1 a n2 n3 )

2
emul/.gitignore vendored
View File

@ -1,8 +1,6 @@
/forth/stage1
/forth/stage1dbg
/forth/stage2
/forth/stage2dbg
/forth/forth
/runbin/runbin
/*/*-bin.h
/*/*.bin

View File

@ -1,4 +1,4 @@
TARGETS = runbin/runbin forth/forth
TARGETS = forth/forth
BIN2C = ../tools/bin2c
# Those Forth source files are in a particular order
BOOTSRCS = ./forth/conf.fs \
@ -58,9 +58,6 @@ forth/stage2: forth/stage.c $(OBJS) forth/forth1-bin.h
forth/forth: forth/forth.c $(OBJS) forth/forth1-bin.h
$(CC) forth/forth.c $(OBJS) -o $@
runbin/runbin: runbin/runbin.c $(OBJS)
$(CC) runbin/runbin.c $(OBJS) -o $@
libz80/libz80.o: libz80/z80.c
$(MAKE) -C libz80/codegen opcodes
$(CC) -Wall -ansi -g -c -o libz80/libz80.o libz80/z80.c

View File

@ -49,14 +49,6 @@ the power of a full Forth intepreter, including an assembler, to assemble
Normally, running this step should yield the exact same `boot.bin` and
`z80c.bin` as before, unless of course you've changed the source.
## runbin
This is a very simple tool that reads binary z80 code from stdin, loads it in
memory starting at address 0 and then run the code until it halts. The exit
code of the program is the value of `A` when the program halts.
This is used for unit tests.
## Problems?
If the libz80-wrapped zasm executable works badly (hangs, spew garbage, etc.),

View File

@ -1,33 +0,0 @@
#include <stdint.h>
#include <stdio.h>
#include "../emul.h"
/* runbin loads binary from stdin directly in memory address 0 then runs it
* until it halts. The return code is the value of the register A at halt time.
*/
static void iowr_stderr(uint8_t val)
{
fputc(val, stderr);
}
int main()
{
Machine *m = emul_init();
m->iowr[0] = iowr_stderr;
// read stdin in mem
int i = 0;
int c = getchar();
while (c != EOF) {
m->mem[i] = c & 0xff;
i++;
c = getchar();
}
if (!i) {
fprintf(stderr, "No input, aborting\n");
return 1;
}
emul_loop();
return m->cpu.R1.br.A;
}

View File

@ -159,7 +159,7 @@ PC ORG @ 4 + ! ( find )
adjust. Because the compare loop pre-decrements, instead
of DECing HL twice, we DEC it once. )
HL DECss,
L3 BSET ( inner )
BEGIN, ( inner )
( DE is a wordref, first step, do our len correspond? )
HL PUSHqq, ( --> lvl 1 )
DE PUSHqq, ( --> lvl 2 )
@ -172,15 +172,15 @@ L3 BSET ( inner )
DE DECss, ( Skip prev field. One less because we )
DE DECss, ( pre-decrement )
B C LDrr, ( loop C times )
L5 BSET ( loop )
BEGIN, ( loop )
( pre-decrement for easier Z matching )
DE DECss,
HL DECss,
LDA(DE),
(HL) CPr,
JRNZ, L6 FWR ( loopend )
DJNZ, L5 BWR ( loop )
L4 FSET L6 FSET ( loopend )
JRNZ, L3 FWR ( loopend )
DJNZ, AGAIN, ( loop )
L4 FSET L3 FSET ( loopend )
( At this point, Z is set if we have a match. In all cases,
we want to pop HL and DE )
DE POPqq, ( <-- lvl 2 )
@ -201,14 +201,14 @@ L4 FSET L6 FSET ( loopend )
( HL is prev field's addr. Is offset zero? )
A D LDrr,
E ORr,
JRZ, L6 FWR ( noprev )
( get absolute addr from offset )
( carry cleared from "or e" )
DE SBCHLss,
EXDEHL, ( result in DE )
L6 FSET ( noprev )
IFZ, ( noprev )
( get absolute addr from offset )
( carry cleared from "or e" )
DE SBCHLss,
EXDEHL, ( result in DE )
THEN, ( noprev )
HL POPqq, ( <-- lvl 1 )
JRNZ, L3 BWR ( inner, try to match again )
JRNZ, AGAIN, ( inner, try to match again )
( Z set? end of dict, unset Z )
L1 FSET ( fail )
A XORr,
@ -260,8 +260,7 @@ PC ORG @ 0x1e + ! ( chkPS )
HL DECss,
HL DECss,
HL DECss,
A ORr, ( clear carry )
SP SBCHLss,
SP SUBHLss,
HL POPqq,
CNC RETcc, ( INITIAL_SP >= SP? good )
JR, L1 BWR ( abortUnderflow )
@ -269,8 +268,7 @@ PC ORG @ 0x1e + ! ( chkPS )
L3 BSET ( chkRS )
IX PUSHqq, HL POPqq,
DE RS_ADDR LDddnn,
A ORr, ( clear carry )
DE SBCHLss,
DE SUBHLss,
CNC RETcc, ( IX >= RS_ADDR? good )
JR, L1 BWR ( abortUnderflow )

View File

@ -16,18 +16,16 @@
To avoid using dict memory in compilation targets, we
pre-declare label variables here, which means we have a
limited number of it. For now, 6 ought to be enough. )
limited number of it. For now, 4 ought to be enough. )
: L1 2 Z80AMEM+ ;
: L2 4 Z80AMEM+ ;
: L3 6 Z80AMEM+ ;
: L4 8 Z80AMEM+ ;
: L5 10 Z80AMEM+ ;
: L6 12 Z80AMEM+ ;
: Z80A$
( 59 == z80a's memory )
H@ 0x59 RAM+ !
14 ALLOT
10 ALLOT
;
( Splits word into msb/lsb, lsb being on TOS )
@ -43,42 +41,6 @@
: A, C, ;
: A,, SPLITB A, A, ;
( There are 2 label types: backward and forward. For each
type, there are two actions: set and write. Setting a label
is declaring where it is. It has to be performed at the
label's destination. Writing a label is writing its offset
difference to the binary result. It has to be done right
after a relative jump operation. Yes, labels are only for
relative jumps.
For backward labels, set happens before write. For forward
labels, write happen before set. The write operation writes
a dummy placeholder, and then the set operation writes the
offset at that placeholder's address.
Variable actions are expected to be called with labels in
front of them. Example, "L2 FSET"
About that "1 -": z80 relative jumps record "e-2", that is,
the offset that *counts the 2 bytes of the jump itself*.
Because we set the label *after* the jump OP1 itself, that's
1 byte that is taken care of. We still need to adjust by
another byte before writing the offset.
)
: BSET PC SWAP ! ;
: BWR @ PC - 1 - A, ;
( same as BSET, but we need to write a placeholder )
: FWR BSET 0 A, ;
: FSET
@ DUP PC ( l l pc )
-^ 1 - ( l off )
( warning: l is a PC offset, not a mem addr! )
SWAP ORG @ + ( off addr )
C!
;
( "r" register constants )
7 CONSTANT A
0 CONSTANT B
@ -375,3 +337,52 @@
( Routines )
( 29 == chkPS )
: chkPS, 29 CALLnn, ;
( Flow
There are 2 label types: backward and forward. For each
type, there are two actions: set and write. Setting a label
is declaring where it is. It has to be performed at the
label's destination. Writing a label is writing its offset
difference to the binary result. It has to be done right
after a relative jump operation. Yes, labels are only for
relative jumps.
For backward labels, set happens before write. For forward
labels, write happen before set. The write operation writes
a dummy placeholder, and then the set operation writes the
offset at that placeholder's address.
Variable actions are expected to be called with labels in
front of them. Example, "L2 FSET"
About that "1 -": z80 relative jumps record "e-2", that is,
the offset that *counts the 2 bytes of the jump itself*.
Because we set the label *after* the jump OP1 itself, that's
1 byte that is taken care of. We still need to adjust by
another byte before writing the offset.
)
( Place BEGIN, where you want to jump back and AGAIN after
a relative jump operator. Just like BSET and BWR. )
: BEGIN, PC ;
: AGAIN, PC - 1 - A, ;
: BSET PC SWAP ! ;
: BWR @ AGAIN, ;
( same as BSET, but we need to write a placeholder )
: FJR, PC 0 A, ;
: IFZ, JRZ, FJR, ;
: IFNZ, JRNZ, FJR, ;
: IFC, JRC, FJR, ;
: IFNC, JRNC, FJR, ;
: THEN,
DUP PC ( l l pc )
-^ 1 - ( l off )
( warning: l is a PC offset, not a mem addr! )
SWAP ORG @ + ( off addr )
C!
;
: FWR BSET 0 A, ;
: FSET @ THEN, ;

View File

@ -149,10 +149,10 @@ CODE NOT
A L LDrr,
H ORr,
HL 0 LDddnn,
JRNZ, L1 FWR ( skip )
( false, make 1 )
HL INCss,
L1 FSET ( skip )
IFNZ, ( skip )
( false, make 1 )
HL INCss,
THEN, ( skip )
HL PUSHqq,
;CODE
@ -168,8 +168,7 @@ CODE -
DE POPqq,
HL POPqq,
chkPS,
A ORr,
DE SBCHLss,
DE SUBHLss,
HL PUSHqq,
;CODE
@ -204,17 +203,17 @@ CODE /MOD
A B LDrr,
B 16 LDrn,
HL 0 LDddnn,
L1 BSET ( loop )
SCF,
C RLr,
RLA,
HL ADCHLss,
DE SBCHLss,
JRNC, L2 FWR ( skip )
DE ADDHLss,
C DECr,
L2 FSET ( skip )
DJNZ, L1 BWR ( loop )
BEGIN, ( loop )
SCF,
C RLr,
RLA,
HL ADCHLss,
DE SBCHLss,
IFNC, ( skip )
DE ADDHLss,
C DECr,
THEN, ( skip )
DJNZ, AGAIN, ( loop )
B A LDrr,
HL PUSHqq,
BC PUSHqq,
@ -316,16 +315,16 @@ CODE SCMP
DE POPqq,
HL POPqq,
chkPS,
L1 BSET ( loop )
LDA(DE),
(HL) CPr,
JRNZ, L2 FWR ( not equal? break early to "end".
NZ is set. )
A ORr, ( if our char is null, stop )
HL INCss,
DE INCss,
JRNZ, L1 BWR ( loop )
L2 FSET ( end )
BEGIN, ( loop )
LDA(DE),
(HL) CPr,
JRNZ, L1 FWR ( not equal? break early to "end".
NZ is set. )
A ORr, ( if our char is null, stop )
HL INCss,
DE INCss,
JRNZ, AGAIN, ( loop )
L1 FSET ( end )
( 40 == flagsToBC )
40 CALLnn,
BC PUSHqq,
@ -335,8 +334,7 @@ CODE CMP
HL POPqq,
DE POPqq,
chkPS,
A ORr, ( clear carry )
DE SBCHLss,
DE SUBHLss,
( 40 == flagsToBC )
40 CALLnn,
BC PUSHqq,
@ -349,13 +347,13 @@ CODE _find
chkPS,
( 3 == find )
3 CALLnn,
JRZ, L1 FWR ( found )
( not found )
HL PUSHqq,
DE 0 LDddnn,
DE PUSHqq,
JPNEXT,
L1 FSET ( found )
IFZ, ( found )
( not found )
HL PUSHqq,
DE 0 LDddnn,
DE PUSHqq,
JPNEXT,
THEN, ( found )
DE PUSHqq,
DE 1 LDddnn,
DE PUSHqq,

View File

@ -7,7 +7,8 @@ itself.
## Gathering parts
* A RC2014 Classic that could install the base recipe
* A RC2014 Classic
* `stage3.bin` from the base recipe
* An extra AT28C64B
* 1x 40106 inverter gates
* Proto board, RC2014 header pins, wires, IC sockets, etc.
@ -32,52 +33,33 @@ in write protection mode, but I preferred building my own module.
I don't think you need a schematic. It's really simple.
## Building the kernel
## Using the at28 driver
For this recipe to work, we need a block device for the `at28w` program to read
from. The easiest way to go around would be to use a SD card, but maybe you
haven't built a SPI relay yet and it's quite a challenge to do so.
The AT28 driver is at `drv/at28.fs` and is a pure forth source file so it's
rather easy to set up from the base Stage 3 binary:
Therefore, for this recipe, we'll have `at28w` read from a memory map and we'll
upload contents to write to memory through our serial link.
`at28w` is designed to be ran as a "user application", but in this case, because
we run from a kernel without a filesystem and that `pgm` can't run without it,
we'll integrate `at28w` directly in our kernel and expose it as an extra shell
command (renaming it to `a28w` to fit the 4 chars limit).
For all this to work, you'll need [glue code that looks like this](glue.asm).
Running `make` in this directory will produce a `os.bin` with that glue code
that you can install in the same way you did with the basic RC2014 recipe.
If your range is different than `0x2000-0x3fff`, you'll have to modify
`AT28W_MEMSTART` before you build.
cat ../stage3.bin ../pre.fs ../../../drv/at28.fs ../run.fs > os.bin
../../../emul/hw/rc2014/classic os.bin
## Writing contents to the AT28
The memory map is configured to start at `0xd000`. The first step is to upload
contents at that address as documented in ["Load code in RAM and run it"][load].
The driver provides `AT28!` which can be plugged in adev's `A!*`.
You have to know the size of the contents you've loaded because you'll pass it
as at argument to `a28w`. You can run:
It's not in the Stage 3 binary, but because it's a small piece of Forth code,
let's just run its definition code:
Collapse OS
> bsel 0
> seek 00 0000
> a28w <size-of-contents>
cat ../../../drv/at28.fs | ./stripfc | ./exec <tty device>
It takes a little while to write. About 1 second per 0x100 bytes (soon, I'll
implement page writing which should make it much faster).
Then, upload your binary to some place in memory, for example `a000`. To do so,
run this from your modern computer:
If the program doesn't report an error, you're all good! The program takes care
of verifying each byte, so everything should be in place. You can verify
yourself by `peek`-ing around the `0x2000-0x3fff` range.
./upload <tty device> a000 <filename>
Note that to write a single byte to the AT28 eeprom, you don't need a special
program. You can, while you're in the `0x2000-0x3fff` range, run `poke 1` and
send an arbitrary char. It will work. The problem is with writing multiple
bytes: you have to wait until the eeprom is finished writing before writing to
a new address, something a regular `poke` doesn't do but `at28w` does.
[load]: ../../../doc/load-run-code.md
Then, activate `AT28!` with `' AT28! A!* !` and then run
`0xa000 0x2000 <size-of-bin> AMOVE`. `AT28!` checks every myte for integrity,
so it there's no error, you should be fine. Your content is now on the EEPROM!
Why not upload content directly to `0x2000` after having activated `AT28!`?
Technically, you could. It was my first idea too. However, at the time of this
writing, I always get weird mismatch errors about halfway through. Maybe that
the ACIA interrupt does something wrong...

1
tools/.gitignore vendored
View File

@ -8,3 +8,4 @@
/stripfc
/bin2c
/exec
/blkpack

View File

@ -8,9 +8,10 @@ SLATEST_TGT = slatest
STRIPFC_TGT = stripfc
BIN2C_TGT = bin2c
EXEC_TGT = exec
BLKPACK_TGT = blkpack
TARGETS = $(MEMDUMP_TGT) $(BLKDUMP_TGT) $(UPLOAD_TGT) $(FONTCOMPILE_TGT) \
$(TTYSAFE_TGT) $(PINGPONG_TGT) $(SLATEST_TGT) $(STRIPFC_TGT) \
$(BIN2C_TGT) $(EXEC_TGT)
$(BIN2C_TGT) $(EXEC_TGT) $(BLKPACK_TGT)
OBJS = common.o
all: $(TARGETS)
@ -29,6 +30,7 @@ $(SLATEST_TGT): $(SLATEST_TGT).c
$(STRIPFC_TGT): $(STRIPFC_TGT).c
$(BIN2C_TGT): $(BIN2C_TGT).c
$(EXEC_TGT): $(EXEC_TGT).c
$(BLKPACK_TGT): $(BLKPACK_TGT).c
$(TARGETS): $(OBJS)
$(CC) $(CFLAGS) $@.c $(OBJS) -o $@

64
tools/blkpack.c Normal file
View File

@ -0,0 +1,64 @@
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <dirent.h>
#include <string.h>
#include <sys/stat.h>
void usage()
{
fprintf(stderr, "Usage: blkpack dirname\n");
}
int main(int argc, char *argv[])
{
DIR *dp;
struct dirent *ep;
char *buf = NULL;
int blkcnt = 0;
if (argc != 2) {
usage();
return 1;
}
dp = opendir(argv[1]);
if (dp == NULL) {
fprintf(stderr, "Couldn't open directory.\n");
return 1;
}
while ((ep = readdir(dp))) {
if ((strcmp(ep->d_name, ".") == 0) || strcmp(ep->d_name, "..") == 0) {
continue;
}
if (ep->d_type != DT_REG) {
continue;
}
int blkid = atoi(ep->d_name);
if (blkid >= blkcnt) {
int newcnt = blkid+1;
buf = realloc(buf, newcnt*1024);
bzero(buf+(blkcnt*1024), (newcnt-blkcnt)*1024);
blkcnt = newcnt;
}
char fullpath[0x200];
strcpy(fullpath, argv[1]);
strcat(fullpath, "/");
strcat(fullpath, ep->d_name);
FILE *fp = fopen(fullpath, "r");
char *line = NULL;
size_t n = 0;
for (int i=0; i<16; i++) {
ssize_t cnt = getline(&line, &n, fp);
if (cnt < 0) break;
if (cnt > 65) {
fprintf(stderr, "Line %d too long in blk %s\n", i+1, ep->d_name);
}
strncpy(buf+(blkid*1024)+(i*64), line, cnt-1);
}
free(line);
}
fwrite(buf, 1024, blkcnt, stdout);
free(buf);
closedir(dp);
return 0;
}

View File

@ -8,7 +8,9 @@
void mread(int fd, char *s, int count)
{
while (count) {
while (read(fd, s, 1) == 0);
while (read(fd, s, 1) == 0) {
usleep(1000);
}
s++;
count--;
}

View File

@ -65,6 +65,7 @@ int main(int argc, char **argv)
// we don't exit now because we need to "consume" our whole program.
returncode = 1;
}
usleep(1000); // let it breathe
}
mread(fd, s, 2); // "> " prompt
sendcmdp(fd, "FORGET _");