Accumulating Integer Results
on the M6809, 6800, 6801, and M68000
So what is an accumulator?
When you're working through a problem of some kind, you generally need some place to keep a record of your current results. As an example, think of adding up a total:
8 + 5 + 2 + 7 + 4 = 26 (if I didn't mess up the math)
If you can do that in your head, you probably don't even think about it, but you probably do keep a running total:
8If you do that on a piece of paper in something like the way I just did it here, your "accumulator area" is the space to the right of the = sign next to the current number you're adding at each step.
+ 5 = 13
+ 2 = 15
+ 7 = 22
+ 4 = 26 (Okay, I didn't mess up the math)
But if you do it in you're head, unless you've trained yourself (or have natural photographic memory or something), you only keep the running total (the accumulated value) and the number you are currently adding, not the previous lines and their results. That current result is your effective accumulator in your head.
It's the natural current focus point of calculations.
In the 6800/6801 and the 6809, you have two small dedicated accumulators, usually called A and B. (In the 6801 and 6809, you can concatenate them together for a single, medium-sized Double accumulator D, for some operations, more on that later.)
In the 68000, you have eight large data registers which can be used as
accumulators, usually called D0 to D7. (Usually. Some assemblers use different
conventions.)
As a totally contrived sequence of operations, let's do the above in 6800 assembly language:
* Adding a sequence of constant values:
LDAA #8 ; Gotta start somewhere.
ADDA #5
ADDA #2
ADDA #7
ADDA #4
NOP ; landing pad for breakpoint
(6801 is the same, or can be.)
- LDAA is the mnemonic for the "Load accumulator A" (LoaD Accumulator A) instruction operation code.
- The # hash mark indicates that the numeric value is immediate.
- ADDA is for "ADD to accumulator A".
- NOP is a No-OPeration op-code, in this case, as noted in the comment, for a landing pad where I can set a breakpoint.
- And the notes after the semicolon are comments for human readers (mostly), and the computer (mostly) ignores them.
Now, this entire sequence is known in advance, and should usually be optimized
to a single instruction
LDAA #26 ; optimized version
But if we optimize away the operations now, we can't see what they are doing.
So we won't do that. Not yet.
If you are wondering where the result is supposed to show up, good.
Let's break out a debugger and trace it through:
% t on
% b 100a
Breakpoint set at 100A
% c 1000
0 A=00 B=00 X=0000 SP=00FF ------ 1000: 86 08 LDA #08 EA=1001 D=08
1 A=08 B=00 X=0000 SP=00FF ------ 1002: 8B 05 ADDA #05 EA=1003 D=05
2 A=0D B=00 X=0000 SP=00FF ------ 1004: 8B 02 ADDA #02 EA=1005 D=02
3 A=0F B=00 X=0000 SP=00FF ------ 1006: 8B 07 ADDA #07 EA=1007 D=07
4 A=16 B=00 X=0000 SP=00FF H----- 1008: 8B 04 ADDA #04 EA=1009 D=04
Breakpoint!
> 5 A=1A B=00 X=0000 SP=FF8A ------ 100A: 01 NOP
Looks good, right? Nothing happening in B, X, and SP. The H half-carry flag gets set and then goes away, we see the PC counting through the code, and the machine code is there with the assembler mnemonics.
(Above, EA is something we call the effective address of the operand, and D is the data operand referenced at the effective address, more on that later.)
But, wait, ... since when does 8 + 5 = what? D? And we don't see 26 in there at the end, or anywhere?
What went wrong? Computers are never wrong! (cough.)
Must be something we did.
Let's try a different sequence of numbers. Here's a well-known one:
1 + 2 + 3 + 4 + 5 + 6 = 21
Yep. ( 7 × 6 ) ÷ 2 is 21. Code:
LDAA #0 ; Clear A out.
ADDA #1 ; Add 1 to A.
ADDA #2 ; Add 2.
ADDA #3 ; Etc.
ADDA #4
ADDA #5
ADDA #6
NOP ; landing pad
That ought to work. Trace it in the debugger:
% b 100e
Breakpoint set at 100E
% t on
% c 1000
0 A=00 B=00 X=0000 SP=FF8A ------ 1000: 86 00 LDA #00 EA=1001 D=00
1 A=00 B=00 X=0000 SP=FF8A ---Z-- 1002: 8B 01 ADDA #01 EA=1003 D=01
2 A=01 B=00 X=0000 SP=FF8A ------ 1004: 8B 02 ADDA #02 EA=1005 D=02
3 A=03 B=00 X=0000 SP=FF8A ------ 1006: 8B 03 ADDA #03 EA=1007 D=03
4 A=06 B=00 X=0000 SP=FF8A ------ 1008: 8B 04 ADDA #04 EA=1009 D=04
5 A=0A B=00 X=0000 SP=FF8A ------ 100A: 8B 05 ADDA #05 EA=100B D=05
6 A=0F B=00 X=0000 SP=FF8A ------ 100C: 8B 06 ADDA #06 EA=100D D=06
Breakpoint!
> 7 A=15 B=00 X=0000 SP=FF8A H----- 100E: 01 NOP
Much better. Load in a 0 and the result in A is 0. Add 1 to get 1, add 2 to get 3, and 3 to get 6, add 4 to get A ...
Erk. Giving 8 + 5 = D a D grade is understandable. What grade do we give to 6 + 4 = A?
Heh. The computer is not (this time) wrong.
So let's talk about
Hexadecimal!
Simple debuggers like the one I'm using here display many results in
hexadecimal instead of decimal, to save time, space on the screen, writing
code, and just to make things more meaningful. (You doubt that last one? Trust
me on this.)
For the time being, and maybe a quick refresher, here's a quick hexadecimal-to-decimal conversion chart, up to 31ten:
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D |
E | F |
16 |
17 | 18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | 27 |
28 |
29 |
30 |
31 |
10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 1A | 1B | 1C | 1D | 1E | 1F |
So, 8 + 5 does, in fact, equal Dsixteen (D base sixteen) in the
usual (modern) way of writing hexadecimal.
And 6 + 4 does equal Asixteen (A base sixteen).
Yes, it's inconvenient when you've spent so much of your life getting used to decimal, but we'll try to take time to make sure we don't get lost in the hexadecimal.
So, from the conversion chart, 22ten is 16sixteen. Maybe
that's confusing, but let's look at why.
Remember that each column in base ten is a power of ten, increasing to the left. (That's most-significant column first.)
22ten is 2 times ten plus 2 times one:
twenty plus two, or 20ten + 2ten.
And each column in base sixteen is a power of sixteen, increasing to the left.
16sixteen is 1 times sixteen plus 6 times one:
sixteen plus six, or 10sixteen + 6sixteen.
(To help us not get confused, we can read that as "one-zero base sixteen plus
six base sixteen".)
So, working in hexadecimal (and checking in decimal),
8sixteen
+ 5sixteen = Dsixteen (13ten)
+ 2sixteen = Fsixteen (15ten)
+ 7sixteen = 16sixteen (16ten + 6ten = 22ten)
+ 4sixteen = 1Asixteen (16ten + 10ten = 26ten)
There's our 26!
And,
0sixteen
+ 1sixteen = 1sixteen (1ten)
+ 2sixteen = 3sixteen (3ten)
+ 3sixteen = 6sixteen (6ten)
+ 4sixteen = Asixteen (10ten)
+ 5sixteen = Fsixteen (15ten)
+ 6sixteen = 15sixteen (16ten + 5ten = 21ten)
And that's enough of non-decimal numeric bases for the moment.
For comparison, the 6809 version of the first sequence is almost the same in assembler:
LDA #8 ; 6809 mnemonic: LoaD accumulator A
ADDA #5 ; Mnemonic shared with 6800/6801.
ADDA #2
ADDA #7
ADDA #4
NOP ; landing pad for breakpoint
Looking at that in a 6809 simulator:
% b 100a
Breakpoint set at 100A
% t on
% c 1000
0 A=00 B=00 X=0000 Y=0000 U=0000 S=00FF P=00 -------- 1000: 86 08 LDA #$08
1 A=08 B=00 X=0000 Y=0000 U=0000 S=00FF P=00 -------- 1002: 8B 05 ADDA #$05
2 A=0D B=00 X=0000 Y=0000 U=0000 S=00FF P=00 -------- 1004: 8B 02 ADDA #$02
3 A=0F B=00 X=0000 Y=0000 U=0000 S=00FF P=00 -------- 1006: 8B 07 ADDA #$07
4 A=16 B=00 X=0000 Y=0000 U=0000 S=00FF P=00 --H----- 1008: 8B 04 ADDA #$04
Breakpoint!
> 5 A=1A B=00 X=0000 Y=0000 U=0000 S=00FF P=00 -------- 100A: 12 NOP
That looks a lot like the 6800 emulation session, which is no real surprise.
An extra index register (Y), an extra stack register (U), the direct page
register (P). But it's a long line already, so the effective address and data are left out.
And the accumulated sums are the same, as they should be.
And the 68000 version looks like this, using default register width instructions (which we will need to talk about later):
MOVEQ #8,D0 ; MOVEQ lets us ignore register width.
ADDQ #5,D0 ; Default register width is okay.
ADDQ #2,D0 ; D0 is equivalent to 6800 A? Not quite?
ADDQ #7,D0
ADDQ #4,D0
NOP ; landing pad for breakpoint
MOVEQ is for "MOVE Quick"; ADDQ is for "ADD Quick". Both are somewhat optimized versions of the move and add instructions. MOVEQ affects the entire register, so any register size for the following ADD instructions is okay for the range of numbers we are using here. (MOVEQ operands can be from -128 to 127; ADDQ operands can be from 1 to 8. Again, more later.)
With all the registers in the 68000, doing a one-line complete register dump becomes rather awkward. You'd need a screen able to show something like 300 characters per line, and then you'd wear your eyes out looking for what changed on each line. So you don't do that.
Here's what the top part of the screen looks like in the devpac debugger on the Hatari Atari ST emulator screen:
d0 = 00000000 .... a0 = 00000000 602E 0206 00E0 0030 0001 ..........
d1 = 00000000 .... a1 = 00000000 602E 0206 00E0 0030 0001 ..........
d2 = 00000000 .... a2 = 00000000 602E 0206 00E0 0030 0001 ..........
d3 = 00000000 .... a3 = 00000000 602E 0206 00E0 0030 0001 ..........
d4 = 00000000 .... a4 = 00000000 602E 0206 00E0 0030 0001 ..........
d5 = 00000000 .... a5 = 00000000 602E 0206 00E0 0030 0001 ..........
d6 = 00000000 .... a6 = 00000000 602E 0206 00E0 0030 0001 ..........
d7 = 00000000 .... a7 = 00000000 602E 0206 00E0 0030 0001 ..........
SR:0000 U ssp = 00000000 602E 0206 00E0 0030 0001 ..........
PC:00E00030 bra $E0004E
After the data register on each line is the ASCII interpretation of the
contents of the data register. Then, after the address register on each
line is the contents of ten bytes of memory in hexadecimal, then the
same ten bytes shown as if they were ASCII characters. SR is the status
register, ssp is the system stack pointer, PC is the program counter. And
that's a lot to take in.
Even without the extra interpretations and memory content, you can see the
problem, right?
If the debugger would just show you the registers that changed, that would be great. But I don't have one of those kinds of debuggers for the 68000.
I can pretend to be one, and I guess that will have to do for now. Here's what it
might look like:
% t on
% b 1800a
Breakpoint set at 0001800A
% c 18000
0 D0=00000000 A7=00017FF8 ----- 00018000: 7008 MOVEQ #8,D0
1 D0=00000008 A7=00017FF8 ----- 00018002: 5A40 ADDQ #5,D0
2 D0=0000000D A7=00017FF8 ----- 00018004: 5440 ADDQ #2,D0
3 D0=0000000F A7=00017FF8 ----- 00018006: 5E40 ADDQ #7,D0
4 D0=00000016 A7=00017FF8 ----- 00018008: 5840 ADDQ #4,D0
Breakpoint!
> 5 D0=0000001A A7=00017FF8 ----- 0001800A: 4E71 NOP
Now, you're probably wanting to try some of this fun stuff yourself!
For the hexadecimal math, I plan to show how to build a hexadecimal calculator as part of the tutorial, but that's a bit down the road, yet.
Until then, hexadecimal math is supported by many desktop calculators on personal and workstation computers. Look for such things as programmer mode, and you'll probably find binary and octal modes, as well, within the programmer mode.
Or, if you have a Unix or Linux OS workstation, you probably also have the Unix command-line arbitrary precision basic calculator utility, bc. That allows you to set the input and output conversion bases to arbitrary positive bases (greater than one), and can be fun and instructive to play with:
obase=16
ibase=16
8 + 8
10
8 + 5 + 2 + 7 + 4
1A
Or, the programming language Forth has a similar conversion base, but it's just one base for both input and output:
16 BASE ! ok
8 5 + 2 + 7 + 4 + . 1A ok
(You might find post-fix math confusing, I suppose.)
Many versions of Forth are available, I tend to use gforth, because it is available in the Debian and Ubuntu repositories.
Playing with hexadecimal math is fun, but what about using a debugger like I have shown above to learn assembler?
There are, of course, many ways to get hardware and/or software to practice on.
Hardware is good (if you have a way to get code onto the hardware), but I don't have room in our apartment for hardware.
So I am using Joe H. Allen's emulator EXORsim for the 6800 and 6809. I'm using a hack I did on EXORsim for the 6801. And I'm using the Atari ST emulator Hatari for the 68000. And I'll explain how to get each of these in the next chapter, since this chapter is long enough already.
What about other CPUs, like the 8080, Z-80, 6502, 8086, ARM, RISC-V, Power PC, 6805, ... ?
This is a fairly time-intensive project. Doing just the four I've already described above is going to be plenty to fill my plate.
But when I get to a good pause point, I plan to come back and add chapters to demonstrate code for some other processors.
So, let's see how to get the emulators (when I get the chapters ready).
First is EXORsim for the 6800 and 6809.
No comments:
Post a Comment