Friday, October 11, 2024

ALPP 03-08 -- Hexadecimal Output on the 68000, without General Divide (Register Allocation)

Hexadecimal Output on the 68000,
without General Divide
(Register Allocation)

(Title Page/Index)

 

In the 6809 version of hexadecimal output, we talked a little about passing parameters via registers, and we'll look at that a bit more closely here, on the 68000. 

If you've skipped the previous three chapters and this code doesn't make sense, go back and look at the 6800 version for the basic math.

Anyway, don't forget to compare the code here with the previous three. Most of the changes from the 6800/6801 are addressing and addressing related, and parallel the changes in the 6809. But there are some details about operand width that induce a different approach to register usage. And, perhaps surprisingly, the wealth of registers in the 68000 does not really relieve us of the necessity of planning out how we use them.

In a sense, registers can be as hard to manage as statically allocated variables in the direct page.

***********************************************************************
* simple 8-bit binary output for 68000
* using parameter stack,
* with test frame
* Joel Matthew Rees, October 2024

	OPT LIST,SYMTAB	; Options we want for the stand-alone assembler.
	OPT DEBUG	; We want labels for debugging.
	OUTPUT
***********************************************************************

	INCLUDE	rt_rig01_68K.s

	MACHINE MC68000	; because there are a lot the assembler can do.


****************
* Program code:
*
ASC0	EQU	'0'	; Some assemblers won't handle 'c constants well.
ASC9	EQU	'9'
ASCA	EQU	'A'
ASCXGAP	EQU	ASCA-ASC9-1	; Gap between '9' and 'A' for hexadecimal
*
* Mask off and convert the nybble in D5 to ASCII numeric,
* including hexadecimals
OUTH4	AND.W	#$0F,D5	; mask it off
	ADD.W	#ASC0,D5	; Add the ASCII for '0'
	CMP.W	#ASC9,D5	; Greater than '9'?
	BLS.S	OUTH4D	; no, output as is.
	ADD.W	#ASCXGAP,D5	; Adjust it to 'A' to 'F'
OUTH4D	MOVE.L	D5,-(A6)
	BSR.W	OUTC
	RTS
*
* Output an 8-bit byte in hexadecimal,
* byte as a 16-bit parameter on PSP.
OUTHX8	MOVE.L	D5,-(A7)	; save
	CLR.L	D5
	MOVE.B	LOWBYTE(A6),D5	; get the byte
	LSR.B	#4,D5
	BSR.S	OUTH4
	MOVE.B	LOWBYTE(A6),D5	; Get it again.
	BSR.S	OUTH4
	LEA	NATWID(A6),A6
	MOVE.L	(A7)+,D5	; restore 
	RTS


HEADLN	DC.B	CR,LF	; Put message at beginning of line
	DC.B	"Outputting $5A in binary and hex: "	; 
	DC.B	CR,LF,NUL	; Put the binary output on a new line
CHEX	DC.B	": $"
	DC.B	NUL


*
	EVEN
PGSTRT	LEA	HEADLN(PC),A0
	MOVE.L	A0,-(A6)
	BSR.W	OUTS
	MOVE.L	#$5A,-(A6)	; byte to output
	BSR.W	OUTB8
	LEA	CHEX(PC),A0
	MOVE.L	A0,-(A6)
	BSR.W	OUTS
	MOVE.L	#$5A,-(A6)	; byte to output
	BSR.W	OUTHX8
	BSR.W	OUTNWLN
	RTS
*
	END	ENTRY

And here's the framework rigging with the 68000 bit output routines, with optimizations specific to the 68000: 

[JMR202410152018 IMPORTANT NOTE: There is a bug in this source. See the binary output on 68000 chapter, below the rigging, for more information.]

***********************************************************************
* A simple run-time framework inclusion for 68000
* providing parameter stack and local base
* Version 00.00.01
* Joel Matthew Rees, October 2024

	MACHINE MC68000	; because there are a lot the assembler can do.
***********************************************************************
*
* Essential control codes
LF	EQU	$0A	; line feed
CR	EQU	$0D	; carriage return
NUL	EQU	0


* Essential BIOS/OS:
*
* We will not be using these:
*GEMDOSTRAP	EQU	1
*GEMprintstr	EQU	9	; PRINT LINE in some docs
*
BIOSTRAP	EQU	13
bconout		EQU	3
devscrkbd	EQU	2

NATWID	EQU	4	; 4 bytes in the CPU's natural integer
HAFNAT	EQU	(NATWID/2)	; 2 bytes in the CPU's half natural integer
LOWBYTE	EQU	(NATWID-1)	; offset of low byte

*********************************
* Per-process local static area:
*	ORG	$20000	; TOS just gives you what it gives you.
	EVEN
LOCBAS	EQU	*	; here pointer, local static base starts here.
ENTRY	BRA.W	START
	NOP		; buffers to 4 byte even
	NOP
A4SAVE	DS.L	1	; a place to keep A4 to A7 so we can return clean
A5SAVE	DS.L	1	; using it as pseudo-DP
A6SAVE	DS.L	1	; save A6 anyway.
A7SAVE	DS.L	1	; SP
* A7 will, of course, be our return address stack pointer
* A6 will be our parameter stack pointer
* A5 will be our local static base.
* A4 not yet dedicated, should be preserved if used.

* A0, A1, A2 are assumed volatile in BIOS.
* A3 is used here (volatile).

* D6, D7 are used here (volatile).
* D0, D1, D2 are assumed volatile in BIOS.
* D3, D4, and D5 should be preserved if used.
* 

* In this code, we handle the parameter stack directly.
* Items on the parameter stack
* are assumed to be full natural width (NATWID bytes),
* as a simplifying assumption.

*			  room for something
GAP1	DS.L	58	; to even 256 bytes, not much here yet
*
	DS.L	2	; a little bumper space
SSTKLIM	DS.L	16	; 16 levels of call, max
* 			; 68000 is pre-dec (decrement-before-store) push
SSTKBAS	DS.L	2	; a little bumper space
PSTKLIM	DS.L	32	; roughly 16 levels of call at two parameters per call
PSTKBAS	DS.L	2	; bumper space -- parameter stack is pre-dec


******************************
* Maintain local static area:
INITRT	MOVE.L	(A7)+,A0	; get the return address
	LEA	LOCBAS(PC),A3	; temporary local base (pseudo-DP)
	MOVEM.L	A4-A7,A4SAVE-LOCBAS(A3)	; Store away what the BIOS gives us.
	MOVE	A3,A5			; set up our local base (pseudo-DP)
	LEA	SSTKBAS-LOCBAS(A5),A7	; set up our return stack
	LEA	PSTKBAS-LOCBAS(A5),A6	; set up our parameter stack
	JMP	(A0)		; return via A0


*****************************************
* Low level I/O:
* This will be a focus point in porting, 
* Primarily OUTNWLN, OUTC, INCH

* Output the 8-bit number on the stack in binary (base two).
* For consistency, we are passing the byte in the lowest-order byte
* of a 32-bit word.
* Uses D6, D7.
OUTB8	MOVE.L	(A6),D6	; shift on memory is 16-bit, use register
	MOVE.W	#8,(A6)	; 8 bits to output, borrow parameter high word.
OUTB8L	LSL.B	#1,D6	; Get the leftmost bit of the lowest byte.
	BCS.S	OUTB81
OUTB80	MOVEQ.L	#'0',D7
	BRA.S	OUTB8D
OUTB81	MOVEQ.L	#'1',D7
OUTB8D	BSR.S	OUTCV
	SUBQ.W	#1,(A6)
	BNE.S	OUTB8L	; loop if not Zero
	SUBQ.L	#NATWID,A6	; drop parameter character
	RTS

* driver level code to output a new line
OUTNWLN	MOVEQ.L	#CR,D7
	BSR.S	OUTCV
	MOVEQ.L	#LF,D7
	BSR.S	OUTCV
	RTS

* OUTC and OUTCV are two entry points to the same routine.
OUTC	CLR.W	D7		; clear character high byte.
	MOVE.B	NATWID-1(A6),D7	; Get the character, high byte cleared
	ADDQ.L	#NATWID,A6	; drop it from parameter stack before falling through.
* Falls through!
* Common low-level hook: call with the character in D7. 
* This bit of glue calls the BIOS.
OUTCV	MOVE.W	D7,-(A7)	; Push the character on A7 where bconout wants it.
	MOVE.W	#devscrkbd,-(A7)	; push the device number
	MOVE.W	#bconout,-(A7)		; push the BIOS routine selector
	TRAP	#BIOSTRAP		; call into the BIOS
	ADDQ.L	#6,A7			; deallocate the BIOS parameters when done
	RTS
* Return from TRAP is RTE,not RTS.
* Can't rob TRAP's return.

* Output NUL-terminated string of bytes.
* Very low-level, no guard rails on length.
* A3 is preserved by Hatari BIOS.
OUTS	MOVE.L	(A6)+,A3	; get the string pointer
OUTSL	CLR.L	D7		; Prepare the high bytes.
	MOVE.B	(A3)+,D7	; get the byte, update the pointer
	BEQ.S	OUTDN		; if NUL, leave before outputting the character.
	BSR.S	OUTCV	; use OUTCV directly
	BRA.S	OUTSL	; next character
OUTDN	RTS


******************************
* intermediate-level library:
*
* We often will not need these, but we'll go ahead and define them:
*
* input parameters:
*   16-bit left, right
* output parameter:
*   16-bit sum
ADD16	MOVE.W	(A6)+,D7	; right (16-bit only)
	ADD.W	D7,(A6)		; add to left
	RTS			; *** all flags valid!! ***
*
* input parameters:
*   16-bit left, right
* output parameter:
*   16-bit difference
SUB16	MOVE.W	(A6)+,D7	; right (16-bit only)
	SUB.W	D7,(A6)		; subtract from left
	RTS			; *** all flags valid!! ***
*
* input parameters:
*   32-bit left, right
* output parameter:
*   32-bit sum
ADD32	MOVE.L	(A6)+,D7	; right 
	ADD.L	D7,(A6)		; add to left
	RTS			; *** all flags valid!! ***
*
* input parameters:
*   32-bit left, right
* output parameter:
*   32-bit difference
SUB32	MOVE.L	(A6)+,D7	; right 
	SUB.L	D7,(A6)		; subtract from left
	RTS			; *** all flags valid!! ***
*
* input parameters:
*   16-bit unsigned left, right
* output parameter:
*   32-bit sum
ADD16L	CLR.L	D7
	MOVE.W	2(A6),D7	; left (no sign extension)
	CLR.L	D6
	MOVE.W	(A6),D6		; right (no sign extension)
	ADD.L	D6,D7		; 32-bit sum
	MOVE.L	D7,(A6)		; 32-bit result on stack
	RTS			; *** X, N, Z valid ***
*
* input parameters:
*   16-bit left, right
* output parameter:
*   32-bit signed difference
SUB16L	CLR.L	D7
	MOVE.W	2(A6),D7	; left (no sign extension)
	CLR.L	D6
	MOVE.W	(A6),D6		; right (no sign extension)
	SUB.L	D6,D7		; 32-bit difference
	MOVE.L	D7,(A6)		; 32-bit result on stack
	RTS			; *** X, N, Z valid ***


************************************
* Start run-time, call program.
* Expects program to define PGSTRT:
*
START	BSR.W	INITRT
	NOP		; place to set breakpoint
*
	BSR.W	PGSTRT
*
DONE	NOP		; place to set breakpoint
	MOVEM.L	A4SAVE-LOCBAS(A5),A4-A7	; restore the monitor's A4-A7
	NOP
	NOP		; landing pad
	NOP
	NOP
* One way to return to the OS or other calling program
	CLR.W	-(A7)	; there should be enough room on the caller's stack
	TRAP	#1	;	quick exit

The process for assembling and testing will be the same as we have been using for Hatari up until now.

And, yes, please give moving the hexadecimal code to the rigging a try before you move on. Among other things, you need to figure out how you go about saving your steps, and how you go about naming the files you save at each step. Back in the early 1980s, we didn't really have the luxury of enough disk space to save every tiny step, but we do now. 

The problem we have now is not being short of disk space, it's how to find what we've saved, and we can do a lot to help that by figuring out ways to name the files we save so that we can find them later.

[JMR202410141705 repeat note: The bug I mentioned at the end of the binary output on 68000 example is still there in this code. If you haven't found it yet, take another look before you leave.]

You should note that, even though the character output function hook into the BIOS involves the TRAP instruction instead of branches or straight jumps, we're keeping the dual entry point approach for nearby code, as explained at the end of the 6809 code and in the comments here.

General numeric output requires multiplication and division, and I want to move on to that, but we need to switch tracks to look at getting input from the keyboard, starting with the 6800 on EXORsim.


(Title Page/Index)

 

 

 

 

 

 

No comments:

Post a Comment