Wednesday, September 11, 2024

ALPP 02-XX -- Introduction to Address Math on the 6800, 6801, 6809, and 68000

This attempt at a chapter went sideways. Keeping it for the records.
Go here, instead: https://joels-programming-fun.blogspot.com/2024/09/alpp-02-05-introduction-to-byte-arithmetic-6800-6801-6809.html

Introduction to Address Math
on the 6800, 6801, 6809, and 68000

(Title Page/Index)

 

In the last chapter, we saw how the 68000 provides similar advanced addressing features to the 6809's.

Now we want to take an introductory look at general address math on all four processors.

Why do you need to do address math?

We've seen one of the use cases, allocating and deallocating space on the stacks. It's also necessary when allocating and deallocating space for variables and other stuff that needs to stick around between calls to various routines.

It's also useful when accessing distinct fields within structured variables and other objects, and when linking such objects together.

The 6800 doesn't have much in the way of address math. INX and DEX are about it. So you have to use the more general integer math, which only comes in byte size on the 6800.

So you have to synthesize the general math, along with some specific useful cases of general math. That's what we'll look at first.

You've noticed by now that there is a carry bit in the 6800's processor state. It's the least significant bit of the state flags, which is convenient when trying to find out its state. But we don't need to know that for combining two 8-bit adds to make a 16-bit add, because we have the ADd Carry instruction that adds the carry bit leftover from the last operation in along with its specified operands.

ADD and ADC are binary operands. Binary operands on 680X and 680XX family CPUs specify one accumulator or register as both a source and a target, and one memory operand as the second source -- thus, in the case of ADD and ADC, add something in memory to a register.

So you can use the Carry bit to chain the addition of bytes together, pretty much the way you use the carry bit in ordinary arithmetic to chain columns together. 

(If you want, you can consider the addition to be to numbers in base 256, and think of each byte as a column. If that does anything for you. If not, forget I mentioned it.)

Here's how to add two 16-bit numbers on the 6800 when both are in memory, either in the direct page or in the extended absolute addressing area:

* Add two 16-bit numbers in memory and store them in memory:
*
ENTRY	JMP	ADDM16
*
N1	FDB	57738	; just an arbitrary number
N2	FDB	17715	; another arbitrary number
RES	RMB	3	; Enough room to hold a carry from the high bytes.
*
ADDM16	LDAB	N1+1	; do the low byte (right columns) first.
	ADDB	N2+1	; Sets the Carry flag if the result is too big to fit.
	STAB	RES+2	; LoaD and STore do not affect the Carry flag.
	LDAA	N1	; Now do the high bytes (left columns).
	ADCA	N2	; ADd in the Carry from the low bytes as well.
	STAA	RES+1	; Save the high byte away.
	LDAB	#0	; CLR would clear the carry.
	ROLB		; Move the carry in
	STAB	RES	; Save the carry in the low bit of the highest byte.
	NOP
	NOP

This is not the way we usually do this, but it is useful for stepping through and watching the columns get added together. 

There is no real reason for using A for the high byte. B could be used instead and, other than that, the code would not change. Using B only would have the potential advantage of preserving A.

This code would work as is on the 6801 and 6809, with the proviso that you would use LDA for LDAA and STA for STAA on the 6809. But, of course, it could be done 16 bits at once on both:

* Add two 16-bit numbers in memory and store them in memory:
*
ENTRY	JMP	ADDM16
*
N1	FDB	57738	; just an arbitrary number
N2	FDB	17715	; another arbitrary number
RES	RMB	3	; Enough room to hold a carry from the high bytes.
*
ADDM16	LDD	N1	; Both bytes at once
	ADDD	N2	; Sets the Carry flag if the result is too big to fit.
	STD	RES+1	; LoaD and STore do not affect the Carry flag.
	LDAB	#0	; CLR would clear the carry.
	ROLB		; Move the carry in
	STAB	RES	; Save the carry in the low bit of the highest byte.
	NOP
	NOP

... using STA instead of STAA at the end for the 6809 code.

As a reader challenge, work the 6809 code out. (Hardly worth calling a challenge, but do it anyway.)

And, especially if you don't feel comfortable that you know what's happening, take the time to play with the code

On the 68000, of course we can do it a byte at a time or 16 bits at once, or ... 

Wait a minute. 

If we do it a byte at a time or even 16 bits at once, things get a little weird with the 68000. It's intended as a 16-bit/32-bit processor, and it kind of shows in places, particularly when using 24-bit results and other odd sizes.

In the 68000, the Carry function is split up between the X flag, used for eXtending multiple precision additions and subtractions, and the C flag, used for testing and branching. Instead of ADC and SBC, there is ADDX and SUBX, and ADDX and SUBX operate only on registers or in predecrement mode on numeric strings in memory. (Interestingly, the 68000 adds a CMPM instruction, to operate only in postincrement mode on numeric strings in memory.)

And then there is the even address requirements for the 68000. If you store or load by 16 bits or 32, you have to access memory on an even (divisible by 2) boundary.

Sounds wonky?

Yes and no. Let's look at what that means for the 16+1 bit math from above:

* Add two 16-bit numbers in memory and store them in memory:
* Both of these are messy because we are doing odd addresses.

Fix these***

*
ENTRY	JMP	ADDM16W
*
	EVEN
N1	DC.W	57738	; just an arbitrary number
N2	DC.W	17715	; another arbitrary number
RES	DS.B	3	; Enough room to hold a carry from the high bytes.
*
	EVEN
ADDM16W	CLR.B	D6	; pre-clear the carry byte
	MOVE.W	N1,D7	; One 16 bit word at a time
	ADD.W	N2,D7	; Sets the Carry and eXtend flags on carry.
	ROXL.B	#1,D6	; Save the eXtended carry in X first
	MOVE.B	D6,RES	; and move it into place (rotate on memory is word only).
	MOVE.B	D7,RES+2	; Save result low byte. Does not affect the X flag.
	ASR.W	#8,D7	; bring the high byte down
	MOVE.B	D7,RES+1	; because we can't save a 16-bit word to an odd address
	NOP
	NOP
	CLR.W	RES	; clear the result so we can do it again.
	CLR.B	RES+2
ADDM16	LEA	RES+3,A3	; point one beyond RES, same as MOVE #RES+3,A3
	LEA	N2+2,A2		; point one beyond N2, same as MOVE #N2+2,A2
	MOVE.B	-(A2),-(A3)	; copy low byte
	MOVE.B	-(A2),-(A3)	; copy high byte
	ADDQ.L	#2,A3		; back up on RES (100% sure clears X)
	LEA	N1+2,A2		; Actually, it was already there
	CLR.B	D7		; pre-clear the carry byte
	ADDX.B	-(A2),-(A3)	; add low bytes directly in memory
	ADDX.B	-(A2),-(A3)	; and high bytes directly in memory
	ROXL.B	#1,D7		; Save the eXtended carry in X
	MOVE.B	D7,-(A3)	; and move it into place (rotate on memory is word only).
	NOP
	NOP

So it takes more instructions than we want to think it would take -- because of the odd alignment issues. 

(FWIW, certain later members of the 680X0 family relax the alignment require about memory access, but the instruction set does not get dressed-out to fill in certain gaps for byte-level operands. So with those processors, we could improve the number of instructions a little, but not completely to the degree the 6809 allows. I won't look at those CPUs at this point, we have more important things to do.)

But, wait! (There's more ...)

Addresses on a 68000 are not 16 bit. 

Well, unless your runtime is only using a 64 K page within the total memory space or something like that. But that's a story for another time.

And, addresses don't use the carry bit. It's modular math. They just wrap around.

(Wraparound is useful when adding negative offsets, but requires some care in pointer comparison.) 

Hmm.  The 6800 and 6801 code is easy enough to read through and remove the part extracting and saving the carry, but the 68000 code needs another look.

* Add two 16-bit numbers in memory and store them in memory:
* Much less messy with even addresses.
*
ENTRY	JMP	ADDM16W
*
	EVEN
N1	DC.W	57738	; just an arbitrary number
N2	DC.W	17715	; another arbitrary number
RES	DS.B	2	; No carry, keep it aligned
*
	EVEN
ADDM16W	CLR.B	D6	; pre-clear the carry byte
	MOVE.W	N1,D7	; One 16 bit word at a time
	ADD.W	N2,D7	; Add it.
	MOVE.W	D7,RES	; and store the result non-destructively.
	NOP
	NOP
	CLR.W	RES	; clear the result so we can do it again.
ADDM16B	LEA	RES+2,A3	; point one beyond RES, same as MOVE #RES+3,A3
	LEA	N2+2,A2		; point one beyond N2, same as MOVE #N2+2,A2
	MOVE.W	-(A2),-(A3)	; copy all at once
	ADDQ.L	#2,A3		; back up on RES, probably (100% sure) clears X
	LEA	N1+2,A2		; Actually, it was already there
* All this setup just so we can do this,
* one byte at a time to show how to chain adds on the 68000:
	ADDX.B	-(A2),-(A3)	; add low bytes directly in memory
	ADDX.B	-(A2),-(A3)	; and high bytes directly in memory
* Yes, ADDX can be done .W and .L widths, as well.
	NOP
	NOP


 

 


 

(Title Page/Index)

 

 

No comments:

Post a Comment