Tuesday, October 8, 2024

ALPP 03-04 -- Binary Output on the 68000, Left-to-right; Framework-by-include

 Binary Output on the 68000,
Left-to-right;
Framework-by-include

(Title Page/Index)

 

Having seen what we can do with binary output on the 6809, let's see what it looks like on the 68000. 

I'm having a hard time coming up with good opening lines. Maybe I should just put a link to the last chapter at top left like I've done in my -- attempts at -- novels. Heh.

Hey, the hardest part of assembly language is staying awake while you're doing all the repetitious stuff.  

In a desperate attempt to make this interesting, I'll point out that shifting a single byte in a register 1 bit on the 68000 requires a 2-byte opcode, as opposed to a 1-byte opcode on the 6800/6801 and 6809. 

Of course, that same 2-byte opcode on a 68000, with just a little variation in a couple of bit fields, can shift an entire 32-bit register up to 8 bits. Doing that on a 680X requires repeating a 6-byte sequence 8 times. 

Which is done more often? We'll see. But in this code, it's a single byte, 1 bit.

Don't forget to compare the source with the source for the 6809. While you're at it, you might even open up the code for the 6801 and 6800 to compare the progression.

***********************************************************************
* 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_rig68K.s

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


****************
* Program code:
*
* Output a 0
OUT0	MOVE.L	#'0',-(A6)
OUT01	BSR.W	OUTC
	RTS
*
* Output a 1 
OUT1	MOVE.L	#'1',-(A6)
	BRA.S	OUT01
* Rob code, shave a couple of bytes, waste a few cycles.
*
* Output the 8-bit binary (base two) number on the stack.
* For consistency, we are passing the byte in the lowest-order byte
* of a 32-bit word.
OUTB8	MOVE.L	D5,-(A7)	; save working register	
	MOVE.L	(A6),D5	; shift on memory is 16-bit
	MOVE.W	#8,(A6)	; 8 bits to output, borrow parameter high word.
OUTB8L	LSL.B	#1,D5	; Get the leftmost bit of the lowest byte.
	BCS.S	OUTB81
OUTB80	BSR.S	OUT0
	BRA.S	OUTB8D
OUTB81	BSR.S	OUT1
OUTB8D	SUBQ.W	#1,(A6)
	BNE.S	OUTB8L	; loop if not Zero
	SUBQ.L	#NATWID,A6	; drop parameter character
	MOVE.L	(A7)+,D5	; MOVEM.W would sign extend
	RTS
*
HEADLN	DC.B	CR,LF	; Put message at beginning of line
	DC.B	"Outputting $5A in binary:"	; 
	DC.B	CR,LF,NUL	; Put the binary output on a new line
*
*
*
*
PGSTRT	LEA	HEADLN(PC),A0
	MOVE.L	A0,-(A6)
	BSR.W	OUTS
	MOVE.L	#$5A,-(A6)	; byte to output
	BSR.W	OUTB8
	BSR.W	OUTNWLN
	RTS
*
	END	ENTRY

Save that as something like

outb8_68K.s

[JMR202410152008 IMPORTANT NOTE! There is a bug in this source. See below the rigging for more information.]

You might notice in the framework that having a lot of registers and being able to work on 32 bits at a time makes the code feel a bit less streamlined than the 6809 code. I certainly get that sense from it. Maybe it's just me.

***********************************************************************
* A simple run-time framework inclusion for 68000
* providing parameter stack and local base
* Version 00.00.00
* 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


*********************************
* 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.

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

** Output character on stack, preserving D7
*OUTC	MOVE.L	D7,-(A6)	; Preserve D7
*	CLR.W	D7		; clear character high byte.
*	MOVE.B	NATWID*2-1(A6),D7	; Get the character, high byte cleared
*	BSR.S	OUTCV
*	MOVE.L	(A6),D7		; restore D7
*	ADDQ.L	2*NATWID,A6	; drop saved D7 and character
*	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

Save this in the same directory, as under the name the inclusion directive specifies --

rt_rig68K.s

Given those names, you can assemble it with 

$ vasmm68k_mot -Ftos -no-opt -o outb8_68K.PRG -L outb8_68K.lst outb8_68K.s

and go through the processes for simulation on Hatari.

Pulling OUT0 and OUT1 into OUTB8 should be fairly straightforward, but using registers instead of the parameter stack, and then the parameter stack instead of registers might mean you have to think just a tad -- to satisfy yourself that it might be that easy.

And I'm going to leave that as an exercise for the reader. (Is that mean of me? ;-)

[JMR202410141655 addendum:]

LoL. There's a (subtle?) bug in here that I found when testing the INKEY step code. I typed in a few keys, the program properly reported the character and its binary and hexadecimal representations until I had typed quite a few, and -- BOOF!!

I got an alignment violation or address error violation or something.

You don't really notice these kinds bugs unless you execute a piece of code several times. So we wouldn't see it here. But it's one the kinds of bugs I've been warning about as we go.

See if you can find it, either by examining the code or by tracing through it, or by asking yourself, 

What kind of bug is likely to work for a little while, then cause some sort of addressing violation error?

I think I need to go back and add a deliberate debugging example, including an example debugging this one. And also a chapter introducing the sort of debugging tool that would make this bug easier to swat.

[JMR202410251124 note: So it turns out there are more than one bug, and it was kind of fortuitous that the address violation bug happened. See the debugging sessions.]

[JMR202410141655 addendum END.]

Does getting hexadecimal output on the 6800 sound like a good next project? 

Yes?

I'm glad you think so. Let's do it.


(Title Page/Index)

 

 

 

 

No comments:

Post a Comment