Compare commits

...

4 Commits

Author SHA1 Message Date
Virgil Dupras
d466c6d95d emul: make stage2 load z80a from blkfs
This allows us to remove forth/z80a.fs. Another big step towards
self-hosting!
2020-04-19 21:31:41 -04:00
Virgil Dupras
6ae65940d2 Update bootstrap bin
It seems that I had forgotten in an earlier commit.
2020-04-19 20:57:09 -04:00
Virgil Dupras
0892193cbe Copy z80a.fs to disk blocks
I don't remove z80a.fs yet because build process needs it, but that
will change shortly.
2020-04-19 20:36:13 -04:00
Virgil Dupras
98191d0b0d recipes/rc2014/sdcard: now works under Forth! 2020-04-19 16:56:37 -04:00
42 changed files with 428 additions and 635 deletions

View File

@ -2,6 +2,7 @@ MASTER INDEX
3 Usage 30 Dictionary
70 Implementation notes 100 Block editor
200 Z80 assembler

13
blk/200 Normal file
View File

@ -0,0 +1,13 @@
Z80 Assembler
201 Guide 208 Instructions list
212 Loader 213 Variables & consts
215 Utils 216 OP1
218 OP1r 220 OP1qq
222 OP1rr 224 OP2
226 OP2n 228 OP2rn
230 OP2br 232 OProt
234 OP2r 236 OP2ss
238 OP3ddnn 240 OP3nn
242 Specials 246 Flow

15
blk/201 Normal file
View File

@ -0,0 +1,15 @@
The following words allow you to assemble z80 binaries. Being
Forth words, opcode assembly is a bit different than with a
typical assembler. For example, what would traditionally be
"ld a, b" would become "A B LDrr,".
H@ offset at which we consider our PC 0. Used to compute PC. To
have a proper PC, call "H@ ORG !" at the beginning of your
assembly process.
Labels are a convenient way of managing relative jump
calculations. Backward labels are easy. It is only a matter or
recording "HERE" and do subtractions. Forward labels record the
place where we should write the offset, and then when we get to
that point later on, the label records the offset there.

13
blk/202 Normal file
View File

@ -0,0 +1,13 @@
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, 4 ought to be enough.
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.

14
blk/203 Normal file
View File

@ -0,0 +1,14 @@
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.

16
blk/208 Normal file
View File

@ -0,0 +1,16 @@
Instructions list
r => A B C D E H L (HL)
ss/qq => BC DE HL AF/SP
cc => CNZ CZ CNC CC CPO CPE CP CM
LD [rr, rn, ddnn, (nn)HL, HL(nn), dd(nn), (nn)dd, rIXY, IXYr,
(DE)A, A(DE)]
ADD [r, n, HLss, IXss, IXIX, IYss, IYIY]
ADC [r, HLss]
CP [r]
SBC [r, HLss]
SUB [r, n]
PUSH [qq] POP [qq]
INC [r, ss] DEC [r, ss]
AND [r, n] OR [r, n] XOR [r, n] (cont.)

11
blk/209 Normal file
View File

@ -0,0 +1,11 @@
(cont.)
OUT [nA, (C)r] IN [An, r(C)]
SET [br] RES [br] BIT [br]
RL [r] RLC [r] SLA [r] RLA RLCA
RR [r] RRC [r] SRL [r] RRA RRCA
CALL [nn] DJNZ
JP [nn, (HL), (IX), (IY)]
JR [, Z, NZ, C, NC]
DI EI EXDEHL EXX HALT
NOP RET SCF

8
blk/212 Normal file
View File

@ -0,0 +1,8 @@
( 59 == z80a's memory )
H@ 0x59 RAM+ !
10 ALLOT
213 LOAD 215 LOAD 216 LOAD 217 LOAD 218 LOAD 219 LOAD
220 LOAD 222 LOAD 223 LOAD 224 LOAD 226 LOAD 228 LOAD
230 LOAD 232 LOAD 234 LOAD 236 LOAD 238 LOAD 240 LOAD
242 LOAD 243 LOAD 244 LOAD 246 LOAD 247 LOAD

9
blk/213 Normal file
View File

@ -0,0 +1,9 @@
: Z80AMEM+ 0x59 RAM+ @ + ;
: ORG 0 Z80AMEM+ ;
: L1 2 Z80AMEM+ ; : L2 4 Z80AMEM+ ;
: L3 6 Z80AMEM+ ; : L4 8 Z80AMEM+ ;
: A 7 ; : B 0 ; : C 1 ; : D 2 ;
: E 3 ; : H 4 ; : L 5 ; : (HL) 6 ;
: BC 0 ; : DE 1 ; : HL 2 ; : AF 3 ; : SP AF ;
: CNZ 0 ; : CZ 1 ; : CNC 2 ; : CC 3 ;
: CPO 4 ; : CPE 5 ; : CP 6 ; : CM 7 ;

16
blk/215 Normal file
View File

@ -0,0 +1,16 @@
( Splits word into msb/lsb, lsb being on TOS )
: SPLITB
256 /MOD SWAP
;
: PC H@ ORG @ - ;
( A, spits an assembled byte, A,, spits an assembled word
Both increase PC. To debug, change C, to .X )
: A, C, ; : A,, SPLITB A, A, ;
: <<3 8 * ; : <<4 16 * ;
( As a general rule, IX and IY are equivalent to spitting an
extra 0xdd / 0xfd and then spit the equivalent of HL )
: IX 0xdd A, HL ; : IY 0xfd A, HL ;
: _ix+- 0xff AND 0xdd A, (HL) ;
: _iy+- 0xff AND 0xfd A, (HL) ;
: IX+ _ix+- ; : IX- 0 -^ _ix+- ;
: IY+ _iy+- ; : IY- 0 -^ _iy+- ;

10
blk/216 Normal file
View File

@ -0,0 +1,10 @@
: OP1 CREATE C, DOES> C@ A, ;
0xf3 OP1 DI, 0xfb OP1 EI,
0xeb OP1 EXDEHL, 0xd9 OP1 EXX,
0x76 OP1 HALT, 0xe9 OP1 JP(HL),
0x12 OP1 LD(DE)A, 0x1a OP1 LDA(DE),
0x00 OP1 NOP, 0xc9 OP1 RET,
0x17 OP1 RLA, 0x07 OP1 RLCA,
0x1f OP1 RRA, 0x0f OP1 RRCA,
0x37 OP1 SCF,

11
blk/217 Normal file
View File

@ -0,0 +1,11 @@
( Relative jumps are a bit special. They're supposed to take
an argument, but they don't take it so they can work with
the label system. Therefore, relative jumps are an OP1 but
when you use them, you're expected to write the offset
afterwards yourself. )
0x18 OP1 JR, 0x10 OP1 DJNZ,
0x38 OP1 JRC, 0x30 OP1 JRNC,
0x28 OP1 JRZ, 0x20 OP1 JRNZ,

10
blk/218 Normal file
View File

@ -0,0 +1,10 @@
( r -- )
: OP1r
CREATE C,
DOES>
C@ ( r op )
SWAP ( op r )
<<3 ( op r<<3 )
OR A,
;
0x04 OP1r INCr, 0x05 OP1r DECr,

13
blk/219 Normal file
View File

@ -0,0 +1,13 @@
( also works for cc )
0xc0 OP1r RETcc,
( r -- )
: OP1r0
CREATE C,
DOES>
C@ ( r op )
OR A,
;
0x80 OP1r0 ADDr, 0x88 OP1r0 ADCr,
0xa0 OP1r0 ANDr, 0xb8 OP1r0 CPr,
0xb0 OP1r0 ORr, 0x90 OP1r0 SUBr,
0x98 OP1r0 SBCr, 0xa8 OP1r0 XORr,

15
blk/220 Normal file
View File

@ -0,0 +1,15 @@
( qq -- also works for ss )
: OP1qq
CREATE C,
DOES>
C@ ( qq op )
SWAP ( op qq )
<<4 ( op qq<<4 )
OR A,
;
0xc5 OP1qq PUSHqq, 0xc1 OP1qq POPqq,
0x03 OP1qq INCss, 0x0b OP1qq DECss,
0x09 OP1qq ADDHLss,
: ADDIXss, 0xdd A, ADDHLss, ; : ADDIXIX, HL ADDIXss, ;
: ADDIYss, 0xfd A, ADDHLss, ; : ADDIYIY, HL ADDIYss, ;

14
blk/222 Normal file
View File

@ -0,0 +1,14 @@
: _1rr
C@ ( rd rr op )
ROT ( rr op rd )
<<3 ( rr op rd<<3 )
OR OR A,
;
( rd rr )
: OP1rr
CREATE C,
DOES>
_1rr
;
0x40 OP1rr LDrr,

13
blk/223 Normal file
View File

@ -0,0 +1,13 @@
( ixy+- HL rd )
: LDIXYr,
( dd/fd has already been spit )
LDrr, ( ixy+- )
A,
;
( rd ixy+- HL )
: LDrIXY,
ROT ( ixy+- HL rd )
SWAP ( ixy+- rd HL )
LDIXYr,
;

7
blk/224 Normal file
View File

@ -0,0 +1,7 @@
: OP2 CREATE , DOES> @ 256 /MOD A, A, ;
0xedb1 OP2 CPIR,
0xed46 OP2 IM0,
0xed56 OP2 IM1,
0xed5e OP2 IM2,
0xed44 OP2 NEG,
0xed4d OP2 RETI,

13
blk/226 Normal file
View File

@ -0,0 +1,13 @@
( n -- )
: OP2n
CREATE C,
DOES>
C@ A, A,
;
0xd3 OP2n OUTnA,
0xdb OP2n INAn,
0xc6 OP2n ADDn,
0xe6 OP2n ANDn,
0xf6 OP2n ORn,
0xd6 OP2n SUBn,

11
blk/228 Normal file
View File

@ -0,0 +1,11 @@
( r n -- )
: OP2rn
CREATE C,
DOES>
C@ ( r n op )
ROT ( n op r )
<<3 ( n op r<<3 )
OR A, A,
;
0x06 OP2rn LDrn,

14
blk/230 Normal file
View File

@ -0,0 +1,14 @@
( b r -- )
: OP2br
CREATE C,
DOES>
0xcb A,
C@ ( b r op )
ROT ( r op b )
<<3 ( r op b<<3 )
OR OR A,
;
0xc0 OP2br SETbr,
0x80 OP2br RESbr,
0x40 OP2br BITbr,

16
blk/232 Normal file
View File

@ -0,0 +1,16 @@
( bitwise rotation ops have a similar sig )
( r -- )
: OProt
CREATE C,
DOES>
0xcb A,
C@ ( r op )
OR A,
;
0x10 OProt RLr,
0x00 OProt RLCr,
0x18 OProt RRr,
0x08 OProt RRCr,
0x20 OProt SLAr,
0x38 OProt SRLr,

14
blk/234 Normal file
View File

@ -0,0 +1,14 @@
( cell contains both bytes. MSB is spit as-is, LSB is ORed
with r )
( r -- )
: OP2r
CREATE ,
DOES>
@ SPLITB SWAP ( r lsb msb )
A, ( r lsb )
SWAP <<3 ( lsb r<<3 )
OR A,
;
0xed41 OP2r OUT(C)r,
0xed40 OP2r INr(C),

12
blk/236 Normal file
View File

@ -0,0 +1,12 @@
( ss -- )
: OP2ss
CREATE C,
DOES>
0xed A,
C@ SWAP ( op ss )
<<4 ( op ss<< 4 )
OR A,
;
0x4a OP2ss ADCHLss,
0x42 OP2ss SBCHLss,

11
blk/238 Normal file
View File

@ -0,0 +1,11 @@
( dd nn -- )
: OP3ddnn
CREATE C,
DOES>
C@ ( dd nn op )
ROT ( nn op dd )
<<4 ( nn op dd<<4 )
OR A,
A,,
;
0x01 OP3ddnn LDddnn,

11
blk/240 Normal file
View File

@ -0,0 +1,11 @@
( nn -- )
: OP3nn
CREATE C,
DOES>
C@ A,
A,,
;
0xcd OP3nn CALLnn,
0xc3 OP3nn JPnn,
0x22 OP3nn LD(nn)HL,
0x2a OP3nn LDHL(nn),

16
blk/242 Normal file
View File

@ -0,0 +1,16 @@
( dd nn -- )
: LDdd(nn),
0xed A,
SWAP <<4 0x4b OR A,
A,,
;
( nn dd -- )
: LD(nn)dd,
0xed A,
<<4 0x43 OR A,
A,,
;
: JP(IX), IX DROP JP(HL), ;
: JP(IY), IY DROP JP(HL), ;

14
blk/243 Normal file
View File

@ -0,0 +1,14 @@
( 26 == next )
: JPNEXT, 26 JPnn, ;
( 29 == chkPS )
: chkPS, 29 CALLnn, ;
: CODE
( same as CREATE, but with native word )
(entry)
( 23 == nativeWord )
23 C,
;
: ;CODE JPNEXT, ;

3
blk/244 Normal file
View File

@ -0,0 +1,3 @@
( Macros )
( clear carry + SBC )
: SUBHLss, A ORr, SBCHLss, ;

13
blk/246 Normal file
View File

@ -0,0 +1,13 @@
( 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, JRNZ, FJR, ;
: IFNZ, JRZ, FJR, ;
: IFC, JRNC, FJR, ;
: IFNC, JRC, FJR, ;

9
blk/247 Normal file
View File

@ -0,0 +1,9 @@
: 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

@ -8,8 +8,7 @@ BOOTSRCS = ./forth/conf.fs \
../forth/icore.fs \
./forth/xstop.fs
FORTHSRCS = core.fs cmp.fs print.fs str.fs parse.fs readln.fs fmt.fs z80a.fs \
link.fs blk.fs
FORTHSRCS = core.fs cmp.fs print.fs str.fs parse.fs readln.fs fmt.fs blk.fs
FORTHSRC_PATHS = ${FORTHSRCS:%=../forth/%} forth/run.fs
OBJS = emul.o libz80/libz80.o
SLATEST = ../tools/slatest
@ -58,12 +57,15 @@ forth/forth1.bin: forth/core.bin $(SLATEST)
forth/forth1-bin.h: forth/forth1.bin $(BIN2C)
$(BIN2C) KERNEL < forth/forth1.bin > $@
forth/stage2: forth/stage.c $(OBJS) forth/forth1-bin.h
forth/stage2: forth/stage.c $(OBJS) forth/forth1-bin.h forth/blkfs-bin.h
$(CC) -DSTAGE2 forth/stage.c $(OBJS) -o $@
blkfs: $(BLKPACK)
$(BLKPACK) ../blk > $@
forth/blkfs-bin.h: blkfs $(BIN2C)
$(BIN2C) BLKFS < blkfs > $@
forth/forth: forth/forth.c $(OBJS) forth/forth1-bin.h blkfs
$(CC) forth/forth.c $(OBJS) -o $@

View File

@ -1,2 +1,3 @@
212 LOAD ( z80 assembler )
0xe800 CONSTANT RAMSTART
0xf000 CONSTANT RS_ADDR

View File

@ -18,7 +18,6 @@
['] EFS@ BLK@* !
['] EFS! BLK!* !
RDLN$
Z80A$
LIT< _sys [entry]
INTERPRET
;

View File

@ -4,6 +4,7 @@
#include "../emul.h"
#ifdef STAGE2
#include "forth1-bin.h"
#include "blkfs-bin.h"
#else
#include "forth0-bin.h"
#endif
@ -34,6 +35,10 @@ trouble of compiling defs to binary.
// To know which part of RAM to dump, we listen to port 2, which at the end of
// its compilation process, spits its HERE addr to port 2 (MSB first)
#define HERE_PORT 0x02
// Port for block reads. Write 2 bytes, MSB first, on that port and then
// read 1024 bytes from the DATA port.
#define BLK_PORT 0x03
#define BLKDATA_PORT 0x04
static int running;
// We support double-pokes, that is, a first poke to tell where to start the
@ -41,6 +46,8 @@ static int running;
// then ending HERE and we start at sizeof(KERNEL).
static uint16_t start_here = 0;
static uint16_t end_here = 0;
static uint16_t blkid = 0;
static unsigned int blkpos = 0;
static uint8_t iord_stdio()
{
@ -68,6 +75,20 @@ static void iowr_here(uint8_t val)
end_here |= val;
}
#ifdef STAGE2
static void iowr_blk(uint8_t val)
{
blkid <<= 8;
blkid |= val;
blkpos = blkid * 1024;
}
static uint8_t iord_blkdata()
{
return BLKFS[blkpos++];
}
#endif
int main(int argc, char *argv[])
{
Machine *m = emul_init();
@ -75,6 +96,10 @@ int main(int argc, char *argv[])
m->iord[STDIO_PORT] = iord_stdio;
m->iowr[STDIO_PORT] = iowr_stdio;
m->iowr[HERE_PORT] = iowr_here;
#ifdef STAGE2
m->iowr[BLK_PORT] = iowr_blk;
m->iord[BLKDATA_PORT] = iord_blkdata;
#endif
// initialize memory
for (int i=0; i<sizeof(KERNEL); i++) {
m->mem[i] = KERNEL[i];

Binary file not shown.

View File

@ -1,392 +0,0 @@
( Z80 assembler )
: Z80AMEM+ 0x59 RAM+ @ + ;
( H@ offset at which we consider our PC 0. Used to compute
PC. To have a proper PC, call "H@ ORG !" at the beginning
of your assembly process. )
: ORG 0 Z80AMEM+ ;
( Labels are a convenient way of managing relative jump
calculations. Backward labels are easy. It is only a matter
or recording "HERE" and do subtractions. Forward labels
record the place where we should write the offset, and then
when we get to that point later on, the label records the
offset there.
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, 4 ought to be enough. )
: L1 2 Z80AMEM+ ;
: L2 4 Z80AMEM+ ;
: L3 6 Z80AMEM+ ;
: L4 8 Z80AMEM+ ;
: Z80A$
( 59 == z80a's memory )
H@ 0x59 RAM+ !
10 ALLOT
;
( Splits word into msb/lsb, lsb being on TOS )
: SPLITB
256 /MOD SWAP
;
: PC H@ ORG @ - ;
( A, spits an assembled byte, A,, spits an assembled word
Both increase PC. To debug, change C, to .X )
: A, C, ;
: A,, SPLITB A, A, ;
( "r" register constants )
7 CONSTANT A
0 CONSTANT B
1 CONSTANT C
2 CONSTANT D
3 CONSTANT E
4 CONSTANT H
5 CONSTANT L
6 CONSTANT (HL)
( "ss" register constants )
0 CONSTANT BC
1 CONSTANT DE
2 CONSTANT HL
3 CONSTANT AF
3 CONSTANT SP
( "cc" condition constants )
0 CONSTANT CNZ
1 CONSTANT CZ
2 CONSTANT CNC
3 CONSTANT CC
4 CONSTANT CPO
5 CONSTANT CPE
6 CONSTANT CP
7 CONSTANT CM
( As a general rule, IX and IY are equivalent to spitting an
extra 0xdd / 0xfd and then spit the equivalent of HL )
: IX 0xdd A, HL ;
: IY 0xfd A, HL ;
: _ix+- 0xff AND 0xdd A, (HL) ;
: _iy+- 0xff AND 0xfd A, (HL) ;
: IX+ _ix+- ;
: IX- 0 -^ _ix+- ;
: IY+ _iy+- ;
: IY- 0 -^ _iy+- ;
: <<3 8 * ;
: <<4 16 * ;
( -- )
: OP1 CREATE C, DOES> C@ A, ;
0xf3 OP1 DI,
0xfb OP1 EI,
0xeb OP1 EXDEHL,
0xd9 OP1 EXX,
0x76 OP1 HALT,
0xe9 OP1 JP(HL),
0x12 OP1 LD(DE)A,
0x1a OP1 LDA(DE),
0x00 OP1 NOP,
0xc9 OP1 RET,
0x17 OP1 RLA,
0x07 OP1 RLCA,
0x1f OP1 RRA,
0x0f OP1 RRCA,
0x37 OP1 SCF,
( Relative jumps are a bit special. They're supposed to take
an argument, but they don't take it so they can work with
the label system. Therefore, relative jumps are an OP1 but
when you use them, you're expected to write the offset
afterwards yourself. )
0x18 OP1 JR,
0x38 OP1 JRC,
0x30 OP1 JRNC,
0x28 OP1 JRZ,
0x20 OP1 JRNZ,
0x10 OP1 DJNZ,
( r -- )
: OP1r
CREATE C,
DOES>
C@ ( r op )
SWAP ( op r )
<<3 ( op r<<3 )
OR A,
;
0x04 OP1r INCr,
0x05 OP1r DECr,
( also works for cc )
0xc0 OP1r RETcc,
( r -- )
: OP1r0
CREATE C,
DOES>
C@ ( r op )
OR A,
;
0x80 OP1r0 ADDr,
0x88 OP1r0 ADCr,
0xa0 OP1r0 ANDr,
0xb8 OP1r0 CPr,
0xb0 OP1r0 ORr,
0x90 OP1r0 SUBr,
0x98 OP1r0 SBCr,
0xa8 OP1r0 XORr,
( qq -- also works for ss )
: OP1qq
CREATE C,
DOES>
C@ ( qq op )
SWAP ( op qq )
<<4 ( op qq<<4 )
OR A,
;
0xc5 OP1qq PUSHqq,
0xc1 OP1qq POPqq,
0x03 OP1qq INCss,
0x0b OP1qq DECss,
0x09 OP1qq ADDHLss,
: ADDIXss, 0xdd A, ADDHLss, ;
: ADDIXIX, HL ADDIXss, ;
: ADDIYss, 0xfd A, ADDHLss, ;
: ADDIYIY, HL ADDIYss, ;
: _1rr
C@ ( rd rr op )
ROT ( rr op rd )
<<3 ( rr op rd<<3 )
OR OR A,
;
( rd rr )
: OP1rr
CREATE C,
DOES>
_1rr
;
0x40 OP1rr LDrr,
( ixy+- HL rd )
: LDIXYr,
( dd/fd has already been spit )
LDrr, ( ixy+- )
A,
;
( rd ixy+- HL )
: LDrIXY,
ROT ( ixy+- HL rd )
SWAP ( ixy+- rd HL )
LDIXYr,
;
: OP2 CREATE , DOES> @ 256 /MOD A, A, ;
0xedb1 OP2 CPIR,
0xed46 OP2 IM0,
0xed56 OP2 IM1,
0xed5e OP2 IM2,
0xed44 OP2 NEG,
0xed4d OP2 RETI,
( n -- )
: OP2n
CREATE C,
DOES>
C@ A, A,
;
0xd3 OP2n OUTnA,
0xdb OP2n INAn,
0xc6 OP2n ADDn,
0xe6 OP2n ANDn,
0xf6 OP2n Orn,
0xd6 OP2n SUBn,
( r n -- )
: OP2rn
CREATE C,
DOES>
C@ ( r n op )
ROT ( n op r )
<<3 ( n op r<<3 )
OR A, A,
;
0x06 OP2rn LDrn,
( b r -- )
: OP2br
CREATE C,
DOES>
0xcb A,
C@ ( b r op )
ROT ( r op b )
<<3 ( r op b<<3 )
OR OR A,
;
0xc0 OP2br SETbr,
0x80 OP2br RESbr,
0x40 OP2br BITbr,
( bitwise rotation ops have a similar sig )
( r -- )
: OProt
CREATE C,
DOES>
0xcb A,
C@ ( r op )
OR A,
;
0x10 OProt RLr,
0x00 OProt RLCr,
0x18 OProt RRr,
0x08 OProt RRCr,
0x20 OProt SLAr,
0x38 OProt SRLr,
( cell contains both bytes. MSB is spit as-is, LSB is ORed with r )
( r -- )
: OP2r
CREATE ,
DOES>
@ SPLITB SWAP ( r lsb msb )
A, ( r lsb )
SWAP <<3 ( lsb r<<3 )
OR A,
;
0xed41 OP2r OUT(C)r,
0xed40 OP2r INr(C),
( ss -- )
: OP2ss
CREATE C,
DOES>
0xed A,
C@ SWAP ( op ss )
<<4 ( op ss<< 4 )
OR A,
;
0x4a OP2ss ADCHLss,
0x42 OP2ss SBCHLss,
( dd nn -- )
: OP3ddnn
CREATE C,
DOES>
C@ ( dd nn op )
ROT ( nn op dd )
<<4 ( nn op dd<<4 )
OR A,
A,,
;
0x01 OP3ddnn LDddnn,
( nn -- )
: OP3nn
CREATE C,
DOES>
C@ A,
A,,
;
0xcd OP3nn CALLnn,
0xc3 OP3nn JPnn,
0x22 OP3nn LD(nn)HL,
0x2a OP3nn LDHL(nn),
( Specials )
( dd nn -- )
: LDdd(nn),
0xed A,
SWAP <<4 0x4b OR A,
A,,
;
( nn dd -- )
: LD(nn)dd,
0xed A,
<<4 0x43 OR A,
A,,
;
: JP(IX), IX DROP JP(HL), ;
: JP(IY), IY DROP JP(HL), ;
( 26 == next )
: JPNEXT, 26 JPnn, ;
: CODE
( same as CREATE, but with native word )
(entry)
( 23 == nativeWord )
23 C,
;
: ;CODE JPNEXT, ;
( Macros )
( clear carry + SBC )
: SUBHLss, A ORr, SBCHLss, ;
( 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, JRNZ, FJR, ;
: IFNZ, JRZ, FJR, ;
: IFC, JRNC, FJR, ;
: IFNC, JRC, 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

@ -33,24 +33,17 @@ in write protection mode, but I preferred building my own module.
I don't think you need a schematic. It's really simple.
## Using the at28 driver
## Building your stage 4
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:
cat ../stage3.bin ../pre.fs ../../../drv/at28.fs ../run.fs > os.bin
../../../emul/hw/rc2014/classic os.bin
Using the same technique as you used for building your stage 3, you can append
required words to your boot binary. Required units are `forth/adev.fs` and
`drv/at28.fs`.
## Writing contents to the AT28
The driver provides `AT28!` which can be plugged in adev's `A!*`.
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:
cat ../../../drv/at28.fs | ./stripfc | ./exec <tty device>
Then, upload your binary to some place in memory, for example `a000`. To do so,
First, upload your binary to some place in memory, for example `a000`. To do so,
run this from your modern computer:
./upload <tty device> a000 <filename>

View File

@ -1,19 +0,0 @@
TARGETS = os.bin cfsin/helo
BASEDIR = ../../..
ZASM = $(BASEDIR)/emul/zasm/zasm
KERNEL = $(BASEDIR)/kernel
APPS = $(BASEDIR)/apps
CFSPACK = $(BASEDIR)/tools/cfspack/cfspack
.PHONY: all
all: $(TARGETS) sdcard.cfs
os.bin: glue.asm
cfsin/helo: helo.asm
$(TARGETS):
$(ZASM) $(KERNEL) $(APPS) < glue.asm > $@
$(CFSPACK):
make -C $(BASEDIR)/tools/cfspack
sdcard.cfs: cfsin $(CFSPACK)
$(CFSPACK) cfsin > $@

View File

@ -8,7 +8,7 @@ You can't really keep pins high and low on an IO line. You need some kind of
intermediary between z80 IOs and SPI.
There are many ways to achieve this. This recipe explains how to build your own
hacked off SPI relay for the RC2014. It can then be used with `sdc.asm` to
hacked off SPI relay for the RC2014. It can then be used with `sdc.fs` to
drive a SD card.
## Goal
@ -18,10 +18,8 @@ design.
## Gathering parts
* A RC2014 with Collapse OS with these features:
* shell
* blockdev
* sdc
* A RC2014 Classic
* `stage3.bin` from the base recipe
* A MicroSD breakout board. I use Adafruit's.
* A proto board + header pins with 39 positions so we can make a RC2014 card.
* Diodes, resistors and stuff
@ -34,7 +32,7 @@ design.
## Building the SPI relay
The [schematic][schematic] supplied with this recipe works well with `sdc.asm`.
The [schematic][schematic] supplied with this recipe works well with `sdc.fs`.
Of course, it's not the only possible design that works, but I think it's one
of the most straighforwards.
@ -71,95 +69,41 @@ matter. However, it *does* matter for the `SELECT` line, so I don't follow my
own schematic with regards to the `M1` and `A2` lines and use two inverters
instead.
## Building the kernel
## Building your stage 4
To be able to work with your SPI relay and communicate with the card, you
should have [glue code that looks like this](glue.asm).
Using the same technique as you used for building your stage 3, you can append
required words to your boot binary. Required units are `forth/blk.fs` and
`drv/sdc.fs`. You also need `drv/sdc.z80` but to save you the troubles of
rebuilding from stage 1 for this recipe, we took the liberty of already having
included it in the base recipe.
Initially, when you don't know if things work well yet, you should comment out
the block creation part.
## Testing in the emulator
## Reading from the SD card
The RC2014 emulator includes SDC emulation. You can attach a SD card image to
it by invoking it with a second argument:
The first thing we'll do is fill the SD card's first 12 bytes with "Hello
World!":
../../../emul/hw/rc2014/classic stage4.bin ../../../emul/blkfs
echo "Hello World!" > /dev/sdX
You will then run with a SD card having the contents from `/blk`.
Then, insert your SD card in your SPI relay and boot the RC2014.
## Usage
Run the `sdci` command which will initialize the card. The blockdev 0 is
already selected at initialization, but you could, to be sure, run `bsel 0` to
select the first blockdev, which is configured to be the sd card.
First, the SD card needs to be initialized
Set your memory pointer to somewhere you can write to with `mptr 9000` and then
you're ready to load your contents with `load d` (load the 13 bytes that you
wrote to your sd card earlier. You can then `peek d` and see that your
"Hello World!\n" got loaded in memory!
SDC$
## Mounting a filesystem from the SD card
If there is no error message, we're fine. Then, we need to hook `BLK@*` and
`BLK!*` into the SDC driver:
The Makefile compiles `helo.asm` in `cfsin` and then packs `cfsin` into a CFS
filesystem into the `sdcard.cfs` file. That can be mounted by Collapse OS!
' SDC@ BLK@* !
' SDC! BLK!* !
$ cat sdcard.cfs > /dev/sdX
And thats it! You have full access to disk block mechanism:
Then, you insert your SD card in your SPI relay and go:
102 LOAD
BROWSE
Collapse OS
> sdci
> fson
> fls
helo
hello.txt
> helo
Hello!
>
The `helo` command is a bit magical and is due to the hook implemented in
`pgm.asm`: when an unknown command is typed, it looks in the currently mounted
filesystem for a file with the same name. If it finds it, it loads it in memory
at a predefined place (in our case, `0x9000`) and executes it.
Now let that sink in for a minute. You've just mounted a filesystem on a SD
card, loaded a file from it in memory and executed that file, all that on a
kernel that weights less than 3 kilobytes!
## Writing to a file in the SD card
Now what we're going to do is to write back to a file on the SD card. From a
system with the SD card initialized and the FS mounted, do:
> fopn 0 hello.txt
> bsel 1
> mptr 9000
9000
> load d
> peek d
48656C6C6F20576F726C64210A
Now that we have our "Hello World!\n" loaded in memory, let's modify it and make
it start with "XXX" and save it to the file. `sdcf` flushes the current SD card
buffer to the card. It's automatically ran whenever we change sector during a
read/write/seek, but was can also explicitly call it with `sdcf`.
> poke 3
[type "XXX"]
> peek d
5858586C6F20576F726C64210A
> seek 00 0000
0000
> save d
> sdcf
The new "XXXlo World!\n" is now written to the card, at its proper place in CFS!
You can verify this by pulling out the card (no need to unmount it from Collapse
OS, but if you insert it again, you'll need to run `sdci` again), insert it in
your modern system and run:
$ head -c 512 /dev/sdX | xxd
You'll see your "XXXlo World!\n" somewhere, normally at offset `0x120`!
(at this moment, the driver is a bit slow though...)
[schematic]: spirelay/spirelay.pdf
[inspiration]: https://www.ecstaticlyrics.com/electronics/SPI/fast_z80_interface.html

View File

@ -1,114 +0,0 @@
; classic RC2014 setup (8K ROM + 32K RAM) and a stock Serial I/O module
; The RAM module is selected on A15, so it has the range 0x8000-0xffff
.equ RAMSTART 0x8000
.equ RAMEND 0xffff
.equ ACIA_CTL 0x80 ; Control and status. RS off.
.equ ACIA_IO 0x81 ; Transmit. RS on.
.equ USER_CODE 0xa000
jp init ; 3 bytes
; *** Jump Table ***
jp printstr
jp sdcWaitResp
jp sdcCmd
jp sdcCmdR1
jp sdcCmdR7
jp sdcSendRecv
; interrupt hook
.fill 0x38-$
jp aciaInt
.inc "err.h"
.inc "ascii.h"
.inc "blkdev.h"
.inc "fs.h"
.inc "core.asm"
.inc "str.asm"
.equ ACIA_RAMSTART RAMSTART
.inc "acia.asm"
.equ BLOCKDEV_RAMSTART ACIA_RAMEND
.equ BLOCKDEV_COUNT 2
.inc "blockdev.asm"
; List of devices
.dw sdcGetB, sdcPutB
.dw blk2GetB, blk2PutB
.equ STDIO_RAMSTART BLOCKDEV_RAMEND
.equ STDIO_GETC aciaGetC
.equ STDIO_PUTC aciaPutC
.inc "stdio.asm"
.equ FS_RAMSTART STDIO_RAMEND
.equ FS_HANDLE_COUNT 1
.inc "fs.asm"
; *** BASIC ***
; RAM space used in different routines for short term processing.
.equ SCRATCHPAD_SIZE STDIO_BUFSIZE
.equ SCRATCHPAD FS_RAMEND
.inc "lib/util.asm"
.inc "lib/ari.asm"
.inc "lib/parse.asm"
.inc "lib/fmt.asm"
.equ EXPR_PARSE parseLiteralOrVar
.inc "lib/expr.asm"
.inc "basic/util.asm"
.inc "basic/parse.asm"
.inc "basic/tok.asm"
.equ VAR_RAMSTART SCRATCHPAD+SCRATCHPAD_SIZE
.inc "basic/var.asm"
.equ BUF_RAMSTART VAR_RAMEND
.inc "basic/buf.asm"
.inc "basic/blk.asm"
.inc "basic/sdc.asm"
.equ BFS_RAMSTART BUF_RAMEND
.inc "basic/fs.asm"
.equ BAS_RAMSTART BFS_RAMEND
.inc "basic/main.asm"
.equ SDC_RAMSTART BAS_RAMEND
.equ SDC_PORT_CSHIGH 6
.equ SDC_PORT_CSLOW 5
.equ SDC_PORT_SPI 4
.inc "sdc.asm"
init:
di
ld sp, RAMEND
im 1
call aciaInit
call fsInit
call basInit
ld hl, basFindCmdExtra
ld (BAS_FINDHOOK), hl
xor a
ld de, BLOCKDEV_SEL
call blkSel
ei
jp basStart
basFindCmdExtra:
ld hl, basFSCmds
call basFindCmd
ret z
ld hl, basBLKCmds
call basFindCmd
ret z
ld hl, basSDCCmds
jp basFindCmd
; *** blkdev 2: file handle 0 ***
blk2GetB:
ld ix, FS_HANDLES
jp fsGetB
blk2PutB:
ld ix, FS_HANDLES
jp fsPutB

View File

@ -1,12 +0,0 @@
; prints "Hello!" on screen
.equ printstr 0x03
.org 0x9000
ld hl, sHello
call printstr
xor a ; success
ret
sHello:
.db "Hello!", 0x0d, 0x0a, 0