Binary Output on the 68000,
Left-to-right;
Framework-by-include
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.
No comments:
Post a Comment