3 ; runtimelib FOR 8080 using DE as the parameter stack pointer
4 ; Joel Matthew Rees September 2020
6 ; Borrowing some concepts from fig-Forth.
8 ; In fact, I was never all that good with 8080 code,
9 ; and it has been almost 40 years, so ...
10 ; don't expect it to work without fixing it.
11 ; Patterned after 6800 libs.
13 ; ------------------------------------LICENSE-------------------------------------
15 ; Copyright (c) 2020 Joel Matthew Rees
17 ; Permission is hereby granted, free of charge, to any person obtaining a copy
18 ; of this software and associated documentation files (the "Software"), to deal
19 ; in the Software without restriction, including without limitation the rights
20 ; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
21 ; copies of the Software, and to permit persons to whom the Software is
22 ; furnished to do so, subject to the following conditions:
24 ; The above copyright notice and this permission notice shall be included in
25 ; all copies or substantial portions of the Software.
27 ; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
28 ; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
29 ; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
30 ; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
31 ; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
32 ; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
35 ; --------------------------------END-OF-LICENSE----------------------------------
39 ; These must be edited for target runtime:
41 ; Necessary here for fake forward referencing:
43 ; Watch label width, 5 significant characters on original 8080
45 BYTESZ: EQU 8 ; bit count in byte
47 ADRWDSZ: EQU 2 ; bytes per address word
49 ; If at all possible, a CELL should be able to contain an address.
50 ; Otherwise, fetch and store become oddities.
52 CELLBSZ: EQU (CELLSZ*BYTESZ) ; bit count in CELL
54 DBLBSZ: EQU (DBLSZ*BYTESZ) ; bit count in DOUBLE
56 GAPCT: EQU 2 ; address words for the gaps
57 ALLOGAP: EQU (GAPCT*CELLSZ) ; For crude checks, gaps always zero.
60 ; Declare initial Return Stack (flow-of-control stack):
61 RSTKBND: EQU 08000H ; Bound: one beyond
62 RSTKINI: EQU (RSTKBND-1) ; Init: 8080? next available byte on 6800
63 RSTKSZ: EQU (62*CELLSZ) ; Size: Safe for most purposes.
64 RSTKLIM: EQU (RSTKBND-RSTKSZ) ; Limit: Last useable
65 ; Kibitzing -- CPUs really should have automatic stack bounds checking.
66 ; Don't forget gaps for CPUs that don't automatically check.
67 ; Crude guard rails is better than none?
69 ; Declare initial Parameter Stack (data stack):
70 SSTKBND: EQU (RSTKLIM-ALLOGAP)
71 SSTKINI: EQU (SSTKBND-CELLSZ) ; 8080? post-dec on 6800, but on address word boundary.
72 SSTKSZ: EQU (126*CELLSZ) ; Size: CELL addressing, small stacks.
73 SSTKLIM: EQU (SSTKBND-SSTKSZ)
75 ; The paramater stack and heap at opposite ends of the same region
78 ; The initial per-user allocation heap:
79 UPGBND: EQU (SSTKLIM-ALLOGAP)
80 UPGSZ: EQU (64*CELLSZ) ; This will need adjusting in many cases.
81 UPGBASE: EQU (UPGBND-UPGSZ)
85 ; ORG directives in older assemblers can get tangled up
86 ; if they are convoluted.
87 ; Keep the ORGs in ascending order.
92 ; Internal registers --
93 ; (When switching contexts, these must be saved and restored.):
95 ; RP: EQU SP ; ?? ; the return/flow-of-control stack pointer is 8080 SP
96 ; PSP: EQU DE ; the parameter/data stack pointer (Forth SP)
97 ; Maybe we can put PSP in HL?
98 UP: DS ADRWDSZ ; pointer to the per-task heap -- BC?
99 ; TEMP: DS 2*CELLSZ ; for general math
100 ; GCOUNT: DS CELLSZ ; general counter
101 ; IXDEST: DS ADRWDSZ ; destination index pointer
102 ; IXSRC: DS ADRWDSZ ; source index pointer
103 ; IXTERM: DS ADRWDSZ ; terminator for moves
107 ; Using DE for the parameter stack pointer means that
108 ; interrupts must *not* use the interrupted routine's parameter stack.
110 ; If we used DE as the stack pointer, it could remain constant while HL indexed the stack.
111 ; But since we are indexing the stack with the stack pointer itself,
112 ; the stack pointer will often be out of position to protect the active contents.
114 ; We could gamble and guess that six bytes or sixteen would be enough to avoid active stack,
115 ; but it will be better to just have the interrupt routines set their own parameter stacks.
117 ; Note that using DE as the stack pointer would not mean we could use XCHG
118 ; to save the current pointer and index the stack, for reasons evident from the above.
130 ; _LOWORD_ Subtract byte in B from cell pointed to by X:
131 ; Not really all that useful when you can CLRA and SUBCELL?
140 ; _LOWORD_ Add byte in B to cell pointed to by X:
141 ; Not really all that useful when you can CLRA and ADDCELL?
158 ; Or doing as subroutine would would add 4 bytes and about ten cycles?
159 ; How useful would it be?
162 LOGOP: MACRO OP0 ; hides a lot of cycles and object code
163 XCHG ; DE with HL, make PSP accessible to OP0
167 OP0 M ; less significant byte on 8080
173 OP0 M ; more significant byte on 8080
180 ; _WORD_ Take logical AND of top two on stack ( n1 n2 --- n3 ):
183 ; _WORD_ Take logical OR of top two on stack ( n1 n2 --- n3 ):
186 ; _WORD_ Take logical OR of top two on stack ( n1 n2 --- n3 ):
190 ; _WORD_ Add top two cells on stack ( n1 n2 --- sum ):
191 ADD: STC ; clear carry (LSB first required!)
192 CMC ; So we don't have to rewrite all of LOGOP
193 LOGOP ADC ; to use ADD first and ADC second.
195 ; _WORD_ Subtract top cell on stack from second ( n1 n2 --- difference(n1-n2) ):
196 SUB: STC ; clear carry (See ADD.)
201 ; _WORD_ Fetch byte only pointed to by top cell on stack ( adr --- b(at adr) ):
202 ; (Refer to Forth's C@, but byte is not character!)
209 ; STAX DE ; more significant byte
215 BFETCH: XCHG ; Make PSP available.
221 MOV M,C ; Clear high byte.
226 ; _WORD_ Store low byte of cell at 2nd at address on top of stack, deallocate both ( n adr --- ):
227 ; (Refer to Forth's C!, but byte is not character!)
229 MOV C,M ; address low byte
231 MOV B,M ; address high byte
233 MOV A,M ; ignore high byte.
240 ; ASSERT CELLSZ == ADRWDSZ
241 ; _WORD_ Fetch cell pointed to by top cell on stack ( adr --- n(at adr) ):
253 ; _WORD_ Store cell at 2nd at address on top of stack, deallocate both ( n adr --- ):
261 ; Rob point to deallocate 2 on stack.
271 ; _WORD_ Unsigned multiply of top two on stack ( n1 n2 --- uproduct(n1*n2) ):
274 LDAA #CELLBSZ ; bits/cell
278 USTARL: ROR CELLSZ,X ; shift multiplier
286 RORB ; shift result in
288 USTARX: STAB 1,X ; store more significant 16 bits
292 ; _WORD_ swap top two cells on stack ( n1 n2 --- n2 n1 ):
306 ; _WORD_ Unsigned divide of unsigned double dividend in 2nd:3rd on stack by top unsigned divisor
307 ; ( ud u --- uremainder uquotient )
308 ; Dividend should be range of product of 16 by 16 bit unsigned multiply,
309 ; divisor should be the multiplier or multiplicand:
312 LDAA #CELLBSZ+1 ; one more than bits/cell
314 LDAA CELLSZ,X ; dividend
316 USLLUP: CMPA 0,X ; divisor
317 BHI USLSUB ; Make sure carry from LSB would carry over
320 BCC USLSUB ; CC = HS (High or Same)
321 USLNSUB: CLC ; For shifting
326 USLNEXT: ROL 1+2*CELLSZ,X
333 BRA USLSUB ; Catch the excess.
334 USLX: INX ; Drop high cell.
337 STAA 0,X ; High cell now empty, save remainder.
338 STAB 1,X ; But remainder is now top.
339 BRA SWAPROB ; PSP in X, reverse quotient & remainder.
343 ; _WORD_ Save top cell on stack to return stack ( n --- ) { --- n }:
350 INX ; Not worth robbing code.
355 ; _WORD_ Pop top of return stack to parameter stack ( --- n ) { n --- }:
361 ; _WORD_ Retrieve, do not pop top of return stack to parameter stack ( --- n ) { n --- n }:
375 ; Should true be set as 1 or -1?
376 ; _WORD_ Test top cell on stack for zero ( n --- f(top==0) ):
389 ; _LOWORD_ Duplicate (count in B) bytes on stack:
396 ; _LOWORD_ Move 256 bytes or less going up:
397 ; 0=256, do not enter without pretesting for 0 count!
398 ; source in IXSRC, destination in IXDEST, count in B:
399 ; Overlaps only work if source is higher than dest.
415 ; _LOWORD_ Move 256 bytes or less going down:
416 ; 0=256, do not enter without pretesting for 0 count!
417 ; source in IXSRC, destination in IXDEST, count in B, A is overwritten:
418 ; Overlaps only work if source is higher than dest.
425 CLRA ; 1# 2~ -- BCC takes 2#, 4~; INC IXSRC takes 3# & 6~
431 CLRA ; 1# 2~ -- BCC takes 2#, 4~; INC IXDEST takes 3# & 6~
447 ; _LOWORD_ Set up parameters for MOVE ( src dest count --- )
448 ; Return without copying if count is zero or if 2^15 or more.
449 ; Make sure destination is left in A:B for math.
451 LDX PSP ; get paramaters for move
453 BEQ GMVPRX ; bail now if zero
454 BMI GMVPRX ; also if greater than 32K
457 LDAA CELLSZ,X ; preparing for math
463 RTS ; Back to MOVE that called us.
466 INS ; Drop return to MOVE code.
470 ; _WORD_ Move up to 32K bytes ( src dest count --- ):
471 ; Copies zero when count >= 2^15
472 ; Compare CMOVE in Forth.
475 SUBB CELLSZ+ADRWDSZ+1,X ;
476 SBCA CELLSZ+ADRWDSZ,X
481 ;***** Working in here to make it go both ways.
482 ;***** Also need to check multiply and divide.
491 LDAB GCOUNT+1 ; Get low byte for partial block
492 CLR GCOUNT+1 ; To avoid debugging confusion.
493 BMOVEL: BSR SMOVE ; partial block and full blocks
494 DEC GCOUNT ; count high byte down (blocks)
495 BPL BMOVEL ; This limits the count.
496 ; Rob point to deallocate 3 on stack, as long as CELLSZ == ADRWDSZ.
499 ADDB #(CELLSZ+2*ADRWDSZ) ; 2# 3~
502 INC PSP ; 3# 6~ Unaries have no direct page version. => 11# 20~
506 ; ADDB #(CELLSZ+2*ADRWDSZ) ; 2# 3~
509 ; ADCA #(CELLSZ+2*ADRWDSZ) ; 2# 3~
510 ; STAA PSP ; 2# 4~ => 12# 20~
513 ; LDX PSP ; 6 INXs is around the breakover point in the 6800.
514 ; INX ; 2 + 6 + 2 bytes => 11#
515 ; INX ; 4 + 24 + 5 cycles => 33~
525 ; _WORD_ Execute the address on the stack:
532 JSR 0,X ; For debugging and flattening, no early optimizations.