Files
Chip8_MiSTer/docs/s_chip10.src.txt
Paul Sajna 3c7a2cc758 initial commit
Draws some stuff, but not what it's supposed to
2020-02-04 15:50:55 -08:00

1769 lines
36 KiB
Plaintext

; schip10.s 910515
;
; SUPERCHIP -- a CHIP-8 interpreter for the HP48SX
;
; (C) Copyright 1990 Andreas Gustafsson
;
; Noncommercial distribution allowed, provided that this
; copyright message is preserved, and any modified versions
; are clearly marked as such.
;
; The program makes use of undocumented low-level features of
; the HP48SX calculator, and may or may not cause loss of data,
; excessive battery drainage, and/or damage to the calculator
; hardware. The Author takes no responsibility whatsoever for
; any damage caused by the use of this program.
;
; THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
; IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
;
; Modifications for full screen graphics made by Erik Bryntse 910515.
; Assembled using "home made" assembler, but should work with some
; modifications with most assemblers using Alonzo Gariepys mnemonics.
;
; Register usage:
;
; d0 = general pointing
; d1 = points to chip-8 instruction (physical)
;
; r0 = CHIP-8 I register
; r1 = last time value
; r2 = physical address of virtual zero
; r3 = CHIP-8 PC
;
!CODE ; compiler directive that makes code object of program
; HP48SX ROM locations
; These are for the Revision A ROM, they may need to be changed for
; other revisions.
do_in_c:=#01160 ; perform "in.4 c" instruction
alloc_str:=#05b7d ; allocate string
push_r0:=#0357f ; push r0 as short integer, rest regs, exit
save_regs:=#0679b ; save d0, d1, b, d
rest_regs:=#067d2 ; restore d0, d1, b, d
check_1:=#18abf ; make sure stack isn't empty
; HP48SX RAM locations
crcval:=#00104 ; hardware CRC register
hwtimer:=#00138 ; hardware timer
sdisp_ptr:=#7055B ; contains address of stack display
mdisp_ptr:=#70551 ; contains address of menu display
uflags:=#706D5 ; user flags
; data area layout (with a smarter assembler these offsets could be
; calculated automatically).
; Don't rearrange; in particular, the variables must be first and
; the order of the "V10".."V15" pseudovariables is important.
o_vars:=0 ; CHIP-8 variables, 16 vars * 2 nibbles = 32
o_timer:=32 ; delay timer "V10", size = 2
o_sound:=34 ; sound timer "V11", size = 2
o_sndon:=36 ; sound on/off flag "V12.0", size = 1
o_sdata:=37 ; speaker data "V12.1", "V13", size = 3
o_ckeys:=40 ; control key status "V14-15", size=4
o_csp:=44 ; CHIP-8 stack pointer, size 5
o_cstack:=49 ; CHIP-8 stack, size "stacknibbles" = 64
o_linetab:=113 ; display row table, size 64 * 5 = 320
o_regsave:=433 ; r0..r3 temporary save location, size = 20
o_scrmode:=453 ; if nonzero extended screen mode is used
o_end:=454 ; end of data area
; don't change these without updating the offsets above also
slevels:=16 ; size of CHIP-8 stack
snibbles:=64 ; 4*stacklevels
; execution begins here
call.a save_regs ; call ROM routine to save d0, d1, b, d
call.a check_1
; allocate a string for temporary data storage
move.p5 o_end+#2000,c ; size of data area plus virtual memory
call.a alloc_str ; call ROM routine to allocate a string
swap.a a,d0
move.a a,r4 ; now R4 points to data area
move.p5 o_end,c
add.a c,a
move.a a,r2 ; and R2 to virtual zero
; clear the CHIP-8 variables and initialize some pseudovariables
move.a r4,c
move.a c,d0 ; point to variables
clr.w c
move.w c,@d0 ; clear V0..V7
add.a 16,d0
move.w c,@d0 ; clear V8..VF
add.a 16,d0
move.p8 #40010000,c ; set timers to #00, sndon to #1,
; and sdata to #400
move.8 c,@d0
; fill "linetab" with pointers to the display rows
move.a r4,a ; get start of data area
move.p5 o_linetab,c
add.a a,c
move.a c,d0 ; d0 points to linetab
move.p5 sdisp_ptr,c ; pointer to address of stack display
move.p2 56,a ; 56 rows
call.3 ltfill
move.p5 mdisp_ptr,c ; pointer to address of menu display
move.p2 8,a ; 8 rows
call.3 ltfill
chipmain:
; copy the chip-8 program from the argument string to the virtual
; chip-8 memory
call.a rest_regs
move.a r2,a ; get virtual zero
move.p5 #0400,c ; virtual #0200 bytes
add.a a,c
move.a c,d0 ; now d0 points to virtual 0200
move.a @d1,c ; point to the argument object
move.a c,d1 ; (presumably a string)
move.a @d1,a ; get the object type
move.p5 #02a2c,c ; string type prefix
brne.a c,a,doerror ; exit if it isn't a string
add.a 5,d1 ; skip the type
move.a @d1,a ; get the object length
sub.a 5,a ; subtract length of length
move.p5 #01c00,c ; this is #1000 - #0200 bytes in nibbles
brgt.a c,a,notlong
doerror:
move.p5 #FFFFF,c
move.a c,r3
jump.3 errexit ; string will not fit in 4 k
notlong:
add.a 5,d1 ; point to the object itself
call.3 copyn
; pop the argument string off the stack
call.a rest_regs
add.a 5,d1
inc.a d
call.a save_regs
; copy the hexadecimal character font to the virtual chip-8 memory,
; unpacking it on-the-fly
move.a r2,c ; get virtual zero
move.a c,d0 ; now d0 points to 0000 virtual
move.a pc,a
ref17: move.p5 hexfont-ref17,c
add.a a,c
move.a c,d1 ; now d1 points to the hex patterns
move.p2 hexfend-hexfont,a ; length (8 bits are enough)
fcopylo:
move.1 @d1,c ; read a nibble
sln.a c ; shift to high nibble in byte
add.a 1,d1
move.2 c,@d0 ; store byte
add.a 2,d0
dec.a a
brnz.b a,fcopylo
; copy the 8x10 fonts to virtual memory
move.a pc,a
ref18: move.p5 hexf810-ref18,c
add.a a,c
move.a c,d1
move.p2 40,a ;only from 0-9
fcopyl2:
move.a @d1,c
move.a c,@d0
add.a 5,d0
add.a 5,d1
dec.a a
brnz.b a,fcopyl2
; turn off interrupts
clrb #F,st
intoff
; jump here when the "restart" key is pressed
restart:
; initialize PC and I
clr.a c
move.a c,r0 ; I=0000 in r0
move.p3 #200,c ; relies on c being cleared
move.a c,r3 ; PC=0200 in r3
; initialize keys
move.a r4,a
move.p5 o_ckeys,c
add.a a,c
move.a c,d0
clr.a c
move.a c,@d0
; initialize stack pointer
add.a o_csp-o_ckeys,d0
move.a c,@d0 ; csp cleared
; initialize extended screen mode flag
move.p5 o_scrmode,c
add.a a,c
move.a c,d0
clr.a c
move.p c,@d0 ; normal screen mode initially
; erase display
call.3 i00e0
; initialize the time value
clr.a c ; must clear all of c for comparison below
move.p3 hwtimer,c
move.a c,d0 ; point to hardware timer
move.a @d0,c
retry1: move.a c,a
move.a @d0,c
brne.a c,a,retry1 ; loop until we get same value twice
move.p5 #00F80,a ; mask away 7 low bits of time value
and.a a,c
move.a a,r1
nextinstr:
call.3 realtime ; do real-time chores
brcc dispatch
rtcarry:
brbs 4,a,restart ; ENTER was pressed
; otherwise, the abort key was pressed; make a clean exit
exit:
inton
setb #F,st
call.a rest_regs
move.a @d0,a ; dispatch next RPL instruction
add.a 5,d0
jump.a @a
dispatch:
; dispatch a CHIP-8 instruction
move.a r3,c ; get cpc
move.a c,a ; keep unincremented value
add.a 2,a ; increment cpc by 2 bytes
move.a a,r3 ; store incremented cpc
call.3 virtophy ; unincremented value is in c
move.a c,d1 ; store virtual pc in d1
clr.a c
add.a 1,d1 ; point to MSN
move.p @d1,c ; get MSN of first byte of CHIP-8 instruction
sub.a 1,d1 ; back to beginning of instruction
add.a c,c
add.a c,c ; now c = nibble * 4
move.a pc,a
ref6: add.a c,a
move.p5 jumptab-ref6,c
add.a a,c ; now c = jumptab + nibble * 4
move.a c,d0
clr.a c ; clear the 5th nibble
move.4 @d0,c ; now c = jumptab entry
move.a pc,a
jtref: add.a c,a ; now a = jump address
move.a pc,c
retref: add.a retloc-retref,c
push.a c ; push return address on stack
jump.a a ; jump to instruction routine
retloc:
brcs errexit ; if carry is set, an error has occurred
jump.3 nextinstr
errexit:
inton
setb #F,st
move.w r3,c ; get the current CHIP-8 PC value
move.w c,r0 ; move to R0
jump.a push_r0 ; ROM routine: push short integer from R0
; and exit to RPL
; ltfill -- fill a part of "linetab"
ltfill:
move.a c,d1
move.a @d1,c ; get display address
add.a 16,c ; increment past the GROB header (20 nibbles)
add.a 4,c
ltf_lp:
move.a c,@d0
add.a 16,c ; increment to next row (34 nibbles)
add.a 16,c
add.a 2,c
add.a 5,d0
dec.b a
brnz.b a,ltf_lp
ret
; copynibbles -- copy a memory block
;
; d1 points to source, d0 to destination, and
; a contains the number of nibbles to copy
copyn:
copylo:
brz.a a,copyend
move.1 @d1,c
move.1 c,@d0
add.a 1,d0
add.a 1,d1
dec.a a
jump.3 copylo
copyend:
ret
; realtime -- do various timer-driven real-time processing
;
; In: nothing
; Out: carry set iff real-time keypress detected; key code is in a
; Uses: all 16 nibbles of a and c; d0
; but neither b nor d
;
realtime:
; flip the speaker if the sound is on
clr.a c
move.p2 o_sound,c
move.a r4,a
add.a a,c
move.a c,d0 ; adress of sound timer in d0
move.b @d0,c
brz.b c,silent
add.a 2,d0 ; point to sound on/off flag
move.p @d0,c
brz.p c,silent
add.a 1,d0 ; point to speaker data
move.3 @d0,c
out.x c
not.x c ; turn #400 into #800 and vice versa
move.p3 #c00,a
and.x a,c
move.3 c,@d0
silent:
; check the hardware timer register to see if it is time for
; a 64 Hz realtime clock tick
clr.a c ; must clear all of c for comparison below
move.p3 hwtimer,c
move.a c,d0
move.a @d0,a
move.p5 #00F80,c ; mask away 7 low bits of time value
and.a a,c
move.a r1,a ; now a is old value, c is new value
brne.a c,a,dotick
jump.3 notick ; code at notick depends on c.0 being zero
dotick: ; handle a 64 Hz tick
clr.a c ; decrement r1 by #80
move.p2 #80,c
sub.a c,a
move.p5 #00F80,c ; mask
and.a c,a
move.a a,r1
call.3 timerpd0 ; point to delay timer
move.b @d0,c
brz.b c,timerzero
dec.b c
move.b c,@d0
timerzero:
add.a 2,d0 ; point to sound timer
move.b @d0,c
brz.b c,soundzero
dec.b c
move.b c,@d0
soundzero:
; check for various control keys
move.a r4,a
move.p5 o_ckeys,c
add.a c,a
move.a a,d0 ; point d0 to key status
move.p3 #010,c ; row ENTER..backstep
out.x c
call.a do_in_c
move.a c,a ; save "in" data in a
clr.a c
out.x c ; zero "out" port as fast as possible
move.4 @d0,c ; get previous key status
move.4 a,@d0 ; save current key status
not.a a ; get keys that are not pressed
and.a c,a ; ..but were..
retbs 4,a ; return with carry set if ENTER pressed
retbs 0,a ; same for the backstep key
brbs 3,a,tsound ; +/-
noabort:
move.p1 #1,c ; set flag to indicate that a tick took place
notick:
retclrc ; return tick flag in c.0
; toggle the sound flag (this is jumped to when the +/- key is pressed)
tsound:
call.3 sndonpd0
move.p @d0,c
not.a c
move.p1 #1,a
and.a a,c
move.p c,@d0
jump.3 noabort
; nnnc - get NNN field of current instruction to c register
;
; In: d1 pointing to chip-8 instruction
; Out: NNN field of instruction in c (5 valid nibbles)
; Uses: none
nnnc:
clr.a c ; clear nibbles 3..4
move.1 2,p
move.p @d1,c ; set nibble 2
move.1 0,p
add.a 2,d1 ; point to second byte of instruction
move.b @d1,c ; set nibbles 0 and 1
sub.a 2,d1 ; restore d1
ret
; virtophy -- convert virtual address to physical address
;
; In: virtual address in c
; Out: physical address in c
; Uses: a
virtophy:
add.a c,c ; convert bytes to nibbles
move.a r2,a ; convert virtual to physical
add.a a,c
ret
; varpd0 - get pointer to variable to d0
;
; In: d1 points to nibble containing variable number
; Out: d0 points to variable
; Uses: a,c
varpd0:
clr.a c
move.p @d1,c ; get nibble with variable number
cvarpd0:
add.a c,c ; convert bytes to nibbles
move.a r4,a
add.a a,c
move.a c,d0
ret
; var0pd0 -- load d0 with pointer to V0
;
; In: none
; Out: d0 points to variable 0
; Uses: a,c
var0pd0:
clr.a c
move.p1 #0,c
jump.3 cvarpd0
; varfpd0 -- load d0 with pointer to VF
;
; In: none
; Out: d0 points to variable F
; Uses: a,c
varfpd0:
clr.a c
move.p1 #f,c
jump.3 cvarpd0
timerpd0:
clr.a c
move.p2 #10,c
jump.3 cvarpd0 ; point d0 to timer
soundpd0:
clr.a c
move.p2 #11,c
jump.3 cvarpd0 ; point d0 to sound timer
sndonpd0:
clr.a c
move.p2 #12,c
jump.3 cvarpd0 ; point d0 to sound on/off flag
; varxcvarya -- get values of X and Y variables
; In: d1 points to instruction
; Out: c contains VX, zero padded to .a field
; a contains VY, zero padded to .a field
; Uses: d0
xcya:
call.3 varpd0 ; get pointer to X
clr.a c
move.b @d0,c ; get X value
push.a c
add.a 3,d1 ; point to Y nibble in instruction
call.3 varpd0 ; get pointer to Y
clr.a a
move.b @d0,a ; get Y value
pop.a c
sub.a 3,d1 ; back to beginning of instruction
ret
; In: d1 points to beginning instruction
; Out: c contains VX (5 nibbles valid)
; a contains VY (5 nibbles valid)
; d0 points to VX
; Uses: none
alusetup:
add.a 3,d1 ; point to Y nibble in instruction
call.3 varpd0
sub.a 3,d1
clr.a c
move.2 @d0,c ; VY in c
push.a c
call.3 varpd0 ; d0 is pointer to VX
clr.a a
move.2 @d0,a ; VX in a
pop.a c ; VY in c
swap.a a,c ; now VX in c and VY in a
ret
savecarry:
srn.a c ; extract the carry byte
srn.a c
lsbcarry:
move.p2 #01,a ; use low bit only
and.b a,c
push.a c
call.3 varfpd0 ; get a pointer to VF
pop.a c
move.b c,@d0 ; store the carry byte
retclrc
; testkey -- check whether a given hex key is pressed
;
; In: key number in c (5 low nibbles must be valid)
; Out: low nibble of c is nonzero iff key is pressed
; Uses: a,d0
testkey:
add.a c,c ; index into keytab
move.a pc,a
ref16: add.a c,a
move.p5 keytab-ref16,c
add.a a,c
move.a c,d0 ; now d0 points to keytab
clr.a c
move.1 @d0,c ; get "out" data
out.x c
call.a do_in_c
move.a c,a ; store the input value in a
clr.a c
out.x c ; zero "out" port as fast as possible
add.a 1,d0
move.1 @d0,c ; get "in" mask
and.a a,c
ret
; setup subroutine for fx55 or fx65 instruction
;
; In: d0 points to VX
; Out: d0 points to to V0, a points to VX, and d1 points to MI
copysetup:
swap.a c,d0 ; copy d0 to c
move.a c,d0
push.a c ; save pointer to the last variable
call.3 var0pd0 ; point d0 to first variable (v0)
move.a r0,c ; get I
call.3 virtophy
move.a c,d1 ; point d1 to data at I
pop.a c
move.a c,a ; now a contains pointer to last var.
ret
; setup subroutine for fx75 or fx85 instruction
;
; In: d0 points to VX
; Out: d0 points to to V0, a points to VX
copyset2:
swap.a c,d0 ; copy d0 to c
move.a c,d0
push.a c ; save pointer to the last variable
call.3 var0pd0 ; point d0 to first variable (v0)
pop.a c
swap.a a,d0
move.a a,d0
add.a 14,a
brge.a c,a,noswap ; maximize to V7
move.a c,a
noswap:
ret
i0: ; mcode call
call.3 nnnc
move.a c,a ; routine address is now in a
clr.a c
move.p2 #e0,c
breq.a c,a,i00e0
move.p2 #ee,c
breq.a c,a,i00ee
move.p2 #ff,c
brne.a c,a,noti00ff
jump.3 i00ff
noti00ff:
dec.b c
brne.a c,a,noti00fe
jump.3 i00fe
noti00fe:
move.p2 #fd,c
brne.a c,a,noti00fd
jump.3 exit ; exit
noti00fd:
retsetc ; illegal mcode call
i00e0: ; erase screen
move.a r4,a ; get start of data area
move.p5 o_linetab,c
add.a c,a
; a contains linetab pointer
; b counts down from 64
move.p2 64,c
move.a c,b
eraselo:
move.a a,d0
move.a @d0,c
move.a c,d0 ; d0 now points to display memory
clr.w c
move.w c,@d0 ; erase 16 nibbles
add.a 16,d0
move.w c,@d0 ; and 16 more
add.a 16,d0
move.b c,@d0 ; and 2 more, total 34
add.a 5,a
dec.b b
brnz.b b,eraselo
retclrc
i00ee: ; subroutine return
move.a r4,a ; get start of data area
move.p5 o_csp,c
add.a a,c
move.a c,d0 ; c and d0 both point to csp
move.a @d0,a ; now a contains the chip-8 stack pointer (0..4n-4)
retz.a a ; return with carry set if stack underflow
sub.a 4,a ; drop one level
move.a a,@d0 ; save new csp
add.a 5,c ; point c at stack[0]
add.a a,c ; point c at popped level
move.a c,d0 ; point d0 at popped level
clr.a c
move.4 @d0,c
move.a c,r3 ; set pc
retclrc
i00ff: ; enable extended mode
move.a r4,a
move.p5 o_scrmode,c
add.a a,c
move.a c,d0
move.p1 #F,c
move.p c,@d0 ; store #F in scrmode
retclrc
i00fe: ; disable extended mode
move.a r4,a
move.p5 o_scrmode,c
add.a a,c
move.a c,d0
clr.a c
move.p c,@d0 ; store #0 in scrmode
retclrc
i1: ; 1NNN, jump
dojmp: call.3 nnnc
move.a c,r3 ; assign to pc
retclrc
i2: ; 2NNN, subroutine call
move.a r4,a ; get start of data area
move.p5 o_csp,c
add.a a,c
move.a c,d0 ; d0 and c both point to csp
move.a @d0,a ; now a contains the chip-8 stack pointer (0..4n-4)
; c still points at csp
add.a 5,c ; point c at stack[0]
add.a a,c ; point c at first free stack level
swap.a c,d0 ; now d0 points to free stack and c points to csp
move.a r3,a ; get pc
move.4 a,@d0 ; store pc in stack
swap.a c,d0 ; now d0 points to cpc again
move.a @d0,a ; now a contains the chip-8 stack pointer (0..4n-4)
add.a 4,a
move.p5 snibbles,c
retlt.a c,a ; return with carry set if stack overflow
move.a a,@d0 ; store incremented sp
jump.3 dojmp ; the reset is like 1nnn
i3: ; 3XKK, skip if X==KK
call.3 varpd0 ; get pointer to X
add.a 2,d1 ; point to second byte of instruction
move.b @d1,a ; now a = KK
move.b @d0,c ; now c = VX
skipeq:
brne.b c,a,noskip
doskip: swap.a c,r3 ; increment cpc by 2
add.a 2,c
swap.a c,r3
noskip:
retclrc
i4: ; 4XKK, skip if X<>KK
call.3 varpd0 ; get pointer to X
add.a 2,d1 ; point to second byte of instruction
move.b @d1,a ; now a = KK
move.b @d0,c ; now c = VX
skipne:
breq.b c,a,noskip
jump.3 doskip
i5: ; 5XY0, skip if X==Y
call.3 xcya ; get VX to c, VY to a
jump.3 skipeq
i9: ; 9XY0, skip if X!=Y
call.3 xcya ; get VX to c, VY to a
jump.3 skipne
i6: ; 6XKK, load variable by constant
call.3 varpd0 ; get pointer to X
add.a 2,d1 ; point to second byte of instruction
move.b @d1,a ; now a = KK
move.b a,@d0 ; store in variable
retclrc
i7: ; 7XKK, add constant to variable
call.3 varpd0 ; get pointer to X
add.a 2,d1 ; point to second byte of instruction
move.b @d1,a ; now a = KK
move.b @d0,c ; get old value
add.b a,c ; add KK
move.b c,@d0 ; store new value
retclrc
i8: ; arithmetic and logic operations
add.a 2,d1 ; point to last nibble of instruction
move.1 @d1,a
sub.a 2,d1
move.p1 #0,c
brne.p c,a,noti8xy0
i8xy0: ; VX := VY
call.3 alusetup
move.b a,@d0 ; store result
retclrc
noti8xy0:
move.p1 #1,c
brne.p c,a,noti8xy1
i8xy1: ; VX := VX or VY
call.3 alusetup
or.b a,c
move.2 c,@d0 ; store result
retclrc
noti8xy1:
move.p1 #2,c
brne.p c,a,noti8xy2
i8xy2: ; VX := VX and VY
call.3 alusetup
and.b a,c
move.2 c,@d0 ; store result
retclrc
noti8xy2:
move.p1 #3,c
brne.p c,a,noti8xy3
i8xy3: ; VX := VX xor VY
call.3 alusetup
move.b a,b
or.b c,b ; (x or y) in b
and.b a,c ; (x and y) in c
not.b c
and.b b,c ; (x xor y) in c
move.b c,@d0 ; store result
retclrc
noti8xy3:
move.p1 #4,c
brne.p c,a,noti8xy4
i8xy4: ; VX := VX + VY; carry in VF
call.3 alusetup
add.a a,c
move.2 c,@d0
jump.3 savecarry
noti8xy4:
move.p1 #5,c
brne.p c,a,noti8xy5
i8xy5: ; VX := VX - VY; carry in VF
call.3 alusetup
subcommon:
not.b a ; do monkey business to get inverted carry
add.a a,c
inc.a c
move.2 c,@d0
jump.3 savecarry
noti8xy5:
move.p1 #6,c
brne.p c,a,noti8xy6
i8xy6: ; VX := VX >> 1; carry in VF
call.3 alusetup
move.b c,a
srb.b c
move.2 c,@d0 ; store result
move.b a,c ; use low bit of original byte for carry
jump.3 lsbcarry
noti8xy6:
move.p1 #7,c
brne.p c,a,noti8xy7
i8xy7: ; VX := VY - VX; carry in VF
call.3 alusetup
swap.a a,c
jump.3 subcommon
noti8xy7:
move.p1 #e,c
brne.p c,a,noti8xye
i8xye: ; VX := VX << 1; carry in VF
call.3 alusetup
add.a c,c
move.2 c,@d0
jump.3 savecarry
noti8xye:
retsetc
ia: ; ANNN, set I
call.3 nnnc
move.a c,r0 ; assign to I
retclrc
ib: ; parametric jump to NNN+V0
call.3 varpd0
call.3 nnnc
clr.a a
move.b @d0,a
add.a a,c
move.a c,r3 ; assign to pc
retclrc
ic: ; pseudo-random number
call.3 varpd0 ; now d0 points to VX
move.p5 crcval,c
swap.a c,d0 ; point d0 to hardware crc
move.2 @d0,a ; read the low byte of the crc
swap.a c,d0 ; now d0 points to VX again
add.a 2,d1 ; point to second byte of instruction
move.b @d1,c ; get mask
sub.a 2,d1 ; restore d1
and.b a,c ; mask
move.b c,@d0 ; store result
retclrc
id_abort:
jump.3 fx0a_ab
id: ; DXYN, show N-byte sprite at MI at screen coordinates (X,Y)
; I doesn't change
move.a r4,a
move.p5 o_scrmode,c
add.a a,c
move.a c,d0
move.p @d0,c
brz.p c,idn
jump.3 ide ; extended screen mode
; Normal screen mode
idn:
; synchronize with 64 Hz tick
tickwait:
call.3 realtime
brcs id_abort ; a realtime key was pressed
brz.p c,tickwait ; wait until a tick occurs
call.3 s_rregs ; save r0..r3
move.a r0,c ; get I
call.3 virtophy
move.a c,r0 ; now r0 points to sprite
call.3 xcya
move.a c,d ; save X in d
move.p2 #1f,c
and.b c,a ; mask Y to range 0..31, leave in a
move.p2 #3f,c
and.b c,d ; mask X to range 0..63, save in d
; get the number of bytes in the sprite (preserving a and d)
add.a 2,d1 ; point to N field
clr.b c
move.1 @d1,c
add.b a,c ; now c contains Y + sprite length
move.b c,b
move.p2 #20,c
sub.b c,b ; now b contains no. of overshoot lines
brcc oshoot
clr.b b ; no overshoot
oshoot:
clr.b c ; get sprite length again
move.1 @d1,c
sub.b b,c ; subtract overshoot
move.1 c,0,p ; now p contains adjusted length
move.1 p,c,15 ; byte counter in nibble 15 of c
move.1 14,p
clr.p c ; collision flag in nibble 14 of c
move.1 0,p
sub.a 2,d1 ; back to beginning of instruction
call.3 spriten ; do it
call.3 r_rregs
call.4 varfpd0 ; d0 points to VF
clr.b c
move.1 14,p
brz.p c,nocolls
inc.b c
nocolls:
move.b c,@d0 ; store collision flag
move.1 0,p
retclrc ; idn
; Extended screen mode
ide: call.3 s_rregs ; save r0..r3
move.a r0,c ; get I
call.3 virtophy
move.a c,r0 ; now r0 points to sprite
call.3 xcya
move.a c,d ; save X in d
move.p2 #3f,c
and.b c,a ; mask Y to range 0..63, leave in a
move.p2 #7f,c
and.b c,d ; mask X to range 0..127, save in d
; get the number of bytes in the sprite (preserving a and d)
add.a 2,d1 ; point to N field
clr.w c
move.1 @d1,c
brnz.p c,nobig
; Big sprite (16x16 pixels) when length is 0 lines
add.b 16,c
move 12,p
move.p1 8,c ; c.12 = 8 when in this mode
move 0,p
nobig: push.a c
add.b a,c ; now c contains Y + sprite length
move.b c,b
move.p2 #40,c
sub.b c,b ; now b contains no. of overshoot lines
brcc oshoote
clr.b b ; no overshoot
oshoote:
pop.a c
sub.b b,c ; subtract overshoot
move.1 c,0,p ; now p contains adjusted length
move.1 p,c,15 ; byte counter in nibble 15 of c
move.1 0,p
sub.a 2,d1 ; back to beginning of instruction
call.3 spritee ; do it
call.3 r_rregs
call.4 varfpd0 ; d0 points to VF
clr.b c
move.1 14,p
brz.p c,nocollse
inc.b c
nocollse:
move.b c,@d0 ; store collision flag
move.1 0,p
retclrc ; ide
ie: ; skip on key pressed / not pressed
call.3 varpd0
clr.a c
move.b @d0,c ; Telmac key number
call.3 testkey
clr.b d
move.p c,d ; now d.b is nonzero iff key was pressed
add.a 2,d1 ; point to second byte of instruction
move.b @d1,a
sub.a 2,d1
move.p2 #9e,c
breq.b c,a,doex9e
move.p2 #a1,c
breq.b c,a,doexa1
retsetc
doex9e: move.b d,c
clr.b a
jump.3 skipne ; skip iff d!=0 (key was pressed)
doexa1: move.b d,c
clr.b a
jump.3 skipeq ; skip iff d==0 (key was not pressed)
; kwait -- wait for key, return it in low byte of c
; In: none
; Out: key code in low byte of c
; Uses: most everything except d0
;
; The beep-and-wait-until-released behaviour may seem a bit crummy,
; but we're just trying to emulate the original Telmac PROM monitor
; keyboard input routine as closely as possible.
kwait:
swap.a c,d0
push.a c ; save d0 value
clr.a d ; low nibble of d used for key code
kwalo: move.a d,c
call.3 testkey
brnz.p c,pressed
call.3 realtime ; preserves d
brcs kwabort
inc.p d ; loop through keys 0..15
jump.3 kwalo
pressed:
call.3 soundpd0
move.p2 #04,c
move.2 c,@d0 ; set sound timer to #04
call.3 realtime ; make some noise
brcs kwabort
move.a d,c
call.3 testkey
brnz.p c,pressed ; wait until key is released
pop.a c ; restore d0 value
move.a c,d0
move.b d,c
retclrc
kwabort:
pop.a c ; adjust stack
retsetc
if: ; misc. functions using VX
; d0 is set up to point to VX
call.3 varpd0
add.a 2,d1 ; point to second byte of instruction
move.b @d1,a
sub.a 2,d1
move.p2 #07,c
brne.b c,a,notifx07
ifx07: ; read timer
swap.a c,d0
push.a c
call.3 timerpd0
move.b @d0,a
pop.a c
swap.a c,d0
move.b a,@d0
retclrc
notifx07:
move.p2 #0a,c
brne.b c,a,notifx0a
ifx0a: call.3 kwait
brcs fx0a_ab
move.b c,@d0
retclrc
fx0a_ab:
pop.a c ; adjust stack for forced return
jump.4 rtcarry
notifx0a:
move.p2 #15,c
brne.b c,a,notifx15
ifx15: ; set timer
move.b @d0,c
push.a c
call.3 timerpd0
pop.a c
move.b c,@d0
retclrc
notifx15:
move.p2 #18,c
brne.b c,a,notifx18
ifx18: ; set sound
move.b @d0,c
push.a c
call.3 soundpd0
pop.a c
move.b c,@d0
retclrc
notifx18:
move.p2 #1e,c
brne.b c,a,notifx1e
ifx1e: ; increment I by VX
clr.a c
move.b @d0,c
move.a r0,a ; get old I
add.x c,a ; increment, modifying only 3 low nibbles
retcs ; it wrapped around #1000
move.a a,r0 ; save new I
retclrc
notifx1e:
move.p2 #29,c
brne.b c,a,notifx29
ifx29: ; point to hex display pattern
; assumes that the hex patterns are at virtual 0000
clr.a c
clr.a a
move.p @d0,a ; use low nibble of variable
move.p a,c
add.a c,c ; * 2
add.a c,c ; * 4
add.a a,c ; * 5
move.a c,r0 ; this is the new I
retclrc
notifx29:
move.p2 #30,c
brne.b c,a,notifx30
; point to large display pattern (8x10)
; at virtual #50
clr.a c
clr.a a
move.p @d0,a ; use low nibble of variable
move.p a,c
add.a a,a ; * 2
add.a a,a ; * 4
add.a c,a ; * 5
add.a a,a ; * 10
move.p2 #50,c
add.a c,a ; + #50
move.a a,r0 ; this is the new I
retclrc
notifx30:
move.p2 #33,c
breq.b c,a,ifx33
jump.3 notifx33
ifx33: ; 8-bit binary to decimal conversion
move.b @d0,c
move.b c,d ; d contains the byte to convert
move.a pc,a
ref12: move.p5 dectab-ref12,c
add.a a,c
move.a c,d0 ; d0 points to the conversion table
clr.a a ; a accumulates decimal result
cnvbit: brz.b d,cnvend
move.b d,c
brbc 0,c,skpbit ; jump if low-order bit is zero
move.3 @d0,c
setdec
add.a c,a ; only 3 low nibbles contain real data
sethex
skpbit: add.a 3,d0
srb.b d
jump.3 cnvbit
cnvabort:
retsetc
cnvend:
move.a a,b ; save the decimal value
move.a r0,c ; get I
move.p5 #00ffd,a ; is I > 0FFD hex?
retgt.a c,a ; protect memory above 0FFF
call.3 virtophy
move.a c,d0 ; virtual I in d0
move.a b,a ; restore the decimal value
clr.a c
move.1 2,p
move.p a,@d0 ; most significant digit first
add.a 1,d0
move.p c,@d0 ; zero
add.a 1,d0
move.1 1,p
move.p a,@d0 ; middle digit
add.a 1,d0
move.p c,@d0 ; zero
add.a 1,d0
move.1 0,p
move.p a,@d0 ; least significant digit
add.a 1,d0
move.p c,@d0 ; zero
; I doesn't change
retclrc
notifx33:
move.p2 #55,c
brne.b c,a,notifx55
ifx55: ; save vars in memory
call.3 copysetup
savelo:
move.b @d0,c ; read a byte from variable
move.b c,@d1 ; store in MI
swap.a c,d0 ; get d0 to c
move.a c,d0
brge.a c,a,doret1 ; are we ready yet?
add.a 2,d1
add.a 2,d0
move.a r0,c ; get I value
inc.x c ; increment 3 low nibbles
retcs ; if carry, we might overwrite #1000
move.a c,r0 ; I changes permanently
jump.3 savelo
doret1:
retclrc
notifx55:
move.p2 #65,c
brne.b c,a,notifx65
ifx65: ; restore vars from memory
call.3 copysetup
restolo:
move.b @d1,c ; read a byte at MI
move.b c,@d0 ; store in variable
swap.a c,d0 ; get d0 to c
move.a c,d0
brge.a c,a,doret1 ; are we ready yet?
add.a 2,d1
add.a 2,d0
move.a r0,c ; get I value
inc.x c ; increment 3 low nibbles
retcs ; if carry, we wrapped around #1000
move.a c,r0 ; I changes permanently
jump.3 restolo
notifx65:
move.p2 #75,c
brne.b c,a,notifx75
ifx75: ; save vars in user flags
call.3 copyset2
move.5 uflags,d1 ; user flags
savelp:
move.b @d0,c ; read a byte from variable
move.b c,@d1 ; store
swap.a c,d0 ; get d0 to c
move.a c,d0
add.a 2,d1
add.a 2,d0
brlt.a c,a,savelp ; are we ready yet?
retclrc
notifx75:
move.p2 #85,c
brne.b c,a,notifx85
ifx85: ; restore vars from memory
call.3 copyset2
move.5 uflags,d1
restlp:
move.b @d1,c ; read a byte at MI
move.b c,@d0 ; store in variable
swap.a c,d0 ; get d0 to c
move.a c,d0
add.a 2,d1
add.a 2,d0
brlt.a c,a,restlp ; are we ready yet?
retclrc
notifx85:
; unknown FxNN instruction
retsetc
; save registers r0..r3
s_rregs:
move.a r4,a ; get start of data area
move.p5 o_regsave,c
add.a a,c
move.a c,d0
move.a r0,c
move.a c,@d0
add.a 5,d0
move.a r1,c
move.a c,@d0
add.a 5,d0
move.a r2,c
move.a c,@d0
add.a 5,d0
move.a r3,c
move.a c,@d0
add.a 5,d0
ret
; restore registers r0..r3
r_rregs:
move.a r4,a ; get start of data area
move.p5 o_regsave,c
add.a a,c
move.a c,d0
move.a @d0,c
move.a c,r0
add.a 5,d0
move.a @d0,c
move.a c,r1
add.a 5,d0
move.a @d0,c
move.a c,r2
add.a 5,d0
move.a @d0,c
move.a c,r3
add.a 5,d0
ret
; sprite: draw a CHIP-8 "sprite", 8 pixels wide by 1..15 pixels high
;
; in: r0.a points to sprite data
; a.a contains y coordinate
; d.a contains x coordinate
; c.14 zero initial value for collision flag
; c.15 number of lines in sprite
; c.12 nonzero in big mode
; out: c.14 collision flag
;
; register usage:
; a,c used for scratch
; c.15 contains sprite byte (line) count
; c.14 contains collision flag
; c.13 is used as temporary save location for p
; c.12 nonzero in 16x16 mode (c.15 = 0)
; b contains the byte to display
; d contains the 32-bit pixel string
; r0 points to sprite data
; r1 points to linetab
; r2 contains the X offset in nibbles from the beginning of the lcd line
; r3 contains entry point to pixel alignment shifts
; Extended sprite mode
spritee:
move.a a,c ; multiply y by 5 to get index into linetab
add.a a,a
add.a a,a
add.a a,c ; now c contains index to linetab
move.a r4,a ; get start of data area
add.a c,a
move.p5 o_linetab,c
add.a a,c
move.a c,r1 ; r1 is the linetab pointer
; calculate X offset in nibbles from the beginning of the lcd line
move.a d,c ; c = X
srb.a c ; convert pixels to nibbles
srb.a c
move.a c,r2 ; r2 contains offset from beginning of line
; calculate entry point to alignment shifts
move.p2 #03,c ; d should still contain X
and.b c,d ; mask out 2 low bits
move.p2 7,c
move 12,p
brz.p c,nobigs1
add.b 8,c
nobigs1:
move 0,p
add.b d,c ; + 7 (+ 8 if big) -> C
move.a c,r3 ; and store in r3
linelo: move.a r0,c ; get sprite pointer
move.a c,d0
clr.a a
move.b @d0,a
move 12,p
brz.p c,nobigs2
; big mode
add.a 2,d0
sln.a a
sln.a a
move.b @d0,a
nobigs2:
move 0,p
swap.a c,d0
add.a 2,c ; advance to next
move.a c,r0 ; save sprite pointer
; the low byte of a now contains the sprite byte
; and r3 contains (x mod 3) + 7 + (8 if big)
; convert and shift in one operation
move.a r3,c
move.a c,d
clr.a c ; and c is target register
rotlp: add.a c,c
brbc 0,a,notone
inc.a c
notone: srb.w a
dec.b d
brcc rotlp
move.a c,d
; now d contains 12/20 pixels (3/5 nibbles)
nobigsto:
move 0,p
move.a r1,a ; get linetab pointer
move.a a,d0 ; ..to d0
move.a @d0,c ; address of current line to c
add.a 5,a ; point 1 line ahead
move.a a,r1 ; this is the new linetab pointer
move.a r2,a ; X offset in nibbles
add.a a,c ; add beginning of lcd line
move.a c,d0 ; now d0 points to display buffer
; now we operate on the display buffer with a word length
; that is normally 16 bits, but near the right end we
; must decrease it to avoid wrapping around and messing up
; the left part of the display. Therefore the word length
; should be min(3 (5), 32-xoff) nibbles.
move.p2 32,c
sub.b a,c ; now c = 32-xoff
move.p2 3,a
move 12,p
brz.p c,nobigcl
add.b 2,a
nobigcl:
move 0,p
brlt.b c,a,clip ; if (32-xoff) is < 3(5), use that, else 3(5)
move.b a,c
clip: dec.b c ; .wp field is (p+1) nibbles; compensate
move.1 c,0,p ; set field size
move.wp @d0,c ; get old display buffer contents
move.wp c,a ; make a copy in a
and.wp d,c ; now c = (old and new), a = old, d = new
brz.wp c,nocoll
; there has been a collision; set nibble 14 in c
swap.1 p,c,13 ; save p in nibble 13 of c
move.1 14,p
move.p1 1,c ; set collision flag in nibble 13 of c
swap.1 p,c,13 ; restore p
nocoll:
swap.wp d,c ; now d = (old and new), a = old, c = new
or.wp a,c ; now c = (old or new)
not.wp d
and.wp d,c ; now c = (old or new) and not(old and new)
move.wp c,@d0 ; store once
move.1 0,p
dec.s c
brz.s c,lineexit
jump.3 linelo
lineexit:
ret ; spritee
; spriten: draw a CHIP-8 "sprite", 8 pixels wide by 1..15 pixels high
; in normal screen mode
spriten:
add.a a,a ; 2Y (the chip-8 pixels are 2 lines high)
move.a a,c ; multiply by 5 to get index into linetab
add.a a,a
add.a a,a
add.a a,c ; now c contains index to linetab
move.a r4,a ; get start of data area
add.a c,a
move.p5 o_linetab,c
add.a a,c
move.a c,r1 ; r1 is the linetab pointer
; calculate X offset in nibbles from the beginning of the lcd line
move.a d,c ; c = X
srb.a c ; convert pixels to pixel octets
srb.a c
srb.a c
add.a c,c ; convert pixel octets to nibbles
add.a c,c
move.a c,r2 ; r2 contains offset from beginning of line
; calculate entry point to alignment shifts
move.p2 #07,c ; d should still contain X
and.b c,d ; mask out 3 low bits
add.a d,c ; + 7 -> C
move.a c,r3 ; and store in r3
linelon:
move.a r0,c ; get sprite pointer
move.a c,d0
clr.a a
move.b @d0,a ; byte to display to low byte of a
add.a 2,c ; advance to next sprite byte
move.a c,r0 ; save sprite pointer
; the low byte of a now contains the sprite byte
; and r3 contains (x mod 8) + 7
; convert and shift in one operation
move.a r3,c ; c.b is counter, a.a is source
clr.a d ; and d.w is target register
rotlpn: add.w d,d
add.w d,d
brbc 0,a,notonen
add.a 3,d
notonen:srb.a a
dec.b c
brcc rotlpn
; now d contains 32 pixels
move.a r1,a ; get linetab pointer
move.a a,d0 ; ..to d0
move.a @d0,c ; address of current line to c
add.a 10,a ; point 2 lines ahead
move.a a,r1 ; this is the new linetab pointer
move.a r2,a ; X offset in nibbles
add.a a,c ; add beginning of lcd line
move.a c,d0 ; now d0 points to display buffer
; now we operate on the display buffer with a word length
; that is normally 32 bits, but near the right end we
; must decrease it to avoid wrapping around and messing up
; the left part of the display. Therefore the word length
; should be min(8, 32-xoff) nibbles.
move.p2 32,c
sub.b a,c ; now c = 32-xoff
move.p2 8,a
brlt.b c,a,clipn ; if (32-xoff) is < 8, use that, else 8
move.b a,c
clipn: dec.b c ; .wp field is (p+1) nibbles; compensate
move.1 c,0,p ; set field size
move.wp @d0,c ; get old display buffer contents
move.wp c,a ; make a copy in a
and.wp d,c ; now c = (old and new), a = old, d = new
brz.wp c,nocolln
; there has been a collision; set nibble 14 in c
swap.1 p,c,13 ; save p in nibble 13 of c
move.1 14,p
move.p1 1,c ; set collision flag in nibble 13 of c
swap.1 p,c,13 ; restore p
nocolln:
swap.wp d,c ; now d = (old and new), a = old, c = new
or.wp a,c ; now c = (old or new)
not.wp d
and.wp d,c ; now c = (old or new) and not(old and new)
move.wp c,@d0 ; store once
add.a 16,d0 ; advance by 34 to next scan line
add.a 16,d0
add.a 2,d0
move.wp c,@d0 ; store again
move 0,p
dec.s c
brz.s c,lineexitn
jump.3 linelon
lineexitn:
ret ; spriten
; 4x5 pixel hexadecimal character font patterns
hexfont:
dat.5 #F999F
dat.5 #72262
dat.5 #F8F1F
dat.5 #F1F1F
dat.5 #11F99
dat.5 #F1F8F
dat.5 #F9F8F
dat.5 #4421F
dat.5 #F9F9F
dat.5 #F1F9F
dat.5 #99F9F
dat.5 #E9E9E
dat.5 #F888F
dat.5 #E999E
dat.5 #F8F8F
dat.5 #88F8F
hexfend:
; 8x10 pixel font patterns (only 10)
hexf810:
dat.20 #3C7EC3C3C3C3C3C37E3C ;0
dat.20 #3C181818181818583818 ;1
dat.20 #FFFF6030180C06C37F3E ;2
dat.20 #3C7EC3030E0E03C37E3C ;3
dat.20 #0606FFFFC666361E0E06 ;4
dat.20 #3C7EC303FEFCC0C0FFFF ;5
dat.20 #3C7EC3C3FEFCC0C07C3E ;6
dat.20 #60606030180C0603FFFF ;7
dat.20 #3C7EC3C37E7EC3C37E3C ;8
dat.20 #7C3E03033F7FC3C37E3C ;9
hf810end:
; powers of two in BCD, for binary-to-decimal conversion
dectab: dat.3 #001
dat.3 #002
dat.3 #004
dat.3 #008
dat.3 #016
dat.3 #032
dat.3 #064
dat.3 #128
; table mapping Telmac hex key locations to HP48SX key codes
; lsn is bit mask to output, lsn is bit mask to mask input with
keytab:
dat.2 #41
dat.2 #88
dat.2 #48
dat.2 #28
dat.2 #84
dat.2 #44
dat.2 #24
dat.2 #82
dat.2 #42
dat.2 #22
dat.2 #81
dat.2 #21
dat.2 #18
dat.2 #14
dat.2 #12
dat.2 #11
; jump table for CHIP-8 instruction dispatching based on the first nibble
jumptab:
dat.4 i0-jtref
dat.4 i1-jtref
dat.4 i2-jtref
dat.4 i3-jtref
dat.4 i4-jtref
dat.4 i5-jtref
dat.4 i6-jtref
dat.4 i7-jtref
dat.4 i8-jtref
dat.4 i9-jtref
dat.4 ia-jtref
dat.4 ib-jtref
dat.4 ic-jtref
dat.4 id-jtref
dat.4 ie-jtref
dat.4 if-jtref
regsave: