Hexadecimal Output on the 68000,
without General Divide
(Register Allocation)
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.
[JMR202410251259 note: It turns out to have been two bugs, you can go take a look at the first one.] ]
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 a keyboard, starting with the 8-bit processors.
No comments:
Post a Comment