Saturday, September 28, 2024

ALPP 02-07 -- A Note on Byte Widening and Scratch Registers on the 6800, 6801, and 6809

A Note on Byte Widening and Scratch Registers
on the 6800, 6801, and 6809

(Title Page/Index)

 

I briefly mentioned, in the introduction to byte arithmetic chapter on the 6800, 6801, and 6809, the necessity of expanding byte data to 16-bit to use the 6801 and 6809's ADDD and SUBD instructions. I gave a more concrete example of byte widening in the 68000's chapter on byte arithmetic that we just finished.

In the 68000, the data registers are plenty wide enough. All we need to do, in the case of unsigned byte widening, is to make sure that, before we load the byte, enough of the register is cleared out to hold the target width.

In the case of signed byte widening for the 68000, we can use the sign-EXTend instruction after loading the byte value, which we will take a look at later.

(Other 16-bit and wider CPUs have load instructions that automatically sign-extend or zero-extend registers on load, instead.)

Let's look at how we can widen a byte on the 6800.

Hmm? What? The 6800 doesn't have a 16-bit add, you say?

Well, yeah, but, (mutter, mumble, ...) actually, when we cleared A out before we used ADCA #0 and SBCA #0, we were effectively performing a byte widening. We just used an immediate zero byte as the high byte of the widened source.

Why are you looking at me like that?

Sigh. Okay, remember, add with carry (ADC) allows us to expand the width of the ADD instruction. So we can synthesize a sixteen-bit add quite easily:

NLFT	FDB	132	; 132 in two bytes, high byte zero
NRT	FDB	188	; 188 in two bytes, high byte zero
RES	RMB	2	; 2-byte result
	...
	LDAB	NLFT+1	; Get the left low byte.
	LDAA	NLFT	; Get the left high byte.
	ADDB	NRT+1	; Add the right low byte.
	ADCA	NRT	; Add the right high byte.
	STAB	RES+1	; Store the result low byte.
	STAA	RES	; Store the result high byte.

And there's your 16-bit add. Just takes 6 instructions. >:-)

Comparing that to the 6801 and 6809, same declarations as above:

NLFT	FDB	132	; 132 in two bytes, high byte zero
NRT	FDB	188	; 188 in two bytes, high byte zero
RES	RMB	2	; 2-byte result
	...
	LDD	NLFT	; Get the left 16-bit word.
	ADDD	NRT	; Add the right 16-bit word.
	STD	RES	; Store the result 16-bit word.

3 instructions.

Okay, it takes a few more than 6 on the 6800 if we need to get the flags perfectly right, but we don't usually need to get the flags perfect for addition. Hardly ever, really. The Carry is correct, and so is the Negative. The Zero only reflects the high byte, which is wrong, but we have ways to handle that. So we're good for most purposes on the 6800 -- for addition. 

If we need to test for zero results, we can check that Carry is clear first, then OR the result low and high bytes together. If carry was set, we know the result was $10000 or greater, so, non-zero. ORing the low and high bytes together will leave non-zero if either was non-zero, so that completes the test. (I can make it sound simple, right? It's simple here because we have two copies of the result, one in memory and one in the accumulators. There is more to this, but I don't want to get too distracted. We'll come back to it.)

And in another point of fact, because loads and stores don't affect the carry flag, we could do all that math in 6 instructions just using a single accumulator instead of using both.

So, let's take that as a jumping-off point, declare the values we're adding as bytes again, and expand before adding. We'll start by declaring a byte of scratch RAM,  preferably in the direct page:

SCRTCH	RMB	1	; preferably in DP
	...
NLFT	FCB	132	; 132 in one byte
NRT	FCB	188	; 188 in one byte
RES	RMB	2	; 2-byte result
	...
	CLRA		; A high byte of zero
	STAA	SCRTCH	; Another high byte of zero
	LDAB	NLFT	; Get the left byte.
	ADDB	NRT	; Add the right byte.
	ADCA	SCRTCH	; Add the widening bytes.
	STAB	RES+1	; Store the result low byte.
	STAA	RES	; Store the result high byte.

So, do you believe me now?

Let's look at that on the 6801 (and 6809, by translating LDAB to LDB):

SCRTCH	RMB	2	; preferably in DP
	...
NLFT	FCB	132	; 132 in one byte
NRT	FCB	188	; 188 in one byte
RES	RMB	2	; 2-byte result
	...
	CLRA		; A high byte of zero
	LDAB	NRT
	STD	SCRTCH	; widened 16-bit word
	LDAB	NLFT	; Get the left byte, A still clear.
	ADDD	SCRTCH	; Add the widened 16-bit word.
	STD	RES	; Store the result high byte.

Because the D register operators need 16-bit operands, widening for the 6801 and 6809 requires two bytes of scratch RAM instead of one -- unless you're doing the math one byte at  a time, in which case you can still use the immediate zero argument to ADC as the source.

The above sources are slightly incomplete, so you might need to edit them a little to check what I am saying. Not much, but a little.

Subtraction is pretty similar, although we do care more about the flags after subtraction. 

By widening before we add or subtract, the addition and subtraction sequences can be made quite parallel. You might want to simply substitute SUBtract for ADD and SuBtract with Carry for ADd with Carry in the above code to see what happens.

I keep trying to go a bit further on the flags and such, but that is a distraction.

One thing that I need to make clear before we go, where the 68000 with lots of accumulator-equivalet data registers shows how natural byte widening is, having only one 16-bit accumulator makes byte widening on the 6801 and 6809 use scratch registers.

And I'm thinking we can now, after you've played with the subtraction code, move forward to doing proper 16-bit arithmetic

>:->


(Title Page/Index)

 

No comments:

Post a Comment