; 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:



