mirror of
https://github.com/hsoft/collapseos.git
synced 2024-11-26 17:48:05 +11:00
Compare commits
5 Commits
d0545d555f
...
d4cdb659b4
Author | SHA1 | Date | |
---|---|---|---|
|
d4cdb659b4 | ||
|
7292d486dc | ||
|
7d184b9e70 | ||
|
d919a10265 | ||
|
b536d3bfd6 |
@ -41,11 +41,12 @@ path to giving Collapse OS a try.
|
|||||||
from this folder.
|
from this folder.
|
||||||
* `recipes`: collection of recipes that assemble parts together on a specific
|
* `recipes`: collection of recipes that assemble parts together on a specific
|
||||||
machine.
|
machine.
|
||||||
|
* `blk`: Collapse OS filesystem's content. See `000` for intro.
|
||||||
* `doc`: User guide for when you've successfully installed Collapse OS.
|
* `doc`: User guide for when you've successfully installed Collapse OS.
|
||||||
* `tools`: Tools for working with Collapse OS from "modern" environments. For
|
* `tools`: Tools for working with Collapse OS from "modern" environments. For
|
||||||
example, tools for facilitating data upload to a Collapse OS machine
|
example, tools for facilitating data upload to a Collapse OS machine
|
||||||
through a serial port.
|
through a serial port.
|
||||||
* `emul`: Emulated applications, such as zasm and the shell.
|
* `emul`: Emulated applications.
|
||||||
* `tests`: Automated test suite for the whole project.
|
* `tests`: Automated test suite for the whole project.
|
||||||
|
|
||||||
## Status
|
## Status
|
||||||
|
15
blk/000
Normal file
15
blk/000
Normal 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.
|
10
drv/at28.fs
10
drv/at28.fs
@ -2,12 +2,12 @@
|
|||||||
operation while doing the right thing. Checks data integrity
|
operation while doing the right thing. Checks data integrity
|
||||||
and ABORT on mismatch.
|
and ABORT on mismatch.
|
||||||
)
|
)
|
||||||
( a n -- )
|
( n a -- )
|
||||||
: AT28!
|
: AT28!
|
||||||
2DUP C! SWAP
|
2DUP C!
|
||||||
( as long as writing operation is running, IO/6 will toggle at each
|
( 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
|
read attempt. We know that write is finished when we read the same
|
||||||
value twice. )
|
value twice. )
|
||||||
BEGIN ( n1 a )
|
BEGIN ( n1 a )
|
||||||
DUP C@ ( n1 a n2 )
|
DUP C@ ( n1 a n2 )
|
||||||
OVER C@ ( n1 a n2 n3 )
|
OVER C@ ( n1 a n2 n3 )
|
||||||
|
2
emul/.gitignore
vendored
2
emul/.gitignore
vendored
@ -1,8 +1,6 @@
|
|||||||
/forth/stage1
|
/forth/stage1
|
||||||
/forth/stage1dbg
|
/forth/stage1dbg
|
||||||
/forth/stage2
|
/forth/stage2
|
||||||
/forth/stage2dbg
|
|
||||||
/forth/forth
|
/forth/forth
|
||||||
/runbin/runbin
|
|
||||||
/*/*-bin.h
|
/*/*-bin.h
|
||||||
/*/*.bin
|
/*/*.bin
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
TARGETS = runbin/runbin forth/forth
|
TARGETS = forth/forth
|
||||||
BIN2C = ../tools/bin2c
|
BIN2C = ../tools/bin2c
|
||||||
# Those Forth source files are in a particular order
|
# Those Forth source files are in a particular order
|
||||||
BOOTSRCS = ./forth/conf.fs \
|
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
|
forth/forth: forth/forth.c $(OBJS) forth/forth1-bin.h
|
||||||
$(CC) forth/forth.c $(OBJS) -o $@
|
$(CC) forth/forth.c $(OBJS) -o $@
|
||||||
|
|
||||||
runbin/runbin: runbin/runbin.c $(OBJS)
|
|
||||||
$(CC) runbin/runbin.c $(OBJS) -o $@
|
|
||||||
|
|
||||||
libz80/libz80.o: libz80/z80.c
|
libz80/libz80.o: libz80/z80.c
|
||||||
$(MAKE) -C libz80/codegen opcodes
|
$(MAKE) -C libz80/codegen opcodes
|
||||||
$(CC) -Wall -ansi -g -c -o libz80/libz80.o libz80/z80.c
|
$(CC) -Wall -ansi -g -c -o libz80/libz80.o libz80/z80.c
|
||||||
|
@ -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
|
Normally, running this step should yield the exact same `boot.bin` and
|
||||||
`z80c.bin` as before, unless of course you've changed the source.
|
`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?
|
## Problems?
|
||||||
|
|
||||||
If the libz80-wrapped zasm executable works badly (hangs, spew garbage, etc.),
|
If the libz80-wrapped zasm executable works badly (hangs, spew garbage, etc.),
|
||||||
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
@ -159,7 +159,7 @@ PC ORG @ 4 + ! ( find )
|
|||||||
adjust. Because the compare loop pre-decrements, instead
|
adjust. Because the compare loop pre-decrements, instead
|
||||||
of DECing HL twice, we DEC it once. )
|
of DECing HL twice, we DEC it once. )
|
||||||
HL DECss,
|
HL DECss,
|
||||||
L3 BSET ( inner )
|
BEGIN, ( inner )
|
||||||
( DE is a wordref, first step, do our len correspond? )
|
( DE is a wordref, first step, do our len correspond? )
|
||||||
HL PUSHqq, ( --> lvl 1 )
|
HL PUSHqq, ( --> lvl 1 )
|
||||||
DE PUSHqq, ( --> lvl 2 )
|
DE PUSHqq, ( --> lvl 2 )
|
||||||
@ -172,15 +172,15 @@ L3 BSET ( inner )
|
|||||||
DE DECss, ( Skip prev field. One less because we )
|
DE DECss, ( Skip prev field. One less because we )
|
||||||
DE DECss, ( pre-decrement )
|
DE DECss, ( pre-decrement )
|
||||||
B C LDrr, ( loop C times )
|
B C LDrr, ( loop C times )
|
||||||
L5 BSET ( loop )
|
BEGIN, ( loop )
|
||||||
( pre-decrement for easier Z matching )
|
( pre-decrement for easier Z matching )
|
||||||
DE DECss,
|
DE DECss,
|
||||||
HL DECss,
|
HL DECss,
|
||||||
LDA(DE),
|
LDA(DE),
|
||||||
(HL) CPr,
|
(HL) CPr,
|
||||||
JRNZ, L6 FWR ( loopend )
|
JRNZ, L3 FWR ( loopend )
|
||||||
DJNZ, L5 BWR ( loop )
|
DJNZ, AGAIN, ( loop )
|
||||||
L4 FSET L6 FSET ( loopend )
|
L4 FSET L3 FSET ( loopend )
|
||||||
( At this point, Z is set if we have a match. In all cases,
|
( At this point, Z is set if we have a match. In all cases,
|
||||||
we want to pop HL and DE )
|
we want to pop HL and DE )
|
||||||
DE POPqq, ( <-- lvl 2 )
|
DE POPqq, ( <-- lvl 2 )
|
||||||
@ -201,14 +201,14 @@ L4 FSET L6 FSET ( loopend )
|
|||||||
( HL is prev field's addr. Is offset zero? )
|
( HL is prev field's addr. Is offset zero? )
|
||||||
A D LDrr,
|
A D LDrr,
|
||||||
E ORr,
|
E ORr,
|
||||||
JRZ, L6 FWR ( noprev )
|
IFZ, ( noprev )
|
||||||
( get absolute addr from offset )
|
( get absolute addr from offset )
|
||||||
( carry cleared from "or e" )
|
( carry cleared from "or e" )
|
||||||
DE SBCHLss,
|
DE SBCHLss,
|
||||||
EXDEHL, ( result in DE )
|
EXDEHL, ( result in DE )
|
||||||
L6 FSET ( noprev )
|
THEN, ( noprev )
|
||||||
HL POPqq, ( <-- lvl 1 )
|
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 )
|
( Z set? end of dict, unset Z )
|
||||||
L1 FSET ( fail )
|
L1 FSET ( fail )
|
||||||
A XORr,
|
A XORr,
|
||||||
@ -260,8 +260,7 @@ PC ORG @ 0x1e + ! ( chkPS )
|
|||||||
HL DECss,
|
HL DECss,
|
||||||
HL DECss,
|
HL DECss,
|
||||||
HL DECss,
|
HL DECss,
|
||||||
A ORr, ( clear carry )
|
SP SUBHLss,
|
||||||
SP SBCHLss,
|
|
||||||
HL POPqq,
|
HL POPqq,
|
||||||
CNC RETcc, ( INITIAL_SP >= SP? good )
|
CNC RETcc, ( INITIAL_SP >= SP? good )
|
||||||
JR, L1 BWR ( abortUnderflow )
|
JR, L1 BWR ( abortUnderflow )
|
||||||
@ -269,8 +268,7 @@ PC ORG @ 0x1e + ! ( chkPS )
|
|||||||
L3 BSET ( chkRS )
|
L3 BSET ( chkRS )
|
||||||
IX PUSHqq, HL POPqq,
|
IX PUSHqq, HL POPqq,
|
||||||
DE RS_ADDR LDddnn,
|
DE RS_ADDR LDddnn,
|
||||||
A ORr, ( clear carry )
|
DE SUBHLss,
|
||||||
DE SBCHLss,
|
|
||||||
CNC RETcc, ( IX >= RS_ADDR? good )
|
CNC RETcc, ( IX >= RS_ADDR? good )
|
||||||
JR, L1 BWR ( abortUnderflow )
|
JR, L1 BWR ( abortUnderflow )
|
||||||
|
|
||||||
|
@ -16,18 +16,16 @@
|
|||||||
|
|
||||||
To avoid using dict memory in compilation targets, we
|
To avoid using dict memory in compilation targets, we
|
||||||
pre-declare label variables here, which means we have a
|
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+ ;
|
: L1 2 Z80AMEM+ ;
|
||||||
: L2 4 Z80AMEM+ ;
|
: L2 4 Z80AMEM+ ;
|
||||||
: L3 6 Z80AMEM+ ;
|
: L3 6 Z80AMEM+ ;
|
||||||
: L4 8 Z80AMEM+ ;
|
: L4 8 Z80AMEM+ ;
|
||||||
: L5 10 Z80AMEM+ ;
|
|
||||||
: L6 12 Z80AMEM+ ;
|
|
||||||
|
|
||||||
: Z80A$
|
: Z80A$
|
||||||
( 59 == z80a's memory )
|
( 59 == z80a's memory )
|
||||||
H@ 0x59 RAM+ !
|
H@ 0x59 RAM+ !
|
||||||
14 ALLOT
|
10 ALLOT
|
||||||
;
|
;
|
||||||
|
|
||||||
( Splits word into msb/lsb, lsb being on TOS )
|
( Splits word into msb/lsb, lsb being on TOS )
|
||||||
@ -43,42 +41,6 @@
|
|||||||
: A, C, ;
|
: A, C, ;
|
||||||
: A,, SPLITB A, A, ;
|
: 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 )
|
( "r" register constants )
|
||||||
7 CONSTANT A
|
7 CONSTANT A
|
||||||
0 CONSTANT B
|
0 CONSTANT B
|
||||||
@ -375,3 +337,52 @@
|
|||||||
( Routines )
|
( Routines )
|
||||||
( 29 == chkPS )
|
( 29 == chkPS )
|
||||||
: chkPS, 29 CALLnn, ;
|
: 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, ;
|
||||||
|
|
||||||
|
@ -149,10 +149,10 @@ CODE NOT
|
|||||||
A L LDrr,
|
A L LDrr,
|
||||||
H ORr,
|
H ORr,
|
||||||
HL 0 LDddnn,
|
HL 0 LDddnn,
|
||||||
JRNZ, L1 FWR ( skip )
|
IFNZ, ( skip )
|
||||||
( false, make 1 )
|
( false, make 1 )
|
||||||
HL INCss,
|
HL INCss,
|
||||||
L1 FSET ( skip )
|
THEN, ( skip )
|
||||||
HL PUSHqq,
|
HL PUSHqq,
|
||||||
;CODE
|
;CODE
|
||||||
|
|
||||||
@ -168,8 +168,7 @@ CODE -
|
|||||||
DE POPqq,
|
DE POPqq,
|
||||||
HL POPqq,
|
HL POPqq,
|
||||||
chkPS,
|
chkPS,
|
||||||
A ORr,
|
DE SUBHLss,
|
||||||
DE SBCHLss,
|
|
||||||
HL PUSHqq,
|
HL PUSHqq,
|
||||||
;CODE
|
;CODE
|
||||||
|
|
||||||
@ -204,17 +203,17 @@ CODE /MOD
|
|||||||
A B LDrr,
|
A B LDrr,
|
||||||
B 16 LDrn,
|
B 16 LDrn,
|
||||||
HL 0 LDddnn,
|
HL 0 LDddnn,
|
||||||
L1 BSET ( loop )
|
BEGIN, ( loop )
|
||||||
SCF,
|
SCF,
|
||||||
C RLr,
|
C RLr,
|
||||||
RLA,
|
RLA,
|
||||||
HL ADCHLss,
|
HL ADCHLss,
|
||||||
DE SBCHLss,
|
DE SBCHLss,
|
||||||
JRNC, L2 FWR ( skip )
|
IFNC, ( skip )
|
||||||
DE ADDHLss,
|
DE ADDHLss,
|
||||||
C DECr,
|
C DECr,
|
||||||
L2 FSET ( skip )
|
THEN, ( skip )
|
||||||
DJNZ, L1 BWR ( loop )
|
DJNZ, AGAIN, ( loop )
|
||||||
B A LDrr,
|
B A LDrr,
|
||||||
HL PUSHqq,
|
HL PUSHqq,
|
||||||
BC PUSHqq,
|
BC PUSHqq,
|
||||||
@ -316,16 +315,16 @@ CODE SCMP
|
|||||||
DE POPqq,
|
DE POPqq,
|
||||||
HL POPqq,
|
HL POPqq,
|
||||||
chkPS,
|
chkPS,
|
||||||
L1 BSET ( loop )
|
BEGIN, ( loop )
|
||||||
LDA(DE),
|
LDA(DE),
|
||||||
(HL) CPr,
|
(HL) CPr,
|
||||||
JRNZ, L2 FWR ( not equal? break early to "end".
|
JRNZ, L1 FWR ( not equal? break early to "end".
|
||||||
NZ is set. )
|
NZ is set. )
|
||||||
A ORr, ( if our char is null, stop )
|
A ORr, ( if our char is null, stop )
|
||||||
HL INCss,
|
HL INCss,
|
||||||
DE INCss,
|
DE INCss,
|
||||||
JRNZ, L1 BWR ( loop )
|
JRNZ, AGAIN, ( loop )
|
||||||
L2 FSET ( end )
|
L1 FSET ( end )
|
||||||
( 40 == flagsToBC )
|
( 40 == flagsToBC )
|
||||||
40 CALLnn,
|
40 CALLnn,
|
||||||
BC PUSHqq,
|
BC PUSHqq,
|
||||||
@ -335,8 +334,7 @@ CODE CMP
|
|||||||
HL POPqq,
|
HL POPqq,
|
||||||
DE POPqq,
|
DE POPqq,
|
||||||
chkPS,
|
chkPS,
|
||||||
A ORr, ( clear carry )
|
DE SUBHLss,
|
||||||
DE SBCHLss,
|
|
||||||
( 40 == flagsToBC )
|
( 40 == flagsToBC )
|
||||||
40 CALLnn,
|
40 CALLnn,
|
||||||
BC PUSHqq,
|
BC PUSHqq,
|
||||||
@ -349,13 +347,13 @@ CODE _find
|
|||||||
chkPS,
|
chkPS,
|
||||||
( 3 == find )
|
( 3 == find )
|
||||||
3 CALLnn,
|
3 CALLnn,
|
||||||
JRZ, L1 FWR ( found )
|
IFZ, ( found )
|
||||||
( not found )
|
( not found )
|
||||||
HL PUSHqq,
|
HL PUSHqq,
|
||||||
DE 0 LDddnn,
|
DE 0 LDddnn,
|
||||||
DE PUSHqq,
|
DE PUSHqq,
|
||||||
JPNEXT,
|
JPNEXT,
|
||||||
L1 FSET ( found )
|
THEN, ( found )
|
||||||
DE PUSHqq,
|
DE PUSHqq,
|
||||||
DE 1 LDddnn,
|
DE 1 LDddnn,
|
||||||
DE PUSHqq,
|
DE PUSHqq,
|
||||||
|
@ -7,7 +7,8 @@ itself.
|
|||||||
|
|
||||||
## Gathering parts
|
## Gathering parts
|
||||||
|
|
||||||
* A RC2014 Classic that could install the base recipe
|
* A RC2014 Classic
|
||||||
|
* `stage3.bin` from the base recipe
|
||||||
* An extra AT28C64B
|
* An extra AT28C64B
|
||||||
* 1x 40106 inverter gates
|
* 1x 40106 inverter gates
|
||||||
* Proto board, RC2014 header pins, wires, IC sockets, etc.
|
* 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.
|
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
|
The AT28 driver is at `drv/at28.fs` and is a pure forth source file so it's
|
||||||
from. The easiest way to go around would be to use a SD card, but maybe you
|
rather easy to set up from the base Stage 3 binary:
|
||||||
haven't built a SPI relay yet and it's quite a challenge to do so.
|
|
||||||
|
|
||||||
Therefore, for this recipe, we'll have `at28w` read from a memory map and we'll
|
cat ../stage3.bin ../pre.fs ../../../drv/at28.fs ../run.fs > os.bin
|
||||||
upload contents to write to memory through our serial link.
|
../../../emul/hw/rc2014/classic os.bin
|
||||||
|
|
||||||
`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.
|
|
||||||
|
|
||||||
## Writing contents to the AT28
|
## Writing contents to the AT28
|
||||||
|
|
||||||
The memory map is configured to start at `0xd000`. The first step is to upload
|
The driver provides `AT28!` which can be plugged in adev's `A!*`.
|
||||||
contents at that address as documented in ["Load code in RAM and run it"][load].
|
|
||||||
|
|
||||||
You have to know the size of the contents you've loaded because you'll pass it
|
It's not in the Stage 3 binary, but because it's a small piece of Forth code,
|
||||||
as at argument to `a28w`. You can run:
|
let's just run its definition code:
|
||||||
|
|
||||||
Collapse OS
|
cat ../../../drv/at28.fs | ./stripfc | ./exec <tty device>
|
||||||
> bsel 0
|
|
||||||
> seek 00 0000
|
|
||||||
> a28w <size-of-contents>
|
|
||||||
|
|
||||||
It takes a little while to write. About 1 second per 0x100 bytes (soon, I'll
|
Then, upload your binary to some place in memory, for example `a000`. To do so,
|
||||||
implement page writing which should make it much faster).
|
run this from your modern computer:
|
||||||
|
|
||||||
If the program doesn't report an error, you're all good! The program takes care
|
./upload <tty device> a000 <filename>
|
||||||
of verifying each byte, so everything should be in place. You can verify
|
|
||||||
yourself by `peek`-ing around the `0x2000-0x3fff` range.
|
|
||||||
|
|
||||||
Note that to write a single byte to the AT28 eeprom, you don't need a special
|
Then, activate `AT28!` with `' AT28! A!* !` and then run
|
||||||
program. You can, while you're in the `0x2000-0x3fff` range, run `poke 1` and
|
`0xa000 0x2000 <size-of-bin> AMOVE`. `AT28!` checks every myte for integrity,
|
||||||
send an arbitrary char. It will work. The problem is with writing multiple
|
so it there's no error, you should be fine. Your content is now on the EEPROM!
|
||||||
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
|
|
||||||
|
|
||||||
|
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
1
tools/.gitignore
vendored
@ -8,3 +8,4 @@
|
|||||||
/stripfc
|
/stripfc
|
||||||
/bin2c
|
/bin2c
|
||||||
/exec
|
/exec
|
||||||
|
/blkpack
|
||||||
|
@ -8,9 +8,10 @@ SLATEST_TGT = slatest
|
|||||||
STRIPFC_TGT = stripfc
|
STRIPFC_TGT = stripfc
|
||||||
BIN2C_TGT = bin2c
|
BIN2C_TGT = bin2c
|
||||||
EXEC_TGT = exec
|
EXEC_TGT = exec
|
||||||
|
BLKPACK_TGT = blkpack
|
||||||
TARGETS = $(MEMDUMP_TGT) $(BLKDUMP_TGT) $(UPLOAD_TGT) $(FONTCOMPILE_TGT) \
|
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) $(EXEC_TGT)
|
$(BIN2C_TGT) $(EXEC_TGT) $(BLKPACK_TGT)
|
||||||
OBJS = common.o
|
OBJS = common.o
|
||||||
|
|
||||||
all: $(TARGETS)
|
all: $(TARGETS)
|
||||||
@ -29,6 +30,7 @@ $(SLATEST_TGT): $(SLATEST_TGT).c
|
|||||||
$(STRIPFC_TGT): $(STRIPFC_TGT).c
|
$(STRIPFC_TGT): $(STRIPFC_TGT).c
|
||||||
$(BIN2C_TGT): $(BIN2C_TGT).c
|
$(BIN2C_TGT): $(BIN2C_TGT).c
|
||||||
$(EXEC_TGT): $(EXEC_TGT).c
|
$(EXEC_TGT): $(EXEC_TGT).c
|
||||||
|
$(BLKPACK_TGT): $(BLKPACK_TGT).c
|
||||||
$(TARGETS): $(OBJS)
|
$(TARGETS): $(OBJS)
|
||||||
$(CC) $(CFLAGS) $@.c $(OBJS) -o $@
|
$(CC) $(CFLAGS) $@.c $(OBJS) -o $@
|
||||||
|
|
||||||
|
64
tools/blkpack.c
Normal file
64
tools/blkpack.c
Normal 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;
|
||||||
|
}
|
||||||
|
|
@ -8,7 +8,9 @@
|
|||||||
void mread(int fd, char *s, int count)
|
void mread(int fd, char *s, int count)
|
||||||
{
|
{
|
||||||
while (count) {
|
while (count) {
|
||||||
while (read(fd, s, 1) == 0);
|
while (read(fd, s, 1) == 0) {
|
||||||
|
usleep(1000);
|
||||||
|
}
|
||||||
s++;
|
s++;
|
||||||
count--;
|
count--;
|
||||||
}
|
}
|
||||||
|
@ -65,6 +65,7 @@ int main(int argc, char **argv)
|
|||||||
// we don't exit now because we need to "consume" our whole program.
|
// we don't exit now because we need to "consume" our whole program.
|
||||||
returncode = 1;
|
returncode = 1;
|
||||||
}
|
}
|
||||||
|
usleep(1000); // let it breathe
|
||||||
}
|
}
|
||||||
mread(fd, s, 2); // "> " prompt
|
mread(fd, s, 2); // "> " prompt
|
||||||
sendcmdp(fd, "FORGET _");
|
sendcmdp(fd, "FORGET _");
|
||||||
|
Loading…
Reference in New Issue
Block a user