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
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
No comments:
Post a Comment