Close
0%
0%

eForth/z80 modifications

A more hackable eForth/z80

Similar projects worth following
I made some small changes to eForth for the z80 to be buildable with asz80/aslink and easier to modify

Inspired by the availability of eForth on the STM8 MCU, I started looking into Forth, particularly the Threaded Interpreter implementation eForth. I found the Github repository for eForth and ported the Z80 eForth to build with asxxxx with a view to possible use in my project.

Hardware

You will almost certainly not be able to use the software as is. That would require you to have exactly the same peripherals as the original author. At the moment I haven't worked out from the register writes what chips were used for the CTC, PIO and SIO. My guess would be Zilog family peripherals, but who knows, maybe an Intel 8255 for the PIO.

You will need to write chip specific initialisation routines and I/O words, as well as interrupt handlers for the SIO. That is, if you choose to use interrupt handling for SIO. Probably a good idea, given how slow an interpreted (albeit threaded interpreted) language will be on a slow micro. But maybe you are running this on a modern fast Z80 descendant. Also you may want to modify the memory layout.

If you get something to work on your platform, I encourage you to put it in a subdirectory named after your platform, and submit a git pull request. Please also write something about your hardware configuration to help others reuse your code. I will leave the original sources as is, except for fixes for bugs which are discovered.

Platform also means any simulators if you get this eForth to work under one.

  • Got eForth/Z80 working under CP/M

    Ken Yap08/15/2019 at 01:47 2 comments

    Woo hoo. I have it working under the RunCPM emulator.

    A>EFZ80
    
    eForth v1.00
    WORDS
     COLD 'BOOT hi VER WORDS SEE .ID >NAME ?CSP !CSP .S DUMP dm+ _TYPE VARIABLE CREATE USER IMMEDIATE : call, ] ; OVERT $COMPILE $,n ?UNIQUE ." $" ABORT" WHILE ELSE AFT THEN REPEAT AHEAD IF AGAIN UNTIL NEXT BEGIN FOR RECURSE $," LITERAL COMPILE [COMPILE] , ALLOT ' QUIT CONSOLE I/O HAND FILE xio PRESET EVAL ?STACK .OK [ $INTERPRET abort" ABORT NULL$ THROW CATCH QUERY EXPECT accept kTAP TAP ^H NAME? find SAME? NAME> WORD TOKEN CHAR \ ( .( PARSE parse ? . U. U.R .R ."| $"| do$ CR TYPE SPACES SPACE PACE NUF? EMIT KEY ?KEY NUMBER? DIGIT? DECIMAL HEX str #> SIGN #S # HOLD <# EXTRACT DIGIT PACK$ -TRAILING FILL CMOVE @EXECUTE TIB PAD HERE COUNT 2@ 2! +! PICK DEPTH >CHAR BL ALIGNED CELLS CELL- CELL+ */ */MOD M* * UM* / MOD /MOD M/MOD UM/MOD WITHIN MIN MAX < U< = ABS - DNEGATE NEGATE NOT + 2DUP 2DROP ROT ?DUP FORTH forth LAST NP CP CURRENT CONTEXT HANDLER HLD 'NUMBER 'EVAL CSP #TIB >IN SPAN tmp BASE 'PROMPT 'ECHO 'TAP 'EXPECT 'EMIT '?KEY RP0 SP0 doUSER UP doVAR BYE TX! ?RX !IO UM+ XOR OR AND 0< OVER SWAP DROP DUP SP! SP@ RP! RP@ R> R@ >R C@ C! @ ! branch ?branch next EXECUTE EXIT doLIST doLIT ok
    65 EMITA ok
    BYE
    RunCPM Version 3.7 (CP/M 2.2 60K)
    
    A>

    It also can be conditionally assembled for the 8080 by changing one equate.

    Will check code into GitHub in a moment.

    Now to look for some eForth tests. 

  • Will it work on an 8080/8085?

    Ken Yap08/10/2019 at 10:51 6 comments

    One of my motivations is to also get it to work on my 8085 SBC. It turns out to be fairly straightforward.

    I have a hacked asz80 that flags instructions that are not in the 8080 subset. Here is the output of a grep '^[a-z]' efz80.lst

    a  0105 ED 79         [12]   11         OUT (C), A
    a  0109 ED 79         [12]   13         OUT (C), A
    a  010D ED 79         [12]   15         OUT (C), A
    a  0111 ED 79         [12]   17         OUT (C), A
    a  0117 ED 79         [12]   21         OUT (C), A
    a  011B ED 79         [12]   23         OUT (C), A
    a  011F ED 79         [12]   25         OUT (C), A
    a  0123 ED 79         [12]   27         OUT (C), A
    a  0129 ED 79         [12]   31         OUT (C), A
    a  012F ED 79         [12]   35         OUT (C), A
    a  0131 ED 5E         [ 8]   39         IM 2
    a  014A ED 79         [12]   58         OUT (C), A
    a  0000 D9            [ 4]    8         EXX                     ;4t
    a  0003 ED 47         [ 9]   10         LD I, A
    a  001E ED 79         [12]   26         OUT (C), A
    a  0022 ED 79         [12]   28         OUT (C), A
    a  0028 ED 79         [12]   34         OUT (C), A
    a  002C ED 79         [12]   36         OUT (C), A
    a  0030 ED 79         [12]   38         OUT (C), A
    a  0034 ED 79         [12]   40         OUT (C), A
    a  003A ED 79         [12]   44         OUT (C), A
    a  003E ED 79         [12]   46         OUT (C), A
    a  0042 ED 79         [12]   48         OUT (C), A
    a  0046 ED 79         [12]   50         OUT (C), A
    a  004C ED 79         [12]   54         OUT (C), A
    a  0052 ED 79         [12]   58         OUT (C), A
    a  0058 ED 79         [12]   64         OUT (C), A
    a  005C ED 79         [12]   66         OUT (C), A
    a  0060 ED 79         [12]   68         OUT (C), A
    a  0064 ED 79         [12]   71         OUT (C), A
    a  0068 ED 79         [12]   73         OUT (C), A
    a  006C ED 79         [12]   76         OUT (C), A
    a  0070 ED 79         [12]   78         OUT (C), A
    a  0074 ED 79         [12]   81         OUT (C), A
    a  0078 ED 79         [12]   83         OUT (C), A
    a  007C ED 79         [12]   85         OUT (C), A
    a  0080 ED 79         [12]   88         OUT (C), A
    a  0086 D9            [ 4]   98         EXX                     ;4t
    a  0000 D9            [ 4]    7         EXX             ; 4t
    a  0004 ED 79         [12]   11         OUT (C), A      ;12t
    a  0006 D9            [ 4]   12         EXX             ; 4t
    a  0000 D9            [ 4]   21         EXX             ; 4t
    a  0002 ED 78         [12]   23         IN A, (C)       ;12t
    a  0008 D9            [ 4]   27         EXX             ; 4t
    a  0035 ED 4D         [14]   55         RETI                    ;14t
    a  003D ED 79         [12]   63         OUT (C), A              ; reset
    a  0043 ED 79         [12]   66         OUT (C), A
    a  0048 ED 4D         [14]   70         RETI
    

    All the OUT (C), A and IN A,(C) instrutions need to be replaced by OUT (port),A and IN A,(port). Most of them happen in the device initialisation routines, where C is loaded with a constant, lots of OUT instructions are done, then C is changed, and so on. In the port output and input routines, a modifiable code fragment will need to be provided so that any arbitrary port can be selected, as the 8080 doesn't support variable ports. This fragment have to be constructed in RAM, not ROM.

    IM 2 (Interrupt Mode 2), LD I,A, and RETI don't exist on the 8080 so interrupt routines would have to use 8080 methods.

    EXX would have to be replaced by pushes and pops of affected registers at the cost of execution time.

    It's interesting that the author didn't use IX, IY, or the other extended instructions.

    But I won't try this now. I have to find a Z80 simulator to check that eForth works, then I can try a modified 8080 image in the same simulator.

  • Got it to build with asz80 and aslink only

    Ken Yap08/09/2019 at 14:31 2 comments

    Woo hoo, I have now sources that build with only asz80 and aslink and generate an identical binary to the distributed sources. Now cleaned up, checked in, and released.

    Thomas you were right. Comma, semicolon, space and apostrophe are problem characters for macros. The trick is to use octal escapes for those characters.

    Now I'll have to find out how I can implement console input and output in a simulator and write I/O words for them to play with this version.

  • End of phase 1

    Ken Yap08/09/2019 at 09:11 0 comments

    Ok, I've checked in the latest notes to Github and I'm calling this phase closed. Nearly all the hand inserted code has been converted back to Z80 assembler lines. The source is buildable with a few expected discrepancies to the original binary.

    In the next phase I will start with the sources so far in a fresh directory and try to get it to build with asz80.

  • Who to believe?

    Ken Yap08/08/2019 at 12:46 0 comments

    Here is a Forth word that I was converting to Z80 assembler. This is the original unmodified assembler.

    ;   next        ( -- )
    ;               Run time code for the single index loop.
    ;               : next ( -- ) \ hilevel model
    ;                 r> r> dup if 1 - >r @ >r exit then drop cell+ >r ;
    
                    $CODE   COMPO+4,'next',DONXT
    DB   2Ah, 0Ch,0FEh     ;       LD  HL, (RP) ;16t
    DB   7Eh               ;       LD  A, (HL)  ; 7t
    DB  0B7h               ;       OR  A        ; 4t
    DB   20h, 0Eh          ;       JR  NZ, DECLOW;12/7t a fast dec is ok, only failed every 255 time
                           ;                    ; low byte 0
    DB   23h               ;       INC HL       ; 6t
    DB   7Eh               ;       LD  A, (HL)  ; 7t
    DB  0B7h               ;       OR  A        ; 4t
    DB   20h, 0Ch          ;       JR  NZ, DECHILO;12/7t Hi-byte no-zero, it is also a re-loop case
                           ;zero bound now .. .
    DB   23h               ;       INC HL       ; 6tdiscard the loop count on R-stack
    DB   22h, 0Ch,0FEh     ;       LD  (RP), HL ;16t
    DB   03h               ;       INC BC       ; 6t\ IP slip over the re-loop-addr
    DB   03h               ;       INC BC       ; 6t
    DB  0C3h
    DW  NextStep           ;       JP  NextStep ;10t loop is over
                           ;                    ; 98t==(10MHz)9.8usec
    DB   35h              ;DECHILO:DEC (HL)     ;11t hi-byte
    DB   2Bh               ;       DEC HL       ; 6t back to low byte
    DB   35h               ;DECLOW:DEC (HL)     ;11t low byte non-zero, just dec it and re-loop
    DB   69h               ;       LD  L, C     ; 4t get loop-start-adr to IP and keep stepping
    DB   60h               ;       LD  H, B     ; 4t
    DB   4Eh               ;       LD  C, (HL)  ; 7t
    DB   23h               ;       INC HL       ; 6t
    DB   46h               ;       LD  B, (HL)  ; 7t
    DB  0C3h
    DW  NextStep           ;       JP  NextStep ;10t
                                  ; low byte dec:    88t==(10MHz)8.8usec
                                  ; lo&Hi byte dec: 134t==(10MHz)13.4usec
    

    Now if I calculate the destinations for the the two JRs from the offsets, they don't land where the assembler code claim they should.

    From my understanding of the intention of the code, decrement a 16 bit value and leave the loop on 0, the assembler code in the comments is correct and the offsets in the DBs are wrong. So how did this this word ever work at all? I think the author never tested this word.

    I'm going to go with the assembler code and note that a discrepancy with the distributed binary is expected here.

  • Using a Z80 assembler

    Ken Yap08/07/2019 at 14:01 8 comments

    One of my goals is to be able to write platform-specific routines in native Z80 assembler instead of hand assembly or separate assembly, then inserting the bytes as DB lines in the code.

    Before I show how it's accomplished, let me present the Z80 source for a couple of Forth words:

    ;   PC!         ( uc p --  )
    ;               output uc to IO port p
    
    ;!                $CODE   3,'PC!',PCSTO
    EXX           ; 4t
    POP BC        ;10t
    POP DE        ;10t
    LD  A, E      ; 4t
    OUT (C), A    ;12t
    EXX           ; 4t
    ;!DB 0C3h
    ;!DW NextStep          ;       JP  NextStep  ;10t
                           ;                     ;54t==(10MHz)5.4usec
    
    ;   PC@         ( p -- uc )
    ;               input uc from IO port p
    
    ;!                $CODE   3,'PC@',PCAT
    EXX           ; 4t
    POP BC        ;10t
    IN  A, (C)    ;12t
    LD  E, A      ; 4t
    LD  D, 0      ; 7t
    PUSH DE       ;11t
    EXX           ; 4t
    ;!DB 0C3h
    ;!DW NextStep          ;       JP  NextStep  ;10t
                           ;                     ;62t==(10MHz)6.2usec

     Now the transformed source:

    ;   PC!         ( uc p --  )
    ;               output uc to IO port p
    
                    $CODE   3,'PC!',PCSTO
    DB 0D9h         ;EXX           ; 4t
    DB 0C1h         ;POP BC        ;10t
    DB 0D1h         ;POP DE        ;10t
    DB 7Bh          ;LD  A, E      ; 4t
    DB 0EDh,79h     ;OUT (C), A    ;12t
    DB 0D9h         ;EXX           ; 4t
    DB 0C3h
    DW NextStep          ;       JP  NextStep  ;10t
                           ;                     ;54t==(10MHz)5.4usec
    
    ;   PC@         ( p -- uc )
    ;               input uc from IO port p
    
                    $CODE   3,'PC@',PCAT
    DB 0D9h         ;EXX           ; 4t
    DB 0C1h         ;POP BC        ;10t
    DB 0EDh,78h     ;IN  A, (C)    ;12t
    DB 5Fh          ;LD  E, A      ; 4t
    DB 16h,00h      ;LD  D, 0      ; 7t
    DB 0D5h         ;PUSH DE       ;11t
    DB 0D9h         ;EXX           ; 4t
    DB 0C3h
    DW NextStep          ;       JP  NextStep  ;10t
                           ;                     ;62t==(10MHz)6.2usec
    

    The transformation is done by a Python script, lsttoinc.py. It follow several rules:

    • The .z80 file should be valid asz80 assembler input
    • The script works in the .lst listing output of the assembly with asz80
    • Lines starting with ;! are passed through with the ;! removed and go through to JWASM
    • Lines starting with ; only are passed through unchanged as comments
    • If opcodes resulted from a line, they are turned into DB declarations for JWASM and output with the original line as a comment. Otherwise they are inserted as comments.

    However this scheme has its limitations, witness this piece of code:

    ;   ?RX         ( -- c T | F )
    ;               Return input character and true, or a false if no input.
    
    ;!                $CODE   3,'?RX',QRX
    ;!DB    21h, 14h,0FEh    ;       LD  HL, SiobRxQout  ;16t
    LD  A, (HL)         ; 7t
    ;!DB    21h, 12h,0FEh    ;       LD  HL, SiobRxQin   ;16t
    CP  (HL)            ; 7t
    JR  NZ,.+9          ; 7t/12t (NZ jump=12t) ptr <>, get the char
                        ; can't use label because JP NextStep not accounted for
    LD  HL, 0           ;10t   False flag
    PUSH HL             ;11t
    ;!DB   0C3h
    ;!DW   NextStep          ;       JP  NextStep        ;10t ==> False Timing:84t==(10MHz)8.4usec
                           ;                           ;
    LD  E, (HL)         ; 7t  get this ptr LOW
    INC HL              ; 6t
    LD  D, (HL)         ; 7t  get this ptr HI
    EX  DE,HL           ; 4t  let HL point the char
    LD  E, (HL)         ; 7t  get the char
    LD  D, 0            ; 7t  high byte =0
    PUSH DE             ;11t
    INC L               ; 4t  ptr+1, a 256 byte Ring queue
    ;!DB    22h, 12h,0FEh    ;       LD  (SiobRxQin),HL  ;16t
    LD  HL,0xFFFF       ;10t
    PUSH HL             ;11t
    ;!DB   0C3h
    ;!DW   NextStep          ;       JP  NextStep        ;10t ==> TRUE case: 100t(10MHz)10.0usec

    And the transformed code:

    ;   ?RX         ( -- c T | F )
    ;               Return input character and true, or a false if no input.
    
                    $CODE   3,'?RX',QRX
    DB    21h, 14h,0FEh    ;       LD  HL, SiobRxQout  ;16t
    DB 7Eh          ;LD  A, (HL)         ; 7t
    DB    21h, 12h,0FEh    ;       LD  HL, SiobRxQin   ;16t
    DB 0BEh         ;CP  (HL)            ; 7t
    DB 20h,07h      ;JR  NZ,.+9          ; 7t/12t (NZ jump=12t) ptr <>, get the char
                        ; can't use label because JP NextStep not accounted for
    DB 21h,00h,00h  ;LD  HL, 0           ;10t   False flag
    DB 0E5h         ;PUSH HL             ;11t
    DB   0C3h
    DW   NextStep          ;       JP  NextStep        ;10t ==> False Timing:84t==(10MHz)8.4usec
                           ;                           ;
    DB 5Eh          ;LD  E, (HL)         ; 7t  get this ptr LOW
    DB 23h          ;INC HL              ; 6t
    DB 56h          ;LD  D, (HL)         ; 7t  get this ptr HI
    DB 0EBh         ;EX  DE,HL           ; 4t  let HL point the char
    DB 5Eh          ;LD  E, (HL)         ; 7t  get the char
    DB 16h,00h      ;LD  D, 0            ; 7t  high byte =0
    DB 0D5h         ;PUSH DE             ;11t
    DB 2Ch          ;INC L               ; 4t  ptr+1, a 256 byte Ring queue
    DB    22h, 12h,0FEh    ;       LD  (SiobRxQin),HL  ;16t
    DB 21h,0FFh,0FFh        ;LD  HL,0xFFFF       ;10t
    DB 0E5h         ;PUSH HL             ;11t
    DB   0C3h
    DW   NextStep          ;       JP  NextStep        ;10t ==> TRUE case: 100t(10MHz)10.0usec

    The instructions involving locations like SiobRxQout are inserted as DBs. To insert them as Z80 instructions, the symbols must be known to the Z80 assembler. We could declare them in an include file. If they are also used in the JWASM code then there is duplication and you have to...

    Read more »

  • Modifications to eForth/z80

    Ken Yap08/05/2019 at 11:27 0 comments

    The first thing I tried to do was to build the Z80 eForth written by Ken Chen. This requires MASM, which is exploited for its macro facility. I wasted some time modifying the assembly code for asz80, from asxxxx. Then I realised I didn't really need a Z80 assembler at this point, merely MASM or a workalike. I found JWASM, which is very close to MASM, is open source, and runs under other OSes, in particular Linux.

    I've pushed my small hack to Github, and my README.md partly rendered below explains everything. At this point I haven't written anything original but now the source should be easier to modify, especially the hardware interface part.

    Z80 eForth built with JWASM

    This directory contains an alternate version of the Z80 eForth. They are the files with lower case names.

    The single ASM file has been slightly modified to build with JWASM, an open source derivative of Watcom ASM that runs under several operating systems, instead of MASM. In the process changes were made to allow the assembler source to complete without errors and make it easier to modify:

    • Lines with DW should not have colon after the label, if any.
    • A few symbols which clash with mnemonics or keywords have been renamed: CMOV, STR, FOR
    • Various pieces of inline Z80 code have been moved into their own files and replaced by INCLUDE <file> in the top level file.

    The modified file builds with JWASM with this command:

    jwasm -Fl -bin efz80.z80
    
    

    No LINK required. The result efz80.BIN is byte for byte identical with EFZ80.COM except for a few locations where a single padding byte due to the EVEN directive resulted in a padding byte of 90H (8086 NOP) for JWASM and 00H for MASM.

    Regarding the last bullet point, have you wondered how an 8086 assembler can handle Z80 code? It turns out that all the Z80 code is hand assembled or assembled elsewhere and the opcodes inserted into the source as DBs. MASM or JWASM are used mainly for the MACRO facility. And the ability to generate binaries from source of course. Hence my interest in making the code easier to modify, particularly if you have different serial interface hardware.

View all 7 project logs

Enjoy this project?

Share

Discussions

Similar Projects

Does this project spark your interest?

Become a member to follow this project and never miss any updates