Monday, October 7, 2024

ALPP 03-02 -- Binary Output on the 6801, Left-to-right; Framework-by-include

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

(Title Page/Index)

 

We've done binary output on the 6800, installing a new separate assembler in the process, and and learning a little about how to use inclusion files in the process. 

Time to do it again on the 6801. Go ahead and check the changes in the code to optimize it for the 6801, and review the process for using my asm68c assembler. 

There are not a lot of changes in the code. The most visible might be the ability to save X off to the return address stack instead of temporaries in the direct page. After that is just being able to load and store 16 bits at a time through the D double accumulator, including immediate 16-bit values.

One thing essential is to enable the 6801 op-codes for asm68c. That is done by the 

OPT 6801

in the line before the one including the framework rigging with the EXP pseudo-op.

It's tempting to use this as an opportunity to clean up the binary output routine, but it's clean enough and I don't want to confuse you about reasons why the code should change. So this chapter will end up as primarily practice on the older simulator in EXORsim6801.

* simple 8-bit binary output for 6801
* using parameter stack,
* with test frame
* Joel Matthew Rees, October 2024
*
	OPT	6801
	EXP	rt_rig6801.asm
****************
* Program code:
*
* Output a 0
OUT0	LDAB	#'0
OUT01	JSR	PPSHD
	JSR	OUTC
	RTS
*
* Output a 1 
OUT1	LDAB	#'1
	BRA	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 low-order byte
* of a 16-bit word.
OUTB8	LDX	PSP	; parameter is at 0,X (low byte at 1,X)
	LDAB	#8	; 8 bits
	STAB	0,X	; Borrow the upper byte of the parameter.
OUTB8L	LSL	1,X	; Get the leftmost bit.
	BCS	OUTB81
OUTB80	BSR	OUT0
	BRA	OUTB8D
OUTB81	BSR	OUT1
OUTB8D	DEC	0,X
	BNE	OUTB8L	; loop if not Zero
	INX		; drop parameter bytes
	INX
	STX	PSP
	RTS
*
HEADLN	FCB	CR,LF	; Put message at beginning of line
	FCC	"Outputting $5A in binary:"	; 
	FCB	CR,LF,NUL	; Put the binary output on a new line
*
*
PGSTRT	LDX	#HEADLN
	JSR	PPSHX
	JSR	OUTS
	LDD	#$5A	; byte to output
	JSR	PPSHD
	JSR	OUTB8
	JSR	OUTNWLN
	RTS
*
	END	ENTRY
The framework rigging makes a bit more use of the 6801:
* A simple run-time framework inclusion for 6801
* providing parameter stack and local base
* Version 00.00.00
* Joel Matthew Rees, October 2024
*
* Essential control codes
LF	EQU	$0A	; line feed
CR	EQU	$0D	; carriage return
NUL	EQU	0
*
SCRLIN	EQU	$40	; More than a screen full of lines.
*
* Essential monitor ROM routines
XOUTCH	EQU	$F018
*
NATWID	EQU	2	; 2 bytes in the CPU's natural integer
*
*
	ORG	$80	; MDOS and EXbug docs say it should be okay here.
ENTRY	JMP	START
	NOP		; Just want even addressed pointers for no reason.
*
* These are the page zero context variables that must be
* saved and restored on process context switch.
* They must never be accessed except in leaf routines:
PSP	RMB	2	; parameter stack pointer
LBP	RMB	2	; local static variable base pointer
XWORK	RMB	2	; for stashing X during other very short operations
*
SSAVE	RMB	2	; a place to keep S so we can return clean
* End of page zero context variables.
*
*
	ORG	$2000	; MDOS says this is a good place for usr stuff
LOCBAS	EQU	*	; here pointer, local static base starts here.
NOENTRY	JMP	START
	NOP
	RMB	64	; room for something
	RMB	2	; a little bumper space
* Not much here
*
SSTKLIM	RMB	31	; 16 levels of call, max
SSTKBAS	RMB	1	; 6800 is post-dec (post-store-decrement) push
	RMB	2	; a little bumper space
PSTKLIM	RMB	64	; 16 levels of call at two parameters per call
PSTKBAS	RMB	2	; bumper space -- parameter stack is pre-dec
*
*
INITRT	LDX	#PSTKBAS	; Set up the run-time environment
	STX	PSP
	LDX	#LOCBAS
	STX	LBP
	PULX		; pop return address to X
	STS	SSAVE	; Save what the monitor gave us.
	LDS	#SSTKBAS	; Move to our own stack
	JMP	0,X	; return via X
*
*
*********************
* Low-level library:
*
* Only alters X
PPOPX	LDX	PSP
	LDX	0,X	; get value for X
	PSHX		; push it out of the way
	LDX	PSP
	INX
	INX
	STX	PSP
	PULX		; and bring it back.
	RTS
*
* Trashes A,B;
* X points to X value just pushed -- PSP top of stack -- at end
PPSHX	PSHX	; push X out of the way
	PULA
	PULB	; Falls through:
*
* X points to PSP top of stack at end
PPSHD	LDX	PSP
	DEX
	DEX
	STX	PSP
	STD	0,X
	RTS
*
* X points to PSP top of stack at end
PPOPD	LDX	PSP
	LDD	0,X
	INX
	INX
	STX	PSP
	RTS
*
* Keep this around in comments, 
* for reference for when we want to referance tables in code.
* Load a constant from the instruction stream into A:B, 
* continue execution after the constant.
* This is not self-modifying code, even though it feels like a trick
* and is playing with the return stack and instruction stream 
* in ways we wouldn't think we wanted to think we should.
* Call it a "necessary" bit of run-time syntactic sugar.
*
* Use it like this:
*	JSR	LD16I	; load D immediate
*	FDB	$1234	; "immediate" 16-bit value to load
*	JSR	SOMEWHERE ; or some other executable code.
*
*LD16I	TSX		; point to top of return address stack
*	LDX	0,X	; point into the instruction stream
*	LDAA	0,X	; high byte from instruction stream
*	LDAB	1,X	; low byte from instruction stream
*	INS		; drop the return address we don't need
*	INS
*	JMP	2,X	; return to the byte after the constant.
*
OUTNWLN	LDAA	#CR	; driver level code to output a new line
	BSR	OUTCV
	LDAA	#LF
	BSR	OUTCV
	RTS
*
OUTC	JSR	PPOPD	; get the character in B
	TBA		; put it where XOUTCH wants it.
	BSR	OUTCV	; output A via monitor ROM
	RTS
*
OUTCV	JMP	XOUTCH	; driver code for outputting a character
*
OUTS	JSR	PPOPX	; get the string pointer
OUTSL	LDAA	0,X	; get the byte out there
	BEQ	OUTDN	; if NUL, leave
	BSR	OUTCV	; use the same call OUTC uses.
	INX		; point to the next
	BRA	OUTSL	; next character
OUTDN	RTS
*
WAKETRM	LDAB	#SCRLIN
WAKETML	JSR	OUTNWLN
	DECB
	BNE	WAKETML
	RTS
*
******************************
* intermediate-level library:
*
* input parameters:
*   16-bit left, right
* output parameter:
*   16-bit sum
ADD16	LDX	PSP
	LDD	2,X	; left
	ADDD	0,X	; right
	STD	2,X	; sum
	INX		; adjust parameter stack
	INX
	STX	PSP
	RTS
*
* input parameters:
*   16-bit left, right
* output parameter:
*   16-bit difference
SUB16	LDX	PSP
	LDD	2,X	; left
	SUBD	0,X	; right
	STD	2,X	; difference
	INX		; adjust parameter stack
	INX
	STX	PSP
	RTS
*
*
************************************
* Start run-time, call program.
* Expects program to define PGSTRT:
*
START	JSR	INITRT
	JSR	WAKETRM
*
	JSR	PGSTRT
*
DONE	LDS	SSAVE	; restore the monitor stack pointer
	NOP		; remember to set a breakpoint here!
	NOP		; landing pad
	NOP
	NOP
	LDX	$FFFE	; alternatively, get reset vector
	JMP	0,X	; and reboot through it
*
* Anyway, if running in EXORsim,
* Ctrl-C should bring you back to EXORsim monitor, 
* but not necessarily to your program in a runnable state.

Why so little use of the 6801's improvements?

This one doesn't need the extensions so much, but, really, allowing us to work with the X register without saving it to static temporaries is one of those small things that is huge for controllers that have to make heavy use of interrupts.

Save it as

rt_rig6801.asm

and save the binary output function and test code in the same directory, as 

outb8_6801.asm

Then use the command line

asm68c -l2 outb8_6801.asm

 to assemble the code. 

If the listing doesn't seem to show obvious errors, use 

asm68c -l2 outb8_6801.asm > outb8_6801.list

to redirect the listing, and open the listing and the S1/S9 object .

outb8_6800.x

in a text editor. 

Remember to fix any errors in the assembly language source file, not the listing file!

Get a session of EXORsim6801 running in another terminal window with the --6801 option with

exor --6801 --mon

or 

./exor --6801 --mon

As with the 6800 session, at the EXORsim monitor's % prompt, hit the "l" (for "load") and hit return, and it will wait for you to paste the s1/s9 object code in. Select it all and copy and paste. Hit return one more time for good measure. This time, however, you have to hit CTRL-C to break out of the load mode.

You may have to hit return again at the end, to bring it back to the % prompt:

Type 'help'
% l
S10700807E212101B7
S109008400000000000072
S105008A000070
S11320007E2121010000000000000000000000000B
S113201000000000000000000000000000000000BC
S113202000000000000000000000000000000000AC
S1132030000000000000000000000000000000009C
S109204000000000000096
S11320460000000000000000000000000000000086
S11320560000000000000000000000000000000076
S11320660000000000000000000000000000000066
S11320760000000000000000000000000000000056
S11320860000000000000000000000000000000046
S11320960000000000000000000000000000000036
S10720A60000000032
S11320AACE20A8DF84CE2000DF86389F8A8E206562
S10520BA6E00B2
S11020BCDE84EE003CDE840808DF84383941
S10620C93C32336F
S10C20CCDE840909DF84ED00390A
S10C20D5DE84EC000808DF843904
S10C20DE860D8D0C860A8D08396B
S10A20E7BD20D5178D01395E
S10620EE7EF01865
S11020F1BD20BCA60027058DF40820F7399A
S10C20FEC640BD20DE5A26FA3961
S1102107DE84EC02E300ED020808DF8439F9
S1102114DE84EC02A300ED020808DF84392C
S1092121BD20AABD20FE52
S1062127BD21795A
S10E212A9E8A01010101FEFFFE6E0011
S10C2135C630BD20CCBD20E73901
S107213EC63120F58D
S1132142DE84C608E700680125048DE720028DECD1
S10C21526A0026F20808DF843952
S113215B0D0A4F757470757474696E67202435415C
S111216B20696E2062696E6172793A0D0A0075
S1132179CE215BBD20C9BD20F1CC005ABD20CCBD08
S10921892142BD20DE39F5
S903218F4C
PC set to 218f

Unknown record on line 41
% 

Again, unassemble the code, get memory dumps, and otherwise and satisfy yourself it's all there. Then step through it and do any necessary debugging:

% r
PC=218F A=00 B=00 X=0000 SP=FF8A CC=C0
% s 80
          0 A=00 B=00 X=0000 SP=FF8A ------          0080: 7E 21 21 JMP 2121  EA=2121        
>         1 A=00 B=00 X=0000 SP=FF8A ------          2121: BD 20 AA JSR 20AA                 


Type 'help'
% c


[... big empty gap ...]


Outputting $5A in binary:
01011010
:9
EXBUG 1.1

This time, instead of a pause, I've had the framework load the reset vector and jump in, which will start EXBUG and mess up what you have in memory. If you don't want that, copy the pause code in, or simply set a breakpoint and scroll back until you confirm that you got the binary output expected.

I'll note I had trouble getting the terminal's attention. If you can't get the terminal to respond, leave me a note. If enough people leave me a notes about it, I'll see about bringing the 6801 simulator into a fork of Joe's current code or something.

For the record, the minor optimization I mentioned at the end of the 6800 version of this can be brought straight into this code without change, of course, but doesn't benefit from the 6801's extensions. Give it at try. Copy the optimized OUTB8 over the OUT0, OUT1, and OUTB8 code above and test it.

One more thing I might mention is the 6801's LSLD and LSRD instructions that shift the whole 16-bit wide Double accumulator left or right one bit in one instruction. If you don't need the Zero flag, they are equivalent to an 8-bit SHift followed by a ROtate in the same direction, only saving 1 byte and 1 cycle. If you do need the Z flag, then, of course, it saves you some additional testing and branching, as I have mentioned about ADDs and SUBtracts on the 6800 vs. 6801. 

As another optional exercise, you might want to try using the 16-bit shifts to output 16 bits at a time. As another, you might want to try adding a parameter to insert an arbitrary character, say a colon or a space, between each 8-bit half, and consider whether it would be easier or clearer to do it 16 bits at a time or use the existing 8-bit routines to do the top half first and then the bottom.

Doing this all over on the 6809 may get old, but we do want to see how much of the 6809's fancy stuff we can use


(Title Page/Index)

 

 

 

 

No comments:

Post a Comment