Sunday, August 4, 2024

ALPP 01-10 -- Hello, World! (Not Yet on the Beach) -- 6800, 6801, 6809

Hello, World!
(Not Yet on the Beach)
6800, 6801, 6809

(Title Page/Index)

 

We've seen a little about how to work our way sequentially through a list of small integers.

Characters are essentially small integers. In the case of 7-bit US ASCII, they are -- or were -- 7-bit integers, with values ranging from 0 to 127. 

So a string of text is a list of small integers, serially accessed.

At least, it used to be so in the small computer world, back in the 1970s and early '80s. Then ligatures came to the PC world. That alone broke the 7-bit ASCII small integer paradigm. And then along came international character sets and mixed contexts and byte orders and assertions that 16 bits would be enough for all the useful characters of all modern languages together, ...

Don't get me wrong, the Unicode Consortium has been doing lots of useful work cataloging characters and their construction. And Unicode itself makes possible a lot more international communication than has been possible in the past.

But the existence of Unicode makes it clear that it is no longer possible to just consider characters to be small integers and be done with it. Nor can we consider text to be a list of small integers serially accessed and be done with it. Not really true, even when you consider that 32-bit integers are still small integers.

But it never really was true. Ligatures were only part of the problems that broke the paradigm, even before we started trying to take on all the languages of the world.

On the other hand, you could do a lot of useful work under the assumption of a list of small integers. That assumption has driven the success of Google and is at the heart of the "progress" of AI. There is a very large subset of the characters that we use and the text that we generate and use that does follow the paradigms of a sequence of small integers. 

And we still can do a lot of useful work this way, particularly in the limited world of retro computing. We just need to recognize up front that we there are a lot of Unicode characters beyond US ASCII that we won't be able to deal with.

We'll start here using the ASCII set that fits nicely in 7 bits, under the assumption that at least these characters are nice small integers. (And we may not actually get beyond that paradigm in any practical sense, in this primer.)

This subset usage is  made possible by our choice of platforms, although most of the computers that are now considered retro had fairly simple, compact character sets. 

(If we had chosen the Tandy Color Computer for the 6809, we would have a much more limited set of characters defined by the 6847 video display generator, not even true US ASCII.)

Our target for this exercise is to get a string of characters, such as 

  • "On the beach!"
  • "Hello World!" 
  • "My name is Mudd."
  • "Here we are now."

output where we can read it. 

Both the EXORciser and the Atari ST include low-level routines for outputting characters. If you can output characters and if you know how to construct a loop, you can output strings of text, as well.

The EXORciser also has low-level routines for outputting strings. The purpose of this tutorial is not to teach how to use the EXORciser monitor ROM, EXbug --  or the Atari BIOS, either -- but we will take a quick look at one of the string routines in EXbug, both to get a general idea of how to access such routines, and to get a general idea of what the routines were are going to put together for our purposes should do and should not do.

War story -- When I was first investigating Joe H. Allen's EXORsim (6800-only at the time), to see how useful it would be for testing my old fig-Forth source code transcriptions, I went looking for how to get character input and output. Couldn't find much, but I noted Joe's mention of the facts files being used by the simulator to figure out labels for disassembly. So I took a look inside that file.

From my previous experience (and the fig-FORTH source), I thought I should be looking for labels such as "INCH" and "OUTCH". And I found several suspicious labels in the facts file. 

In particular, these labels, part of what appears from the facts file to be a jump table that starts at $F000, caught my attention:

f012 code INBYTEV	Input byte with echo
f015 code INCHV		Input char with echo (strip bit 7)
f018 code OUTCH		Output character

If you disassemble the ROM code at $F000, you'll see that there is, indeed, a series of jump instructions starting at $F000: 

% u f000 
F000: 7E F5 58 COLDSTART  JMP $F558 [COLDBOOT Cold start (main reset)]			* Cold start
F003: 7E F7 89 ADDRPROMPT JMP $F789 [ADDRPRMT Prompt for addresses]			* Prompt for address
F006: 7E FA A7 HEXBIN     JMP $FAA7 [CVTHEX Convert ascii hex to binary]			* Convert hex to binary
F009: 7E F9 C0 CVTUPPER   JMP $F9C0 [CVTUPP Convert upper half to ascii hex]			* Convert upper half to ascii
F00C: 7E F9 C4 CVTLOWER   JMP $F9C4 [CVTLOW Convert lower half to ascii hex]			* Convert lower half to ascii
F00F: 7E FA 65 GETHEX     JMP $FA65 [GETHEX4 Get 4 hex digits from user into x]			* Get 4 hex digits from user into x
F012: 7E FA 8B INBYTEV    JMP $FA8B [INBYTE Input byte with echo unless AECHO is set]			* Input byte with echo
F015: 7E FA A0 INCHV      JMP $FAA0 [INCH Input character]			* Input char with echo (strip bit 7)
F018: 7E F9 DC OUTCH      JMP $F9DC [OUTCH Output character with NULs]			* Output character
F01B: 7E FA 24 OUTHEX1    JMP $FA24 [OUTHEX1 Output byte in hex ,x+]			* Output byte in hex ,x+
F01E: 7E FA 22 OUTHEX2    JMP $FA22 [OUTHEX2 Output 2 bytes in hex ,x+]			* Output 2 bytes in hex ,x++
F021: 7E FA 41 PCRLF      JMP $FA41 [PCRLF Print CR-LF]			* Print CR-LF
F024: 7E FA 33 PDATA      JMP $FA33 [PDATA Print CR-LF then string]			* Print CR-LF then string
F027: 7E FA 35 PDATA1     JMP $FA35 [PDATA1 Print string]			* Print string
F02A: 7E FA 26 PSPC       JMP $FA26 [PSPC Print space]			* Print space

Yes, this is a jump table. It's purpose is to present a reliable set of addresses to key functions.

Let's look at OUTCH, the entry at $F018, as an example. You see that it will jump from there to $F9DC. This seems like unnecessary jumping around, but the extra jump doesn't take much time compared to the character output function itself. 

If the version of the monitor ROM changes, the actual character output routine might move, say, to $F9D8. In that case, the jump at OUTCH will change to jump to $F9D8 instead of $F9DC, but any program that jumps to OUTCH will then jump from there to the right place without having to be updated. 

The ROM jump table provides a way for programs that use the functions inside the ROM to access those functions even when the insides of the ROM change -- as long as the jump table is maintained. (It can be maintained by re-assembling the monitor from the source code, or by patching it by hand. But the former is more reliable.)

Outputting a single character, 6800/6801:

After I disassembled the code that the OUTCH entry jumps to and examined it, I decided to give it a try. I used something like the following bit of code:

XOUTCH	EQU	$F018
*
ENTRY	LDAA	#'H	; the character to ouput
	JSR	XOUTCH	; Call the output routine in monitor ROM
	NOP		; landing pad
	NOP

Yeah. It's really short. Let's use it now.

EQU is a directive to the assembler, to EQUate a label to a value. In this case the label XOUTCH is defined as the OUTCH entry in the EXbug monitor ROM jump table. 

EQU produces no actual object code, only an entry in a symbol table.

The operand to the LDAA is a small integer, in this case the ASCII code for 'H'. You can put a trailing single quote ('H') on it to make it less disconcerting to human readers, but Motorola's assemblers don't care here. It's just a one-byte character, anything after the byte will be ignored.

JSR is Jump to SubRoutine, the general way to call subroutines in the 6800 and the 6801. It pushes the return address (the address of the first following NOP, in this case) on the return address stack pointed to by the stack pointer register, S. When the subroutine is done, it ends with a RTS -- ReTurn from Subroutine -- instruction, which pops the return address back off the stack and puts it back in the program counter, allowing the CPU to proceed with the instruction that follows the JSR.

How did S get to point to valid memory, by the way? The monitor ROM does that for us, but we won't want to depend on that in the future. I know it will be okay this time. But I'll show you how we can set up our own stack as soon as we've watched this tiny program run on the 6800 and 6801.

So, break out an EXORciser 6800 session, as we've done before, and assemble this at $2000. Disassemble it to be sure.

$ ./exor --mon
Load facts file 'facts'
'exbug.bin' loaded.
  EXBUG-1.1 detected
'mdos.dsk' opened for drive 0 (double sided)

OSLOAD...

Hit Ctrl-C for simulator command line.  Starting simulation...

>         0 A=00 B=00 X=0000 SP=00FF ------          0020: B6 E8 00 LDA E800                 

6800 Monitor: Ctrl-C to exit, 'c' to continue, or type 'help'
% a 2000
2000: XOUTCH	EQU	$F018
2000: *
2000: ENTRY	LDAA	#'H'	; the character to ouput
2002: 	JSR	XOUTCH	; output routine in monitor ROM
2005: 	NOP		; landing pad
2006: 	NOP
2007: 
% u 2000
2000: 86 48               LDA #$48
2002: BD F0 18            JSR $F018 [OUTCH Output character]
2005: 01                  NOP
2006: 01                  NOP
2007: 00                  ???
2008: 00                  ???
...
%

Set a breakpoint at the appropriate place, set tracing on, and, just for fun, step (s 2000) through the first instruction at $2000, to watch it load $48 (ASCII code for 'H') into accumulator A:

% b 2006
Breakpoint set at 2006
% t on
% s 2000

          0 A=00 B=00 X=0000 SP=00FF ------ ENTRY    2000: 86 48    LDA #48   EA=2001 D=48   
>         1 A=48 B=00 X=0000 SP=00FF ------          2002: BD F0 18 JSR F018                 

6800 Monitor: Ctrl-C to exit, 'c' to continue, or type 'help'
%

There it is in A.

Continue from there and watch it trace through the ROM routine and return. (Scroll to the right if you can't see what the CPU is doing in the listing.) --

% c

          1 A=48 B=00 X=0000 SP=00FF ------          2002: BD F0 18 JSR F018  EA=F018(XOUTCH) 

          2 A=48 B=00 X=0000 SP=00FD ------ XOUTCH   F018: 7E F9 DC JMP F9DC  EA=F9DC(OUTCH) 
          3 A=48 B=00 X=0000 SP=00FD ------ OUTCH    F9DC: 37       PSHB                     Output character with NULs
          4 A=48 B=00 X=0000 SP=00FC ------          F9DD: F6 FC F4 LDB FCF4  EA=FCF4(ACIA0) D=02 
          5 A=48 B=02 X=0000 SP=00FC ------          F9E0: C5 02    BITB #02  EA=F9E1 D=02   
          6 A=48 B=02 X=0000 SP=00FC ------          F9E2: 27 F9    BEQ F9DD  EA=F9DD        
          7 A=48 B=02 X=0000 SP=00FC ------          F9E4: B7 FC F5 STA FCF5  EA=FCF5(ACIA1) D=48 
          8 A=48 B=02 X=0000 SP=00FC ------          F9E7: 81 0D    CMPA #0D  EA=F9E8 D=0D   
          9 A=48 B=02 X=0000 SP=00FC ------          F9E9: 26 1B    BNE FA06  EA=FA06        
         10 A=48 B=02 X=0000 SP=00FC ------          FA06: 7D FF 02 TST FF02  EA=FF02(NULCTRL) 
         11 A=48 B=02 X=0000 SP=00FC ---Z--          FA09: 2A F9    BPL FA04  EA=FA04        
         12 A=48 B=02 X=0000 SP=00FC ---Z--          FA04: 33       PULB                     
         13 A=48 B=00 X=0000 SP=00FD ---Z--          FA05: 39       RTS                      

         14 A=48 B=00 X=0000 SP=00FF ---Z--          2005: 01       NOP                      

Breakpoint!
H>        15 A=48 B=00 X=0000 SP=00FF ---Z--          2006: 01       NOP                      

6800 Monitor: Ctrl-C to exit, 'c' to continue, or type 'help'
% 

And we see the H output somewhere in there, in this case, right after the breakpoint is reported.

Just for grins, read through the code that it executed in the ROM routine and see if you can guess what it is doing.

Also just for fun and enlightenment, let's take a look at the memory pointed to by S. Type "m fc" and hit return until we've gone far enough, and then hit Ctrl-C once:

% m fc
00fc 00 
00fd 00 
00fe 20 
00ff 05 
0100 00 
0101 00 
%

You can see the return address, $2005, stored from $00fe to $00ff. Think about what address was in S when, and what that means for a minute.

I'll wait.

Got it, right? 

No?

Okay, I guess it isn't all that obvious. :-*

On the 6800 and 6801, S will be pointing to the next available byte on the stack any time you try to look at it. That's kind of opposite the usual approach in the industry, but it works, with a few caveats which I will return to later.

Again, the 6801 code will be exactly the same. And since no one has at this point attempted to optimize the monitor ROM for the 6801, the monitor ROM code should also be exactly the same, as well. The only difference would be that, if my emulation of the 6801 were accurate, it would run a bit faster. I'm not going to urge you check the code on the 6801 for this one unless you want the practice.  

[JMR202408181632 edit:]

If you do, you'll run into a problem I had forgotten about. If I get around to updating EXORsim6801, the problem should go away, but until then you'll need some workarounds. Don't forget to come back here if you go look at that now.

[JMR202408181632 edit-end.]

Continuing with the war story, now that Joe has added exor09 to his simulator, I have dug into the facts file for the 6809, and it is (currently) exactly the same as the 6800 facts file. Disassembling the ROM code for the 6809 shows that Motorola's engineers have made quite an effort to avoid changing entry points and effects of code, in spite of the 6809 providing much better native approaches for much of it.  

And, looking at the M6809 EXORciser's User's Guide, the jump table and some other entry points are now documented in the User's Guide, where they weren't in the M6800 EXORciser's User's Guide. So we have a more complete description of the routines.

If you download the M6809 EXORciser's User's Guide, you can find descriptions of the supported entry points in section 3-7, beginning on page 3-9, about p. 50 of the PDF I have.

[JMR202410141236 Note: I haven't explored all of the facts files yet, but I have found a parameter definition that should be different -- AECHO. Based on that, we should be a bit wary of assuming too much. I haven't checked much of the variables and parameters. The entry points I've checked so far do match.]

Outputting a single character, 6809:

The 6809 source code will use the 6809 LDA mnemonic, and otherwise will be the same as the 6800:

$ ./exor09 --mon
Load facts file 'facts09'
'exbug09.bin' loaded.
  EXBUG09-2.1 detected
'mdos09.dsk' opened for drive 0 (double sided)

OSLOAD...

Hit Ctrl-C for simulator command line.  Starting simulation...

>         0 A=00 B=00 X=0000 Y=0000 U=0000 S=00FF P=00 --------            0020: 86 10        LDA #$10                   

6809 Monitor: Ctrl-C to exit, 'c' to continue, or type 'help'
% a 2000
2000: XOUTCH	EQU	$F018
2000: *
2000: ENTRY	LDA	#'H'	; the character to ouput
2002: 	JSR	XOUTCH	; output routine in monitor ROM
2005: 	NOP		; landing pad
2006: 	NOP
2007: 
% u 2000
2000: 86 48               LDA #$48
2002: BD F018             JSR $f018
2005: 12                  NOP 
2006: 12                  NOP 
2007: 00 00               NEG $00
2009: 00 00               NEG $00
...
% b 2006 Breakpoint set at 2006 % c 2000 Breakpoint! H 28 A=48 B=00 X=0000 Y=0000 U=0000 S=00FF P=00 -----Z-- 2005: 12 NOP > 29 A=48 B=00 X=0000 Y=0000 U=0000 S=00FF P=00 -----Z-- 2006: 12 NOP 6809 Monitor: Ctrl-C to exit, 'c' to continue, or type 'help'
%

Woops! Forgot to turn tracing on. (Deliberately? ;-) But you can see the 'H' output there. 

Turn tracing on and do it again (and scroll to the right in the listing below to watch what the CPU is doing): 

% t on
% c 2000

         29 A=48 B=00 X=0000 Y=0000 U=0000 S=00FF P=00 -----Z-- ENTRY      2000: 86 48        LDA #$48                   
         30 A=48 B=00 X=0000 Y=0000 U=0000 S=00FF P=00 --------            2002: BD F018      JSR $f018                  
         31 A=48 B=00 X=0000 Y=0000 U=0000 S=00FD P=00 -------- XOUTCH     F018: 16 0092      LBRA $f0ad                 
         32 A=48 B=00 X=0000 Y=0000 U=0000 S=00FD P=00 --------            F0AD: 7D FF67      TST $ff67   EA=FF67 D=00   
         33 A=48 B=00 X=0000 Y=0000 U=0000 S=00FD P=00 -----Z--            F0B0: 26 76        BNE $f128                  
         34 A=48 B=00 X=0000 Y=0000 U=0000 S=00FD P=00 -----Z--            F0B2: 8D 74        BSR $f128                  
         35 A=48 B=00 X=0000 Y=0000 U=0000 S=00FB P=00 -----Z--            F128: 17 00AC      LBSR $f1d7                 
         36 A=48 B=00 X=0000 Y=0000 U=0000 S=00F9 P=00 -----Z--            F1D7: 34           PSHS B                     
         37 A=48 B=00 X=0000 Y=0000 U=0000 S=00F8 P=00 -----Z--            F1D9: F6 FCF4      LDB $fcf4   EA=FCF4(ACIA0) D=02 
         38 A=48 B=02 X=0000 Y=0000 U=0000 S=00F8 P=00 --------            F1DC: C5 02        BITB #$02                  
         39 A=48 B=02 X=0000 Y=0000 U=0000 S=00F8 P=00 --------            F1DE: 27 F9        BEQ $f1d9                  
         40 A=48 B=02 X=0000 Y=0000 U=0000 S=00F8 P=00 --------            F1E0: B7 FCF5      STA $fcf5   EA=FCF5(ACIA1) D=48 
         41 A=48 B=02 X=0000 Y=0000 U=0000 S=00F8 P=00 --------            F1E3: 35           PULS PC,B                  
         42 A=48 B=00 X=0000 Y=0000 U=0000 S=00FB P=00 --------            F12B: 34           PSHS B,A                   
         43 A=48 B=00 X=0000 Y=0000 U=0000 S=00F9 P=00 --------            F12D: 84 7F        ANDA #$7f                  
         44 A=48 B=00 X=0000 Y=0000 U=0000 S=00F9 P=00 --------            F12F: 81 0D        CMPA #$0d                  
         45 A=48 B=00 X=0000 Y=0000 U=0000 S=00F9 P=00 --------            F131: 26 15        BNE $f148                  
         46 A=48 B=00 X=0000 Y=0000 U=0000 S=00F9 P=00 --------            F148: F6 FF02      LDB $ff02   EA=FF02(NULCTRL) D=00 
         47 A=48 B=00 X=0000 Y=0000 U=0000 S=00F9 P=00 -----Z--            F14B: 7D FF67      TST $ff67   EA=FF67 D=00   
         48 A=48 B=00 X=0000 Y=0000 U=0000 S=00F9 P=00 -----Z--            F14E: 27 ED        BEQ $f13d                  
         49 A=48 B=00 X=0000 Y=0000 U=0000 S=00F9 P=00 -----Z--            F13D: C4 7F        ANDB #$7f                  
         50 A=48 B=00 X=0000 Y=0000 U=0000 S=00F9 P=00 -----Z--            F13F: 5A           DECB                       
         51 A=48 B=FF X=0000 Y=0000 U=0000 S=00F9 P=00 ----N---            F140: 2B 0E        BMI $f150                  
         52 A=48 B=FF X=0000 Y=0000 U=0000 S=00F9 P=00 ----N---            F150: 4F           CLRA                       
         53 A=00 B=FF X=0000 Y=0000 U=0000 S=00F9 P=00 -----Z--            F151: 35           PULS PC,B,A                
         54 A=48 B=00 X=0000 Y=0000 U=0000 S=00FD P=00 -----Z--            F0B4: 7D FF37      TST $ff37   EA=FF37 D=00   
         55 A=48 B=00 X=0000 Y=0000 U=0000 S=00FD P=00 -----Z--            F0B7: 27 DA        BEQ $f093                  
         56 A=48 B=00 X=0000 Y=0000 U=0000 S=00FD P=00 -----Z--            F093: 39           RTS                        
         57 A=48 B=00 X=0000 Y=0000 U=0000 S=00FF P=00 -----Z--            2005: 12           NOP                        

Breakpoint!
H>        58 A=48 B=00 X=0000 Y=0000 U=0000 S=00FF P=00 -----Z--            2006: 12           NOP                        

6809 Monitor: Ctrl-C to exit, 'c' to continue, or type 'help'
%

We can see that the 6809's path through the monitor ROM is different, and contains instructions unique to the 6809. Just for curiosity, let's look at the 6809's stack after the code is run:

% m 00f8
00f8 00 
00f9 48 
00fa 00 
00fb f0 
00fc b4 
00fd 20 
00fe 05 
00ff 00 
0100 00 
0101 00 
% 

We can see that much more information has been stored on the stack during the run for some reason. (More registers to save? I'm guessing that the 6809 is doing a bit more than would be necessary, in order to maintain compatibility with the EXORciser's 6800-based code.)

We should note that the return address to our code has been stored from $00fd to $00fe. The 6809 varies from the 6800 in stack discipline, always pointing to the last byte pushed instead of to the next available byte. We'll discuss this later, as well.

Now let's try outputting a full string of text on the 6800/6801.

As I mentioned earlier, in the M6809 EXORciser's User's Guide, we find a more complete description of the EXbug routines for outputting strings. We don't intend to use these routines extensively, for reasons I'll explain later, but we will take a quick look at them.

XPDATA and XPDAT1 are described on p. 3-35, about p. 56 in the PDF I have. These correspond to PDATA and PDATA1 in Joe's facts file. 

Specific points of usage are that the string should be terminated by EOT (ASCII 4), and that the X index register should point to the beginning of the string when you call the routine.

Providing PDATA as an entry point that outputs a carriage return/line feed in front of a string is a bit of a byte-count-saving technique, which we will ignore, to keep our excursion into the ROMs short. We'll just use the more general PDATA1 and insert CR/LF characters as necessary to separate the strings from the debugger output.

And, as I said, this time we'll provide our own stack. The Reserve Memory Block directive, RMB, is useful for this.

The assembler keeps track of where the next instruction should be allocated in memory. This pointer is sometimes called the "here" counter or pointer, as in "You are here.".

In effect, the RMB directive adds its argument to the assembler's here pointer, without recording any instructions, constants, or data in the gap. 

A linker or loader may later fill the gap in with zeroes or some other constant, or may just leave it with whatever might have been there before, essentially "garbage" data. You can't rely on what might be in there.

You'll note that the stack's label is at the end of the area allocated for the stack, not the beginning. This is because data is pushed down in memory, most recently pushed data going below what was there before. Also, you'll note that , for the 6800, the initial stack pointer is pointing to the first place available to push a byte to. 

Another note, Joe's interactive assembler doesn't support having the assembler calculate the size of the stack, which is why I commented that line out and replaced it with a pre-calculated size.

I've braced the string in CR/LF pairs to make it more visible in the output. "Sekai yo, yai!" is a Latinization of (or, Rōmaji for) 「世界よ、ヤイ!」 -- a rough approximation of "Hey, World, Hello!" in Japanese. Choose your own phrase to put in here. 

Placement of the text after the stack was deliberate, to avoid the text being overwritten in the unlikely occurrence of a stack overflow.

And don't forget that the monitor ROM routines expect the End Of Text character at the end of the string.

XPDAT1	EQU	$F027	; string output, terminated by EOT
EOT	EQU	$04	; $04 is decimal 4
LF	EQU	$0A	; line feed
CR	EQU	$0D	; carriage return
*
NATWID	EQU	2	; 2 bytes in the CPU's natural integer
*
ENTRY	JMP	START
* (EXORsim apparently doesn't want to calculate RMB arguments.)
*	RMB	16*NATWID-1
	RMB	31	; 16 levels of call minus any saved registers, max
STKBAS	RMB	1	; 6800 is post-dec (post-store-decrement) push
SAVES	RMB	2	; a place to keep S so we can be clean
HELLO	FCB	CR,LF	; Put message at beginning of line
	FCB	"SEKAI YO, YAI!"	; Whatever the user wants here.
	FCB	CR,LF,EOT	; Put the debugger's output on a new line.
*
START	STS	SAVES	; Save what the monitor gives us.
	LDS	#STKBAS	; Move to our own stack
	LDX	#HELLO	; point to the string
	JSR	XPDAT1	; output it
DONE	LDS	SAVES	; restore the stack pointer
	NOP
	NOP		; landing pad

Assemble at $2000. Set the breakpoint at the DONE label, just after the return from the call to XPDAT1 this time, but don't turn tracing on. Single step a few instructions to watch S and X get set. Then continue (c) from there to the breakpoint, and single step after the breakpoint to see S restored:

$ ./exor --mon
Load facts file 'facts'
'exbug.bin' loaded.
  EXBUG-1.1 detected
'mdos.dsk' opened for drive 0 (double sided)

OSLOAD...

Hit Ctrl-C for simulator command line.  Starting simulation...

>         0 A=00 B=00 X=0000 SP=00FF ------          0020: B6 E8 00 LDA E800                 

6800 Monitor: Ctrl-C to exit, 'c' to continue, or type 'help'
% a 2000
2000: XPDAT1	EQU	$F027	; string output, terminated by EOT
2000: EOT	EQU	$04	; $04 is decimal 4
2000: LF	EQU	$0A	; line feed
2000: CR	EQU	$0D	; carriage return
2000: *
2000: NATWID	EQU	2	; 2 bytes in the CPU's natural integer
2000: *
2000: ENTRY	JMP	START
2003: * (EXORsim apparently doesn't want to calculate RMBs.)
2003: *	RMB	16*NATWID-1
2003: 	RMB	31	; 16 levels of call minus any saved registers, max
2022: STKBAS	RMB	1	; 6800 is post-dec (post-store-decrement) push
2023: SAVES	RMB	2	; a place to keep S so we can be clean
2025: HELLO	FCB	CR,LF	; Put message at beginning of line
2027: 	FCB	"SEKAI YO, YAI!"	; Whatever the user wants here.
2035: 	FCB	CR,LF,EOT	; Put the debugger's output on a new line.
2038: *
2038: START	STS	SAVES	; Save what the monitor gives us.
Address at 2001 set to 2038
203b: 	LDS	#STKBAS	; Move to our own stack
203e: 	LDX	#HELLO	; point to the string
2041: 	JSR	XPDAT1	; output it
2044: DONE	LDS	SAVES	; restore the stack pointer
2047: 	NOP
2048: 	NOP		; landing pad
2049: 
% b 2044
Breakpoint set at 2044
% s 2000

          0 A=00 B=00 X=0000 SP=00FF ------ ENTRY    2000: 7E 20 38 JMP 2038  EA=2038(START) 
>         1 A=00 B=00 X=0000 SP=00FF ------ START    2038: BF 20 23 STS 2023                 

6800 Monitor: Ctrl-C to exit, 'c' to continue, or type 'help'
% s

          1 A=00 B=00 X=0000 SP=00FF ------ START    2038: BF 20 23 STS 2023  EA=2023(SAVES) D=00FF 
>         2 A=00 B=00 X=0000 SP=00FF ------          203B: 8E 20 22 LDS #$2022                

6800 Monitor: Ctrl-C to exit, 'c' to continue, or type 'help'
% s

          2 A=00 B=00 X=0000 SP=00FF ------          203B: 8E 20 22 LDS #$2022 EA=203C D=2022 
>         3 A=00 B=00 X=0000 SP=2022 ------          203E: CE 20 25 LDX #$2025                

6800 Monitor: Ctrl-C to exit, 'c' to continue, or type 'help'
% s

          3 A=00 B=00 X=0000 SP=2022 ------          203E: CE 20 25 LDX #$2025 EA=203F D=2025 
>         4 A=00 B=00 X=2025 SP=2022 ------          2041: BD F0 27 JSR F027                 


6800 Monitor: Ctrl-C to exit, 'c' to continue, or type 'help'
% c


Breakpoint!

SEKAI YO, YAI!
        321 A=04 B=00 X=2037 SP=2020 ---Z--          FA40: 39       RTS                      

>       322 A=04 B=00 X=2037 SP=2022 ---Z-- DONE     2044: BE 20 23 LDS 2023                 

6800 Monitor: Ctrl-C to exit, 'c' to continue, or type 'help'
% s

        322 A=04 B=00 X=2037 SP=2022 ---Z-- DONE     2044: BE 20 23 LDS 2023  EA=2023(SAVES) D=00FF 
>       323 A=04 B=00 X=2037 SP=00FF ------          2047: 01       NOP                      

6800 Monitor: Ctrl-C to exit, 'c' to continue, or type 'help'
% 

And we see the string output just after the breakpoint is reported.

While you're looking at the output string, play around a bit. Disassemble the code, look at the stack, check where the stack pointer was saved, that kind of stuff. Take notes of anything interesting. 

You might even try doing it again, setting trace mode on this time. But, if you do, be prepared to wade through a lot of trace output. And be prepared to have to keep a sharp look out for the characters of the string mixed in with the trace.

Again, the 6801 will do just the same as the 6800. Do it for practice, anyway, unless you're really impatient. 

[JMR202408181636 edit:]

But, as I have now noted above, you'll run into a problem I had forgotten about. If I get around to updating EXORsim6801, the problem should go away, but until then you'll need some workarounds. And, again, don't forget to come back here when done, if you go there now.

[JMR202408181636 edit-end.]

And let's try outputting a full string of text on the 6809.

The same 6800 source will actually work on the 6809 as it is. As it turns out, 15 and a half levels minus the bytes of saved registers is still plenty and then some. Give it a try and note how the return address gets saved starting one byte lower on the stack. You were expecting that by now, right?

But let's modify the source for the way the 6809 does the stack, and, while we are at it, use the 6809's PC-relative addressing modes a little:

* 6809 special version
XPDAT1	EQU	$F027	; string output, terminated by EOT
EOT	EQU	$04	; $04 is decimal 4
LF	EQU	$0A	; line feed
CR	EQU	$0D	; carriage return
*
NATWID	EQU	2	; 2 bytes in the CPU's natural integer
*
ENTRY	BRA	START	; Close enough for the short branch.
	RMB	32	; 16 levels of call minus any saved registers, max
*STKBAS	EQU	*	; Didn't want to tie STKBAS to SAVES
* But the interactive assembler doesn't recognize * as the here pointer.
SAVES	RMB	2	; a place to keep S so we can return cleanly
* And the interactive assembler wants EQU arguments to be known when used.
STKBAS	EQU	SAVES 	; 6809 is pre-dec (pre-store-decrement) push
HELLO	FCB	CR,LF	; Put message at beginning of line
	FCB	"SEKAI YO, YAI!"	; Whatever the user wants here.
	FCB	CR,LF,EOT	; Put the debugger's output on a new line.
*
START	STS	SAVES,PCR	; Save what the monitor gives us.
	LEAS	STKBAS,PCR	; Move to our own stack
	LEAX	HELLO,PCR	; point to the string
	LBSR	XPDAT1		; output it
DONE	LDS	SAVES,PCR	; restore the stack pointer
	NOP
	NOP		; landing pad

I'll explain about the asterisk and the here pointer and why we can't use them in this code later. For now, it should be enough to show that the initial stack pointer on the 6809 points 1 address beyond the actual stack allocation area. This is because the 6809 decrements the stack pointer before pushing a byte on the stack.

For a variety of reasons, we will be revisiting these concepts as we go. 

Let's take a look at this code running:

$ ./exor09 --mon
Load facts file 'facts09'
'exbug09.bin' loaded.
  EXBUG09-2.1 detected
'mdos09.dsk' opened for drive 0 (double sided)

OSLOAD...

Hit Ctrl-C for simulator command line.  Starting simulation...

>         0 A=00 B=00 X=0000 Y=0000 U=0000 S=00FF P=00 --------            0020: 86 10        LDA #$10                   

6809 Monitor: Ctrl-C to exit, 'c' to continue, or type 'help'
% a 2000
2000: * 6809 special version
2000: XPDAT1	EQU	$F027	; string output, terminated by EOT
2000: EOT	EQU	$04	; $04 is decimal 4
2000: LF	EQU	$0A	; line feed
2000: CR	EQU	$0D	; carriage return
2000: *
2000: NATWID	EQU	2	; 2 bytes in the CPU's natural integer
2000: *
2000: ENTRY	BRA	START	; Close enough for the short branch.
later = 2001
2002: 	RMB	32	; 16 levels of call minus any saved registers, max
2022: *STKBAS	EQU	*	; Didn't want to tie STKBAS to SAVES
2022: * But the interactive assembler doesn't recognize * as the here pointer.
2022: SAVES	RMB	2	; a place to keep S so we can return cleanly
2024: * And the interactive assembler wants EQU arguments to be known when used.
2024: STKBAS	EQU	SAVES 	; 6809 is pre-dec (pre-store-decrement) push
2024: HELLO	FCB	CR,LF	; Put message at beginning of line
2026: 	FCB	"SEKAI YO, YAI!"	; Whatever the user wants here.
2034: 	FCB	CR,LF,EOT	; Put the debugger's output on a new line.
2037: *
2037: START	STS	SAVES,PCR	; Save what the monitor gives us.
Offset at 2001 set to 35
203b: 	LEAS	STKBAS,PCR	; Move to our own stack
203e: 	LEAX	HELLO,PCR	; point to the string
2041: 	LBSR	XPDAT1		; output it
2044: DONE	LDS	SAVES,PCR	; restore the stack pointer
2048: 	NOP
2049: 	NOP		; landing pad
204a: 
% u 2000
2000: 20 35               BRA $2037
2002: 00 00               NEG $00
...
2020: 00 00               NEG $00
2022: 00 00               NEG $00
2024: 0D 0A               TST $0a
2026: 53                  COMB 
2027: 45                  ???A 
2028: 4B                  ???A 
% u 2037
2037: 10EF 8C E7           STS $2022,PCR
203B: 32 8C E4            LEAS $2022,PCR
203E: 30 8C E3            LEAX $2024,PCR
2041: 17 CFE3             LBSR $f027 [XPDAT1 Print data string (Enter with X)]
2044: 10EE 8C DA           LDS $2022,PCR
2048: 12                  NOP 
2049: 12                  NOP 
204A: 00 00               NEG $00
204C: 00 00               NEG $00
...
% b 2044
Breakpoint set at 2044
% s 2000

          0 A=00 B=00 X=0000 Y=0000 U=0000 S=00FF P=00 -------- ENTRY      2000: 20 35        BRA $2037                  
>         1 A=00 B=00 X=0000 Y=0000 U=0000 S=00FF P=00 -------- START      2037: 10EF 8C E7   STS $2022,PCR                

6809 Monitor: Ctrl-C to exit, 'c' to continue, or type 'help'
% s

          1 A=00 B=00 X=0000 Y=0000 U=0000 S=00FF P=00 -------- START      2037: 10EF 8C E7   STS $2022,PCR EA=2022(STKBAS) D=00FF 
>         2 A=00 B=00 X=0000 Y=0000 U=0000 S=00FF P=00 --------            203B: 32 8C E4     LEAS $2022,PCR                

6809 Monitor: Ctrl-C to exit, 'c' to continue, or type 'help'
% s

          2 A=00 B=00 X=0000 Y=0000 U=0000 S=00FF P=00 --------            203B: 32 8C E4     LEAS $2022,PCR                
>         3 A=00 B=00 X=0000 Y=0000 U=0000 S=2022 P=00 --------            203E: 30 8C E3     LEAX $2024,PCR                

6809 Monitor: Ctrl-C to exit, 'c' to continue, or type 'help'
% s

          3 A=00 B=00 X=0000 Y=0000 U=0000 S=2022 P=00 --------            203E: 30 8C E3     LEAX $2024,PCR                
>         4 A=00 B=00 X=2024 Y=0000 U=0000 S=2022 P=00 --------            2041: 17 CFE3      LBSR $f027                 

6809 Monitor: Ctrl-C to exit, 'c' to continue, or type 'help'
% c


Breakpoint!

SEKAI YO, YAI!
        587 A=04 B=00 X=2036 Y=0000 U=0000 S=2020 P=00 -----Z--            F069: 39           RTS                        
>       588 A=04 B=00 X=2036 Y=0000 U=0000 S=2022 P=00 -----Z-- DONE       2044: 10EE 8C DA   LDS $2022,PCR                

6809 Monitor: Ctrl-C to exit, 'c' to continue, or type 'help'
% s

        588 A=04 B=00 X=2036 Y=0000 U=0000 S=2022 P=00 -----Z-- DONE       2044: 10EE 8C DA   LDS $2022,PCR EA=2022(STKBAS) D=00FF 
>       589 A=04 B=00 X=2036 Y=0000 U=0000 S=00FF P=00 --------            2048: 12           NOP                        

6809 Monitor: Ctrl-C to exit, 'c' to continue, or type 'help'
% 
  • Copy it, assemble it, 
  • use the disassembler to make sure it assembled correctly; 
  • set the breakpoint, 
  • single-step a few to watch the stack pointer and the index register get set correctly, 
  • continue execution to watch the string get output; 
  • single-step to watch the S stack pointer get restored.

After that, check the stack memory with the (d)ump or (m)emory commands. (Remember, use Ctrl-C to get out of the memory change cycle.)

One more thing to play with -- Try leaving the EOT off the end of the string and see what happens. (I'll talk more about this later.)

This kind of playing is important. So important that I'll dedicate an entire chapter to it every now and then.

And, just for the record, in case you missed it above, we will be building our own string output routines.

This chapter has gotten kind of long, so we'll try to repeat this all on the Atari ST's 68000 in the next chapter. 

Or you could check out the workarounds I recommend for EXORsim6801 now, and look at the 68000 code after.


(Title Page/Index)

 

Saturday, July 27, 2024

ALPP 01-09 -- Sequentially Accessing a Simple List on the 6800, 6801, 6809, and 68000

Sequentially Accessing a
Simple List on the 6800, 6801, 6809, and 68000

(Title Page/Index)

 

Now that we've learned to treat that list of small integers as something like a list, let's access that list sequentially. Why? Because sometimes you want to work through a list in order.

We've done most of this before, so it should go quickly. (Right?)

Again, we are going to re-use source from the exercise we just finished, changing it slightly:

ENTRY	JMP	START
*
BYTTBL	FCB	8
	FCB	5
	FCB	2
	FCB	7
	FCB	4
*
START	LDX	#BYTTBL
	LDAB	0,X
	INX
	ADDB	0,X
	INX
	ADDB	0,X
	INX
	ADDB	0,X
	INX
	ADDB	0,X
	NOP
DONE	NOP

Don't you think that's a minor change? 

Heh. Well, I I have a reason for thinking so. 

Can you guess what it's going to do? Can you guess what INX means?

INX is a mnemonic for INcrement X. Now you know. And now you may be guessing what it does. 

Assemble and run it according to the patterns we've been using, referring to the previous chapters if you can't remember details:

  • Get a session of the 6800 version of EXORsim running with the --mon switch;
  • start an (a)ssembly at $1000, or maybe $2000 this time;
  • copy this source and paste it in to assemble it;
  • (d)isassemble it from appropriate addresses to see what assembled;
  • set a breakpoint at the landing pad NOP (the DONE label);
  • turn (t)racing on;
  • use the (c)ontinue command to run it from the ENTRY point. Or you can (s)tep through each instruction, instead, if you want.

Confirm that the summation occurs correctly as before, and watch the value of X as the CPU works through the code.

Now, you may notice, that inserting the INX instructions uses more code and more processor cycles. But it maintains a pointer in X to the currently interesting item in the list, which is what we are trying to do this time. Because we can. And because there will be times when that's what we want to do, and we will need to know how.

This is called the post-increment mode of addressing, since we INcrement X after each access. 

Each access? Well, we don't have to increment after the last access, unless our algorithm requires the pointer treatment to be uniform. Right now, we just want to look at the difference, more than use these in some explicit algorithm.

Why is it called post-increment access mode instead of post-access increment mode? I'm not sure. Post-access incrementing is what we are doing. But mathematicians can use strange grammar sometimes. (And strange grammars, as well, but that's a whole 'nuther story.)

A partial explanation can be found in the terms "post-inc" and "pre-dec", which are common jargon in discussions of low-level computer programming. In both cases, the redundant word "access" has been deleted:

  • post-access increment => post-access inc => post-inc
  • pre-access decrement => pre-access dec => pre-dec

And, yes, the technical literature then re-uses the coinage, including in the less-used pre-inc and post-dec modes that we will visit later on.

We can deal with this, I think?

The 6801 is again the same for this, both the source code and the object. It will be a bit quicker -- slightly reduced cycle counts for the 6801 on both the indexed mode operators and the INX instruction. It's a bit of a rinse-and-repeat, but go ahead and check the simulation on the 6801, to keep the processes fresh in your head.

INX on the 6809

You might think it surprising and counter-intuitive, but the 6809 has no INX instruction.

WHAT?!? No INX?!?!?!? 

So, of course, it has something better. So to speak.

Oh, it really is better, but if you start with a familiarity with the incrementing instructions, it might be easy to miss. 

You've seen the LEA instruction already, in relation to position independent addressing. Now see what you think of this:

	LEAX	1,X

Load into index register X the address 1 past the current address in X.

(If you define this as a macro called INX, then you have an INX instruction for the 6809, but I do not want to talk about macros yet. So just forget I mentioned it, okay? ;-P)

Why? you ask, would a person want to invoke all the complexities of LEA just to increment X?

Why not? That's basically what the address calculation unit is for, isn't it? -- calculating addresses?

Here's a version for the 6809:

ENTRY	JMP	START
*
BYTTBL	FCB	8
	FCB	5
	FCB	2
	FCB	7
	FCB	4
*
START	LEAX	BYTTBL,PCR
	LDB	0,X
	LEAX	1,X	; INX equivalent
	ADDB	0,X
	LEAX	1,X
	ADDB	0,X
	LEAX	1,X
	ADDB	0,X
	LEAX	1,X
	ADDB	0,X
	NOP
DONE	NOP

Give it a try.

Admittedly, LEAX 1,X on the 6809 is a 2-byte instruction, where INX on the 6800 and 6801 is a 1-byte instruction. And LEAX 1,X takes 5 clock cycles, where INX only takes 4 on the 6800 and 3 one the 6801.

So, ... Why?

Adding fancy instructions does take room in the op-code map. The index mode post-byte that the 6809 indexing modes requires takes another cycle to process, too. And Motorola developed the 6801 after the 6809, so it benefits from some improvements in circuitry design and layout that Motorola picked up in-between.

It's a definite question why Motorola never improved the 6809, but what I heard from probably reliable sources was that Motorola didn't want the 6809 eating into the 68000's market. 

And it would have. Initially. 

At similar memory cycle timing, for primarily 8-bit stuff, the 6809 can be faster than the 68000 -- if there is very little use of division and only light use of multiplication of integers larger than 8 bits. And re-using 6809 source code on the 68000 can be kind of tricky, if you aren't using a discipline like I will be demonstrating here once we get the basics down.So upgrading from the 6809 to the 68000 is not a given.

Well, so then Motorola would have been stuck with customers wanting upgraded 6809s, like the customers that asked for upgraded 6800s that didn't require conversion like the 6809 did, and management was worried about losing focus, if I understand correctly.

It's a legitimate worry when you don't have a lot of talented engineers in the pool available to hire from. But ... what about the 6805 with its weird 8-bit index register? And that even weirder bit-serial 6804 that you probably will never hear about? Why then did Motorola do those?

Incidentally, the series that started with the 6805, from my inexpert survey, was probably as profitable for Motorola as any other series, and was longer lived than most, including the 68HC11. Both are still hiding in a lot of the electronics you use every day. I want to do chapters for both of them at some point, but I need to find decent open source/libre emulators and support software. Or build my own.

(And there is a non-Motorola side-path I'm deliberately ignoring here. Maybe sometime later.)

Oh, there I go, getting distracted again.

So, yeah, LEAX 1,X does not seem to be a win. 

But LEAX 2,X instead of two INX instructions? Two bytes, either way. One instruction and just 5 cycles on the 6809, vs. two instructions and 6 cycles total for the 6801, 8 cycles total for the 6800. That's a win. And the 6809 has more magic in that index post-byte, to cover for the more common not-a-win case of incrementing by 1:

ENTRY	JMP	START
*
BYTTBL	FCB	8
	FCB	5
	FCB	2
	FCB	7
	FCB	4
*
START	LEAX	BYTTBL,PCR
	LDB	,X+
	ADDB	,X+	; INX implied
	ADDB	,X+
	ADDB	,X+
	ADDB	,X	; Following the 6800 code, no final increment
	NOP
DONE	NOP

Of course you're going to give this one a try and make sure it works as advertised.

Is it a win? 

In this source code, you take care of the post-access increment in the same instruction as the load or add. The assembler source makes it clear that the access is post-inc, whereas INX instructions are not always for post-inc accesses. Win for clarity, also saves a few cycles on each access, vs. the ADDB 0,X ; INX pair.

But converting 6800 source code to the 6809 becomes, through this kind of magic, shall we say, not straightforward. It will require allocating time for an engineer to work through the source, possibly adding bugs that will then need to be fixed. And much of the existing 6800 source code was definitely put together without any real discipline, and is thus hard to understand, and ...

In my way of thinking, that added engineering time could be considered an investment. More than one engineer will end up understanding the code. And it will be an opportunity preemptively attacking undiscovered bugs in the original code. 

But even today (especially today?), management (and accounting and boards of directors) do not seem to understand that kind of investment in intangible capital.

If you're interested in comparing the op-codes and cycle counts of the processors, do a web search for the programming manuals. Look for something like

Motorola 6801 programming manual

You should find the PDFs on Bitsavers and Internet Archives, and in other usual places. The 6809 even has an HTML manual on-line (Thank you, Maddes and the docs team.) 

No, actually, even if you don't think you are all that interested in details like byte and cycle counts, go get the manuals anyway. You need them. Don't forget the 68000 manual.

INX on the 68000

Let's go back and get the 68000 source from the most recent example:

	EVEN	
ENTRY	JMP	START
*
BYTTBL	DC.B	8	; byte data doesn't have to be aligned.
	DC.B	5
	DC.B	2
	DC.B	7
	DC.B	4
*
	EVEN		; But 68K code does have to be even aligned.
START	MOVE.L	#BYTTBL,A0
	MOVE.B	0(A0),D1
	ADD.B	1(A0),D1
	ADD.B	2(A0),D1
	ADD.B	3(A0),D1
	ADD.B	4(A0),D1
	NOP
DONE	NOP
* One way to return to the OS or other calling program
	clr.w	-(sp)	; there should be enough room on the caller's stack
	trap	#1		;	quick exit

Let's try that with a 68000 equivalent of INX:

	OPT LIST,SYMTAB	; Options we want for the stand-alone assembler.
	MACHINE MC68000	; because there are a lot the assembler can do.
	OPT DEBUG	; We want labels for debugging.
	OUTPUT
***********************************************************************
	EVEN	
ENTRY	JMP	START
*
BYTTBL	DC.B	8	; byte data doesn't have to be aligned.
	DC.B	5
	DC.B	2
	DC.B	7
	DC.B	4
*
	EVEN		; But 68K code does have to be even aligned.
START	LEA	BYTTBL(PC),A0
	MOVE.B	(A0),D1
	ADDQ.L	#1,A0	; INX equivalent
	ADD.B	(A0),D1
	ADDQ.L	#1,A0
	ADD.B	(A0),D1
	ADDQ.L	#1,A0
	ADD.B	(A0),D1
	ADDQ.L	#1,A0
	ADD.B	(A0),D1	; No trailing INX.
	NOP
DONE	NOP
* One way to return to the OS or other calling program
	clr.w	-(sp)	; there should be enough room on the caller's stack
	trap	#1		;	quick exit

So, do you see that? The 68000 actually has a direct equivalent of the INX instruction.

What is INX, really? It's an instruction to add 1 to the index register in the 6800/6801. If we had an ADDX instruction for the 6809, to ADD some source to the index register, INX would be the approximate equivalent of

	ADDX	#1	; => INX (theoretical)

We don't have an ADDX on the 6809, but we do have something like it on the 68000. (We actually have an ADDX on the 68000, but that's something rather different, which we will discuss later.) 

On the 68000, any address register could be our index register (equivalent of the 6809/68006801 X). Since we use A0 in the previous chapter, we'll use it now. (It agrees with stack order, by the way.) Here's an approximate equivalent of the 6800/1/9 INX instruction:

	ADD.L	#1,A0	; INX equivalent

ADD.L immediate, long add. Why long? Because addresses in the 68000 are long, unless you specifically know they are not. And we don't know that. But that means we have to waste bytes in the code for a 32-bit version of the small integer 1. In hexadecimal, the combined op-code and immediate argument would be 48 bits:

$D1FC
$0000
$0001

In binary,

1101000111111100
0000000000000000
0000000000000001

For the curious, the various fields of the instruction are

1101: add address
000: A0 is destination
111: size long (32 bit) operation
111 100: immediate source
0000000000000000: high 16 bits
0000000000000001: low 16 bits

That's a lot of zero bits, just to increment by 1. 

So Motorola defined the ADDQ instruction that I use above. ADDQ increments by any immediate value from 1 to 8, with the value specified internal to the single 16-bit instruction. In hexadecimal, the complete instruction is

$5288

In binary,

0101001010001000

That's 16 bits instead of 48. Again, for the curious, the various fields are

0101: add quick
001: data (number to add, 0 means 8)
0: addq (1 is subq)
10: size long (32 bit) operation
001: target is address register
000: A0

SUBQ is the corollary decrement.  And, by the way, either can target any register. (Memory, too.)

Refer back to the last chapter and 

  • Copy the source to a text file in the host OS;
  • Save it in your working directory with a name you'll maybe recognize, 8 characters or less;
  • Open a terminal command-line shell and change to your working directory;
  • Assemble it with vasm, don't forget the
    -Ftos TOS output and
    -no-opt optimization switches;
  • Start a Hatari session
  • In Hatari:
    • Hit Ctrl-C to drop out of GEM into the EmuTOS CPM shell;
    • Change to the working directory in EmuTOS;
    • Hit Alt-PAUSE to invoke the debugger;
    • Move with the mouse to the debugger in the command-line terminal and
      • Set a breakpoint (b) on TEXT segment entry;
      • Continue (c) execution in the EmuTOS shell;
    • Run the executable you created with vasm;
    • Use the mouse to go back to the debugger;
      • Disassemble the code to make sure it's all where it should be;
      • Find the address of the START label and disassemble from there, too;
      • Show the registers (r); and
      • Step (s) through the code, watching the index and sum change;
    • Continue (c) back to EmuTOS, or quit (q) back to the host OS.

Convinced?

We could also use LEA instead of ADDQ, like we did on the 6809. The instruction would look like

	LEA	1(A0),A0

It takes an extra word (two bytes) of code, as compared with ADDQ, and it takes more cycles to complete, but it can be done. 

We are more interested in the post-inc mode:

	ADD.B	(A0)+,D1

Let's do that now:

	OPT LIST,SYMTAB	; Options we want for the stand-alone assembler.
	MACHINE MC68000	; because there are a lot the assembler can do.
	OPT DEBUG	; We want labels for debugging.
	OUTPUT
***********************************************************************
	EVEN	
ENTRY	JMP	START
*
BYTTBL	DC.B	8	; byte data doesn't have to be aligned.
	DC.B	5
	DC.B	2
	DC.B	7
	DC.B	4
*
	EVEN		; But 68K code does have to be even aligned.
START	LEA	BYTTBL(PC),A0
	MOVE.B	(A0)+,D1	; post-access inc implied
	ADD.B	(A0)+,D1
	ADD.B	(A0)+,D1
	ADD.B	(A0)+,D1
	ADD.B	(A0),D1	; Leaving out the trailing post-inc.
	NOP
DONE	NOP
* One way to return to the OS or other calling program
	clr.w	-(sp)	; there should be enough room on the caller's stack
	trap	#1		;	quick exit

By the way, do you see that push instruction before the trap back to EmuTOS? 

:)

I'll explain later, maybe in the next example.

For now, go ahead and copy this source into a text file, assemble it, and run it in the debugger, and then we will be done with this list of small constants -- for a while, at least.

Once you have that tested, and are satisfied that it works as advertised, you might want to clean up your working directory a bit. Make a subdirectory called step1 or basicadr or s1_adr or something, and move all the 68000 code files we have made to it.

Next, let's see if we can put some of this together to put out a message, and hopefully this all will begin to make some sense.


(Title Page/Index)

 

Saturday, July 20, 2024

ALPP 01-08 -- Simple Lists on the 6800, 6801, 6809, and 68000

Simple Lists on the 6800, 6801, 6809, and 68000

(Title Page/Index)

 

I'm going to keep working with that list of small integers for at least one more, to make it easier to focus on the topic, in this case, a way to treat a list like a list. But now that you know some tools to help you with the binary and hexadecimal math, you can make your own list(s) if you want. It's not a bad idea, I think. Just keep the total under 128 while we are working on byte math. It'll keep things more sane.

What we want to do now is treat the list as a single entity.

Let's take another look at the disassembly of the most recent example. We'll start another exorsim session and assemble it again:

$ ./exor --mon
Load facts file 'facts'
'exbug.bin' loaded.
  EXBUG-1.1 detected
'mdos.dsk' opened for drive 0 (double sided)

OSLOAD...

Hit Ctrl-C for simulator command line.  Starting simulation...

>         0 A=00 B=00 X=0000 SP=00FF ------          0020: B6 E8 00 LDA E800                 

6800 Monitor: Ctrl-C to exit, 'c' to continue, or type 'help'
% a 1000
1000: ENTRY	JMP	START
1003: *
1003: BYTE8	FCB	8
1004: BYTE5	FCB	5
1005: BYTE2	FCB	2
1006: BYTE7	FCB	7
1007: BYTE4	FCB	4
1008: *
1008: START	LDAB	BYTE8
Address at 1001 set to 1008
100b: 	ADDB	BYTE5
100e: 	ADDB	BYTE2
1011: 	ADDB	BYTE7	
1014: 	ADDB	BYTE4
1017: 	NOP
1018: DONE	NOP	
1019: 
% u 1000
1000: 7E 10 08            JMP $1008
1003: 08                  INX
1004: 05                  ???
1005: 02                  ???
1006: 07                  TPA
1007: 04                  ???
1008: F6 10 03            LDB $1003
100B: FB 10 04            ADDB $1004
100E: FB 10 05            ADDB $1005
1011: FB 10 06            ADDB $1006
1014: FB 10 07            ADDB $1007
1017: 01                  NOP
1018: 01                  NOP
1019: 00                  ???
101A: 00                  ???

If we type help, we'll notice a command called "sy":

% help
help			Show this help text
h			Show this help text
q			Exit simulator
...
clr			Clear symbol table
sy			Show symbol table
u hhhh			Unassemble
...
% 

Let's try that:

% sy
1018 DONE
1007 BYTE4
1006 BYTE7
1005 BYTE2
1004 BYTE5
1003 BYTE8
1008 START
1000 ENTRY
% 

if we think about it, 

  • BYTE5 is BYTE8 plus 1. 
  • BYTE2 is BYTE8 plus 2.
  • BYTE7 is BYTE8 plus 3.
  • And BYTE4 is BYTE8 plus 4.

So we could write the code like this:

ENTRY	JMP	START
*
BYTTBL	FCB	8
	FCB	5
	FCB	2
	FCB	7
	FCB	4
*
START	LDAB	BYTTBL+0
	ADDB	BYTTBL+1
	ADDB	BYTTBL+2
	ADDB	BYTTBL+3
	ADDB	BYTTBL+4
	NOP
DONE	NOP	

and the object code would look exactly as in the last example, as long as we start the assembly at $1000 like before ("%a 1000").

Give it a try, with all four CPUs. The 6801 code is exactly the same. The 6809 source code is only different where LDAB becomes LDB. The 68000 code only changes in the declaration of BYTTBL instead of BYTE8, etc., and in the use of BYTTBL+0, etc., instead of BYTE8, etc.

Done that? That was easy, right? 

If re-reading the last chapter doesn't solve whatever problems, leave a message below and I'll try to get back to you.

What's next?

We've just looked at one way to use offsets. Let's see if there's another:

ENTRY	JMP	START
*
BYTTBL	FCB	8
	FCB	5
	FCB	2
	FCB	7
	FCB	4
*
START	LDX	#BYTTBL
	LDAB	0,X
	ADDB	1,X
	ADDB	2,X
	ADDB	3,X
	ADDB	4,X
	NOP
DONE	NOP

What is this LDX and the ",X" all over the place?

X is what is called an index register. On the 6800 and the 6801, it's the only one. An index register is a place to keep addresses, mostly.

So we LoaD X with the address of BYTTBL. Note the immediate "#". If we used the extended (absolute) mode instead of the immediate mode,

	LDX	BYTTBL

without the immediate mode hash in there, the assembled code would make the CPU go out to BYTTBL and load the first two bytes in it into X, which would result in $805 being loaded into the index register.

I wonder, what's at address $805?

Anyway, in the next instruction,

	LDAB	0,X

the B accumulator will now be loaded from the address which is in X, plus the constant offset 0. That's what the "0," means.

In the 6800 and 6801, offsets to the index register are always constant.

So, you add the 0 to the address of BYTTBL in X, and that's the address of the first byte, the 8. Add 1 to it in the next instruction, and that's the address of the second byte, the 5. Where does that address go? Once it is used by the indexed mode instruction, it's gone. 

Then add 2 to the (unchaged) base of the BYTTBL array to get the address of the third byte, the 2. And so forth. So we're doing the same thing as before, but a different way. We are using X to tell us where the list is, and we are using offsets to the index register X to find each entry.

Some people would say this is actually an array, not a list. It's a reasonable distinction to make, but it isn't always meaningful. More to talk about later, I guess. We'll make it to the beach soon, I promise.

Let's watch the code run:

% a 1000
1000: ENTRY	JMP	START
1003: *
1003: BYTTBL	FCB	8
1004: 	FCB	5
1005: 	FCB	2
1006: 	FCB	7
1007: 	FCB	4
1008: *
1008: START	LDX	#BYTTBL
Address at 1001 set to 1008
100b: 	LDAB	0,X
100d: 	ADDB	1,X
100f: 	ADDB	2,X
1011: 	ADDB	3,X
1013: 	ADDB	4,X
1015: 	NOP
1016: DONE	NOP
1017: 
% u 1000
1000: 7E 10 08            JMP $1008
1003: 08                  INX
1004: 05                  ???
1005: 02                  ???
1006: 07                  TPA
1007: 04                  ???
1008: CE 10 03            LDX #$1003
100B: E6 00               LDB $00,X
100D: EB 01               ADDB $01,X
100F: EB 02               ADDB $02,X
1011: EB 03               ADDB $03,X
1013: EB 04               ADDB $04,X
1015: 01                  NOP
1016: 01                  NOP
1017: 00                  ???
1018: 00                  ???
... % b 1015 Breakpoint set at 1015 % t on % c 1000 0 A=00 B=00 X=0000 SP=00FF ------ ENTRY 1000: 7E 10 08 JMP 1008 EA=1008(START) 1 A=00 B=00 X=0000 SP=00FF ------ START 1008: CE 10 03 LDX #$1003 EA=1009 D=1003 2 A=00 B=00 X=1003 SP=00FF ------ 100B: E6 00 LDB 00,X EA=1003(BYTTBL) D=08 3 A=00 B=08 X=1003 SP=00FF ------ 100D: EB 01 ADDB 01,X EA=1004 D=05 4 A=00 B=0D X=1003 SP=00FF ------ 100F: EB 02 ADDB 02,X EA=1005 D=02 5 A=00 B=0F X=1003 SP=00FF ------ 1011: EB 03 ADDB 03,X EA=1006 D=07 6 A=00 B=16 X=1003 SP=00FF H----- 1013: EB 04 ADDB 04,X EA=1007 D=04 Breakpoint! > 7 A=00 B=1A X=1003 SP=00FF ------ 1015: 01 NOP 6800 Monitor: Ctrl-C to exit, 'c' to continue, or type 'help' %

If you look carefully at the right edge of the trace listing, you'll see the effective address (EA) and the data (D) at that address being reported by the debugger. But once the instruction is done with the address and the data, the CPU does not remember either any more. Only the effect remains in the CPU, where the data is being added up in the B accumulator.

Out in memory, of course, the data is still there at the address it was at, waiting in case we decide to look at it again.

Again, when we run this in the 6801, it will do exactly the same thing again. Go ahead and try it, changing to that directory and running it there. Remember to use the --6801 option: 

$ ./exor --6801 --mon
Load facts file 'facts'
'exbug.bin' loaded.
'mdos.dsk' opened for drive 0 (single sided)

Hit Ctrl-C for simulator command line.  Starting simulation...

>         0 A=00 B=00 X=0000 SP=FF8A ------ OSLOAD   E800: 8E FF 8A LDS #$FF8A                Load OS

Type 'help'
%       
% a 1000
1000: ENTRY	JMP	START
1003: *
1003: BYTTBL	FCB	8
1004: 	FCB	5
1005: 	FCB	2
1006: 	FCB	7
1007: 	FCB	4
1008: *
1008: START	LDX	#BYTTBL
Address at 1001 set to 1008
100b: 	LDAB	0,X
100d: 	ADDB	1,X
100f: 	ADDB	2,X
1011: 	ADDB	3,X
1013: 	ADDB	4,X
1015: 	NOP
1016: DONE	NOP
1017: 
% u 1000
1000: 7E 10 08            JMP $1008
1003: 08                  INX
1004: 05                  LSLD
1005: 02                  ???
1006: 07                  TPA
1007: 04                  LSRD
1008: CE 10 03            LDX #$1003
100B: E6 00               LDB $00,X
100D: EB 01               ADDB $01,X
100F: EB 02               ADDB $02,X
1011: EB 03               ADDB $03,X
1013: EB 04               ADDB $04,X
1015: 01                  NOP
1016: 01                  NOP
1017: 00                  ???
1018: 00                  ???
...
% b 1015
Breakpoint set at 1015
% t on 
% c 1000
          0 A=00 B=00 X=0000 SP=FF8A ------ ENTRY    1000: 7E 10 08 JMP 1008  EA=1008(START) 
          1 A=00 B=00 X=0000 SP=FF8A ------ START    1008: CE 10 03 LDX #$1003 EA=1009 D=1003 
          2 A=00 B=00 X=1003 SP=FF8A ------          100B: E6 00    LDB 00,X  EA=1003(BYTTBL) D=08 
          3 A=00 B=08 X=1003 SP=FF8A ------          100D: EB 01    ADDB 01,X EA=1004 D=05   
          4 A=00 B=0D X=1003 SP=FF8A ------          100F: EB 02    ADDB 02,X EA=1005 D=02   
          5 A=00 B=0F X=1003 SP=FF8A ------          1011: EB 03    ADDB 03,X EA=1006 D=07   
          6 A=00 B=16 X=1003 SP=FF8A H-----          1013: EB 04    ADDB 04,X EA=1007 D=04   

Breakpoint!
>         7 A=00 B=1A X=1003 SP=FF8A ------          1015: 01       NOP                      

Type 'help'
  
% 

Change back to the directory where you can run exor09 and try it on the 6809. Remember to use LDB instead of LDAB:

$ ./exor09 --mon
Load facts file 'facts09'
'exbug09.bin' loaded.
  EXBUG09-2.1 detected
'mdos09.dsk' opened for drive 0 (double sided)

OSLOAD...

Hit Ctrl-C for simulator command line.  Starting simulation...

>         0 A=00 B=00 X=0000 Y=0000 U=0000 S=00FF P=00 --------            0020: 86 10        LDA #$10                   

6809 Monitor: Ctrl-C to exit, 'c' to continue, or type 'help'
% a 1000
1000: ENTRY	JMP	START
later = 1001
1003: *
1003: BYTTBL	FCB	8
1004: 	FCB	5
1005: 	FCB	2
1006: 	FCB	7
1007: 	FCB	4
1008: *
1008: START	LDX	#BYTTBL
Address at 1001 set to 1008
100b: 	LDB	0,X
100d: 	ADDB	1,X
100f: 	ADDB	2,X
1011: 	ADDB	3,X
1013: 	ADDB	4,X
1015: 	NOP
1016: DONE	NOP	
1017: 
% u 1000
1000: 7E 1008             JMP $1008
1003: 08 05               ASL $05
1005: 02 07               ??? $07
1007: 04 8E               LSR $8e
1009: 1003 E6             COM $e6
100C: 00 EB               NEG $eb
100E: 01 EB               ??? $eb
1010: 02 EB               ??? $eb
1012: 03 EB               COM $eb
1014: 04 12               LSR $12
1016: 12                  NOP 
1017: 00 00               NEG $00
1019: 00 00               NEG $00
...
% u 1008
1008: 8E 1003             LDX #$1003
100B: E6 00               LDB 0,X
100D: EB 01               ADDB 1,X
100F: EB 02               ADDB 2,X
1011: EB 03               ADDB 3,X
1013: EB 04               ADDB 4,X
1015: 12                  NOP 
1016: 12                  NOP 
1017: 00 00               NEG $00
1019: 00 00               NEG $00
...
% b 1015
Breakpoint set at 1015
% t on
% c 1000

          0 A=00 B=00 X=0000 Y=0000 U=0000 S=00FF P=00 -------- ENTRY      1000: 7E 1008      JMP $1008                  
          1 A=00 B=00 X=0000 Y=0000 U=0000 S=00FF P=00 -------- START      1008: 8E 1003      LDX #$1003                 
          2 A=00 B=00 X=1003 Y=0000 U=0000 S=00FF P=00 --------            100B: E6 00        LDB 0,X     EA=1003(BYTTBL) D=08 
          3 A=00 B=08 X=1003 Y=0000 U=0000 S=00FF P=00 --------            100D: EB 01        ADDB 1,X    EA=1004 D=05   
          4 A=00 B=0D X=1003 Y=0000 U=0000 S=00FF P=00 --------            100F: EB 02        ADDB 2,X    EA=1005 D=02   
          5 A=00 B=0F X=1003 Y=0000 U=0000 S=00FF P=00 --------            1011: EB 03        ADDB 3,X    EA=1006 D=07   
          6 A=00 B=16 X=1003 Y=0000 U=0000 S=00FF P=00 --H-----            1013: EB 04        ADDB 4,X    EA=1007 D=04   

Breakpoint!
>         7 A=00 B=1A X=1003 Y=0000 U=0000 S=00FF P=00 --------            1015: 12           NOP                        

6809 Monitor: Ctrl-C to exit, 'c' to continue, or type 'help'
%     

And we see that the code does the same thing, there are just more indexable registers to report nothing happening in. And op-codes are a little different.

Hmm. What about PC-relative? Should we try it? How is that going to work? Is there a way to load an immediate address in PC-relative or something? Let's have a look. 

There is a Load Effective Address (LEA) instruction for the 6809 that can do what we want. The assembler will calculate the offset and assemble it, and when the CPU runs the instruction, it will add the offset to the PC and grab the effective address before it disappears, loading the calculated addres into the specified index register:

	LEAX	BYTTBL,PCR

We'll  use this instruction instead of the "LDX #BYTTBL" instruction.

Hit Ctrl-C or type "quit" to end the session, start a new one, and paste the code in:

% a 1000
1000: ENTRY	JMP	START
later = 1001
1003: *
1003: BYTTBL	FCB	8
1004: 	FCB	5
1005: 	FCB	2
1006: 	FCB	7
1007: 	FCB	4
1008: *
1008: START	LEAX	BYTTBL,PCR
Address at 1001 set to 1008
100b: 	LDB	0,X
100d: 	ADDB	1,X
100f: 	ADDB	2,X
1011: 	ADDB	3,X
1013: 	ADDB	4,X
1015: 	NOP
1016: DONE	NOP	
1017: 
% u 1000
1000: 7E 1008             JMP $1008
1003: 08 05               ASL $05
1005: 02 07               ??? $07
1007: 04 30               LSR $30
1009: 8C F8E6             CMPX #$f8e6
100C: 00 EB               NEG $eb
100E: 01 EB               ??? $eb
1010: 02 EB               ??? $eb
1012: 03 EB               COM $eb
1014: 04 12               LSR $12
1016: 12                  NOP 
1017: 00 00               NEG $00
1019: 00 00               NEG $00
...
% u 1008
1008: 30 8C F8            LEAX $1003,PCR
100B: E6 00               LDB 0,X
100D: EB 01               ADDB 1,X
100F: EB 02               ADDB 2,X
1011: EB 03               ADDB 3,X
1013: EB 04               ADDB 4,X
1015: 12                  NOP 
1016: 12                  NOP 
1017: 00 00               NEG $00
1019: 00 00               NEG $00
...
% b 1015
Breakpoint set at 1015
% t on
% c 1000

          0 A=00 B=00 X=0000 Y=0000 U=0000 S=00FF P=00 -------- ENTRY      1000: 7E 1008      JMP $1008                  
          1 A=00 B=00 X=0000 Y=0000 U=0000 S=00FF P=00 -------- START      1008: 30 8C F8     LEAX $1003,PCR                
          2 A=00 B=00 X=1003 Y=0000 U=0000 S=00FF P=00 --------            100B: E6 00        LDB 0,X     EA=1003(BYTTBL) D=08 
          3 A=00 B=08 X=1003 Y=0000 U=0000 S=00FF P=00 --------            100D: EB 01        ADDB 1,X    EA=1004 D=05   
          4 A=00 B=0D X=1003 Y=0000 U=0000 S=00FF P=00 --------            100F: EB 02        ADDB 2,X    EA=1005 D=02   
          5 A=00 B=0F X=1003 Y=0000 U=0000 S=00FF P=00 --------            1011: EB 03        ADDB 3,X    EA=1006 D=07   
          6 A=00 B=16 X=1003 Y=0000 U=0000 S=00FF P=00 --H-----            1013: EB 04        ADDB 4,X    EA=1007 D=04   

Breakpoint!
>         7 A=00 B=1A X=1003 Y=0000 U=0000 S=00FF P=00 --------            1015: 12           NOP                        

6809 Monitor: Ctrl-C to exit, 'c' to continue, or type 'help'
% 

Now we can begin to see some of the usefulness of index registers.

Let's try this on the 68000. Getting the base address of BYTTBL by absolute address mode first:

	OPT LIST,SYMTAB	; Options we want for the stand-alone assembler.
	MACHINE MC68000	; because there are a lot the assembler can do.
	OPT DEBUG	; We want labels for debugging.
	OUTPUT
***********************************************************************
	EVEN	
ENTRY	JMP	START
*
BYTTBL	DC.B	8	; byte data doesn't have to be aligned.
	DC.B	5
	DC.B	2
	DC.B	7
	DC.B	4
*
	EVEN		; But 68K code does have to be even aligned.
START	MOVE.L	#BYTTBL,A0
	MOVE.B	0(A0),D1
	ADD.B	1(A0),D1
	ADD.B	2(A0),D1
	ADD.B	3(A0),D1
	ADD.B	4(A0),D1
	NOP
DONE	NOP
* One way to return to the OS or other calling program
	clr.w	-(sp)	; there should be enough room on the caller's stack
	trap	#1		;	quick exit

Save that with a new name, maybe sumconx.s , in your working directory under the Hatari C: drive emulation directory. According to the example I've been using, that would be as

~/asmwork/share/hatari/C/primer/sumconx.s

Change to that directory in your host OS shell and assemble it with vasm. Then get a Hatari session running:

$ cd ~/asmwork/share/hatari/C/primer/
$ vasmm68k_mot -Ftos -no-opt -o SUMCONX.PRG -L sumconx.lst sumconx.s
$ hatari

Use Ctrl-Z to drop out of the GUI into the EmuTOS CPM shell, then change to the working directory:

C:\>CD PRIMER
C:\primer>

HIt Alt-PAUSE to break out of the CPM shell into the debugger, and use the mouse to bring up the host OS's parent window where the debugger is running . 

Set the debugger to (b)reak on entry to the TEXT segment of the next program loaded; and then (c)ontinue, to return to the CPM shell:

> b pc = TEXT :once
CPU condition breakpoint 1 with 1 condition(s) added:
	pc = TEXT
-> Break only once, and delete breakpoint afterwards.
> c
Returning to emulation...

Run the code in the CPM shell:

C:\primer>SUMCONX

Go back to the debugger in the host's parent window and (d)isassemble code to make sure it's what you think it is:

1. CPU breakpoint condition(s) matched 1 times.
	pc = TEXT :once
Removed CPU breakpoint 1:
	pc = TEXT :once
Reading symbols from program '/home/nova/usr/share/hatari/C:/primer/SUMCONX.PRG' symbol table...
TOS executable, DRI / GST symbol table, reloc=0, program flags: PRIVATE (0x0)
Program section sizes:
  text: 0x2e, data: 0x0, bss: 0x0, symtab: 0x38
Trying to load DRI symbol table at offset 0x4a...
Offsetting BSS/DATA symbols from TEXT section.
Skipping duplicate address & symbol name checks when autoload is enabled.
Loaded 4 symbols (4 TEXT) from '/home/nova/usr/share/hatari/C:/primer/SUMCONX.PRG'.

CPU=$13d10, VBL=2649, FrameCycles=235056, HBL=231, LineCycles=360, DSP=N/A
00013d10 4ef9 0001 3d1c           jmp $00013d1c
> d
(PC)
ENTRY:
00013d10 4ef9 0001 3d1c           jmp $00013d1c
BYTTBL:
00013d16 0805 0207                btst.l #$0207,d5
00013d1a 0400 207c                sub.b #$7c,d0
00013d1e 0001 3d16                or.b #$16,d1
00013d22 1228 0000                move.b (a0,$0000) == $00000000 [60],d1
00013d26 d228 0001                add.b (a0,$0001) == $00000001 [2e],d1
00013d2a d228 0002                add.b (a0,$0002) == $00000002 [02],d1
00013d2e d228 0003                add.b (a0,$0003) == $00000003 [06],d1
00013d32 d228 0004                add.b (a0,$0004) == $00000004 [00],d1
00013d36 4e71                     nop 
DONE:
00013d38 4e71                     nop 
00013d3a 4267                     clr.w -(a7) [0000]
00013d3c 4e41                     trap #$01
00013d3e 0000 0000                or.b #$00,d0
00013d42 0000 0000                or.b #$00,d0
...
>

Check the target of the jump address so you can disassemble the code after the table where it will make sense, and (d)isassamble again, from the jump target address:

> d $13d1c
START:
00013d1c 207c 0001 3d16           movea.l #$00013d16,a0
00013d22 1228 0000                move.b (a0,$0000) == $00000000 [60],d1
00013d26 d228 0001                add.b (a0,$0001) == $00000001 [2e],d1
00013d2a d228 0002                add.b (a0,$0002) == $00000002 [02],d1
00013d2e d228 0003                add.b (a0,$0003) == $00000003 [06],d1
00013d32 d228 0004                add.b (a0,$0004) == $00000004 [00],d1
00013d36 4e71                     nop 
DONE:
00013d38 4e71                     nop 
00013d3a 4267                     clr.w -(a7) [0000]
00013d3c 4e41                     trap #$01
00013d3e 0000 0000                or.b #$00,d0
00013d42 0000 0000                or.b #$00,d0
...
>

Check the (r)egisters and (s)tep, repeating until the NOP comes up, and looking for the sum to accumulate in D1:

> r       
  D0 00000000   D1 00000000   D2 00000000   D3 00000000 
  D4 00000000   D5 00000000   D6 00000000   D7 00000000 
  A0 00000000   A1 00000000   A2 00000000   A3 00000000 
  A4 00013D3E   A5 00013D3E   A6 00077FC6   A7 00077FF8 
USP  00077FF8 ISP  00007E64 
T=00 S=0 M=0 X=0 N=0 Z=0 V=0 C=0 IMASK=3 STP=0
Prefetch 4ef9 (JMP) 0001 (OR) Chip latch 00000000
00013d10 4ef9 0001 3d1c           jmp $00013d1c
Next PC: 00013d16
> s

CPU=$13d1c, VBL=2649, FrameCycles=235068, HBL=231, LineCycles=372, DSP=N/A
00013d1c 207c 0001 3d16           movea.l #$00013d16,a0
> r
  D0 00000000   D1 00000000   D2 00000000   D3 00000000 
  D4 00000000   D5 00000000   D6 00000000   D7 00000000 
  A0 00000000   A1 00000000   A2 00000000   A3 00000000 
  A4 00013D3E   A5 00013D3E   A6 00077FC6   A7 00077FF8 
USP  00077FF8 ISP  00007E64 
T=00 S=0 M=0 X=0 N=0 Z=0 V=0 C=0 IMASK=3 STP=0
Prefetch 207c (MOVEA) 0001 (OR) Chip latch 00000000
00013d1c 207c 0001 3d16           movea.l #$00013d16,a0
Next PC: 00013d22
> s

CPU=$13d22, VBL=2649, FrameCycles=235080, HBL=231, LineCycles=384, DSP=N/A
00013d22 1228 0000                move.b (a0,$0000) == $00013d16 [08],d1
> r
  D0 00000000   D1 00000000   D2 00000000   D3 00000000 
  D4 00000000   D5 00000000   D6 00000000   D7 00000000 
  A0 00013D16   A1 00000000   A2 00000000   A3 00000000 
  A4 00013D3E   A5 00013D3E   A6 00077FC6   A7 00077FF8 
USP  00077FF8 ISP  00007E64 
T=00 S=0 M=0 X=0 N=0 Z=0 V=0 C=0 IMASK=3 STP=0
Prefetch 1228 (MOVE) 0000 (OR) Chip latch 00000000
00013d22 1228 0000                move.b (a0,$0000) == $00013d16 [08],d1
Next PC: 00013d26
> s

CPU=$13d26, VBL=2649, FrameCycles=235092, HBL=231, LineCycles=396, DSP=N/A
00013d26 d228 0001                add.b (a0,$0001) == $00013d17 [05],d1
> r
  D0 00000000   D1 00000008   D2 00000000   D3 00000000 
  D4 00000000   D5 00000000   D6 00000000   D7 00000000 
  A0 00013D16   A1 00000000   A2 00000000   A3 00000000 
  A4 00013D3E   A5 00013D3E   A6 00077FC6   A7 00077FF8 
USP  00077FF8 ISP  00007E64 
T=00 S=0 M=0 X=0 N=0 Z=0 V=0 C=0 IMASK=3 STP=0
Prefetch d228 (ADD) 0001 (OR) Chip latch 00000000
00013d26 d228 0001                add.b (a0,$0001) == $00013d17 [05],d1
Next PC: 00013d2a
> s

CPU=$13d2a, VBL=2649, FrameCycles=235104, HBL=231, LineCycles=408, DSP=N/A
00013d2a d228 0002                add.b (a0,$0002) == $00013d18 [02],d1
> r
  D0 00000000   D1 0000000D   D2 00000000   D3 00000000 
  D4 00000000   D5 00000000   D6 00000000   D7 00000000 
  A0 00013D16   A1 00000000   A2 00000000   A3 00000000 
  A4 00013D3E   A5 00013D3E   A6 00077FC6   A7 00077FF8 
USP  00077FF8 ISP  00007E64 
T=00 S=0 M=0 X=0 N=0 Z=0 V=0 C=0 IMASK=3 STP=0
Prefetch d228 (ADD) 0002 (OR) Chip latch 00000000
00013d2a d228 0002                add.b (a0,$0002) == $00013d18 [02],d1
Next PC: 00013d2e
> s

CPU=$13d2e, VBL=2649, FrameCycles=235116, HBL=231, LineCycles=420, DSP=N/A
00013d2e d228 0003                add.b (a0,$0003) == $00013d19 [07],d1
> r
  D0 00000000   D1 0000000F   D2 00000000   D3 00000000 
  D4 00000000   D5 00000000   D6 00000000   D7 00000000 
  A0 00013D16   A1 00000000   A2 00000000   A3 00000000 
  A4 00013D3E   A5 00013D3E   A6 00077FC6   A7 00077FF8 
USP  00077FF8 ISP  00007E64 
T=00 S=0 M=0 X=0 N=0 Z=0 V=0 C=0 IMASK=3 STP=0
Prefetch d228 (ADD) 0003 (OR) Chip latch 00000000
00013d2e d228 0003                add.b (a0,$0003) == $00013d19 [07],d1
Next PC: 00013d32
> s

CPU=$13d32, VBL=2649, FrameCycles=235128, HBL=231, LineCycles=432, DSP=N/A
00013d32 d228 0004                add.b (a0,$0004) == $00013d1a [04],d1
> r
  D0 00000000   D1 00000016   D2 00000000   D3 00000000 
  D4 00000000   D5 00000000   D6 00000000   D7 00000000 
  A0 00013D16   A1 00000000   A2 00000000   A3 00000000 
  A4 00013D3E   A5 00013D3E   A6 00077FC6   A7 00077FF8 
USP  00077FF8 ISP  00007E64 
T=00 S=0 M=0 X=0 N=0 Z=0 V=0 C=0 IMASK=3 STP=0
Prefetch d228 (ADD) 0004 (OR) Chip latch 00000000
00013d32 d228 0004                add.b (a0,$0004) == $00013d1a [04],d1
Next PC: 00013d36
> s

CPU=$13d36, VBL=2649, FrameCycles=235140, HBL=231, LineCycles=444, DSP=N/A
00013d36 4e71                     nop 
> r
  D0 00000000   D1 0000001A   D2 00000000   D3 00000000 
  D4 00000000   D5 00000000   D6 00000000   D7 00000000 
  A0 00013D16   A1 00000000   A2 00000000   A3 00000000 
  A4 00013D3E   A5 00013D3E   A6 00077FC6   A7 00077FF8 
USP  00077FF8 ISP  00007E64 
T=00 S=0 M=0 X=0 N=0 Z=0 V=0 C=0 IMASK=3 STP=0
Prefetch 4e71 (NOP) 4e71 (NOP) Chip latch 00000000
00013d36 4e71                     nop 
Next PC: 00013d38
> s

CPU=$13d38, VBL=2649, FrameCycles=235144, HBL=231, LineCycles=448, DSP=N/A
00013d38 4e71                     nop 
> r
  D0 00000000   D1 0000001A   D2 00000000   D3 00000000 
  D4 00000000   D5 00000000   D6 00000000   D7 00000000 
  A0 00013D16   A1 00000000   A2 00000000   A3 00000000 
  A4 00013D3E   A5 00013D3E   A6 00077FC6   A7 00077FF8 
USP  00077FF8 ISP  00007E64 
T=00 S=0 M=0 X=0 N=0 Z=0 V=0 C=0 IMASK=3 STP=0
Prefetch 4e71 (NOP) 4267 (CLR) Chip latch 00000000
00013d38 4e71                     nop 
Next PC: 00013d3a
>

You can see that the address of BYTTBL got loaded into A0, and the bytes in the table got accumulated in D1 as we expected. You'll also notice that A4 to A7 have some sort of values in them that looks like they might be addresses. And you'll notice the USP and the ISP, and you'll wonder what those are. We'll talk about all this stuff later on.

(c)ontinue, to return to the CPM shell:

> c
Returning to emulation...
Program exit, removing its symbols.

The 68000 also has a LEA instruction. Let's try using that in PC-relative mode. Change the move immediate

START	MOVE.L	#BYTTBL,A0

at the START label to load effective address, PC relative:

START	LEA	BYTTBL(PCR),A0

Save the file under a different name, say sumconxpc.s .

Uhm, no. Too many file names with most of the part that's significant to CPM the same, and now we would have two that have all that is significant to CPM the same. I shouldn't be getting you in the habit of doing that. Shame on me. Don't do that, okay? :-/

Sigh. Save it as something different that you'll hopefully remember what it is later -- maybe sumcnxrpc.s . Short file names can get a little crazy.

End the Hatari session and use vasm to assemble the new file:

$ vasmm68k_mot -Ftos -no-opt -o SUMCNXR.PRG -L sumcnxrpc.lst sumcnxrpc.s

Go through the process in the EmuTOS CPM shell and the Hatari debugger to set the TEXT segment to break on program entry, and step through the code watching the registers change, convincing yourself that it works as I advertise it. In particular, check that the LEA instruction does what I say it does.

No. Really. Save the file, assemble it, walk it through. Make sure you can use the Hatari debugger to do this.

This approach to a list is usually referred to as an array, by the way. As you see, we access it by a base address and an index, or offset into the array. It is our method of accessing it that is array-like. But array access is usually a little more complicated. We'll get to that, too.

Next, let's see if we can put some of this together to put out a message, and hopefully this will begin to make sense.

No, I guess we aren't quite ready for that. I think we need to first take a little look at accessing the elements of a list in an orderly sequence. That should help us get in the mood to work with strings. Then we can put out a simple message.


(Title Page/Index)

 

Sunday, July 14, 2024

ALPP 01-06 -- Global Constants on the 6800, 6801, 6809, and 68000

Global Constants on the 6800, 6801, 6809, and 68000

(Title Page/Index)

 

So, now we have working environments set up for the 6800, 6809, 6801, and 68000,  and have taken a look at summing up a few small integer constants buried in the code on each of these processors.

Do you think having a list of numbers buried in the code might be a little clumsy? Just a little bit?

It is. Usually.

There are several ways to separate the numbers from the code that adds them. Let's take a look at one here, a way which we will call using global constants.

There are three related topics we will introduce here, in the process.

  • labels,
  • lists in memory
  • absolute addressing

And, while we're at it, you might want to think about object code size and execution time. We haven't taken up all the tools necessary to think about such things, but you might want to, anyway.

First, lets get the constants out of the code. 

In Motorola's 680X assemblers, the usual way to define a constant byte-sized integer is to use the FCB directive. You may think FCB stands for File Control Block, but in this context it means Form Constant Byte:

	FCB	8

We do not use "FCB #8" for two reasons. Well, three. One is that this is no longer immediate addressing. The other is that FCB is not an instruction for the microprocessor. It's a directive to the assembler. And the other other reason is that the assembler will complain if you do. Because of the first two reasons. And I promise this will make more sense later.

But it's not enough to have that small constant out there in memory somewhere. We need some way for the code to reference it. So we will give it a label, maybe call it BYTE8 (because it sounds cool or something):

BYTE8	FCB	8

You might remember the label START at the beginning of the code in our previous examples? It's basically the same thing in a slightly different context. 

This is quite literally just a label, a name by which a particular address in memory can be referenced.

Okay, here's the entire summing code for the 6800, using the separate constants:

ENTRY	JMP	START
*
BYTE8	FCB	8
BYTE5	FCB	5
BYTE2	FCB	2
BYTE7	FCB	7
BYTE4	FCB	4
*
START	LDAB	BYTE8
	ADDB	BYTE5
	ADDB	BYTE2
	ADDB	BYTE7	
	ADDB	BYTE4
	NOP
DONE	NOP	

Those asterisks at the start of those two lines are Motorola's way to show that the entire line is a comment. In this case, it's to make a deliberate empty line in the source code, to separate code from constants. It has zero effect in the object code.

That's a JuMP at label ENTRY. It jumps over the constants.

Is it necessary? 

Good question. 

:-)

Get a running EXORsim debugging session:

$ ./exor --mon
Load facts file 'facts'
'exbug.bin' loaded.
  EXBUG-1.1 detected
'mdos.dsk' opened for drive 0 (double sided)

OSLOAD...

Hit Ctrl-C for simulator command line.  Starting simulation...

>         0 A=00 B=00 X=0000 SP=00FF ------          0020: B6 E8 00 LDA E800                 

6800 Monitor: Ctrl-C to exit, 'c' to continue, or type 'help'
%

Start an assembly at address $1000 and paste it in. Remember, right-click-paste to paste into the shell, because the shell thinks Ctrl-V means something else:

% a 1000
1000: ENTRY	JMP	START
1003: *
1003: BYTE8	FCB	8
1004: BYTE5	FCB	5
1005: BYTE2	FCB	2
1006: BYTE7	FCB	7
1007: BYTE4	FCB	4
1008: *
1008: START	LDAB	BYTE8
Address at 1001 set to 1008
100b: 	ADDB	BYTE5
100e: 	ADDB	BYTE2
1011: 	ADDB	BYTE7	
1014: 	ADDB	BYTE4
1017: 	NOP
1018: DONE	NOP	
1019: 

Disassemble it to see how it went:

% u 1000
1000: 7E 10 08            JMP $1008
1003: 08                  INX
1004: 05                  ???
1005: 02                  ???
1006: 07                  TPA
1007: 04                  ???
1008: F6 10 03            LDB $1003
100B: FB 10 04            ADDB $1004
100E: FB 10 05            ADDB $1005
1011: FB 10 06            ADDB $1006
1014: FB 10 07            ADDB $1007
1017: 01                  NOP
1018: 01                  NOP
1019: 00                  ???
101A: 00                  ???
...

Ackkkkkk! Where are the constants?!?!?!?!? And what is this INX and TPA and ????

If you don't jump over the constants, the processor will see that $08 as the op-code for the INcrement X instruction, and the $07 as the Transfer Processor status to A instruction. So that's what the disassembler shows you. And 5, 2, and 4 are not defined instructions, so the disassembler prints question marks.

And the processor, who knows what it would do?

(Interested geeks have researched this question. You can look it up if you are so inclined, but most undefined op-codes aren't all that interesting on the 6800.)

So. Maybe we do need that JMP instruction. Yes, we do. Sort of.

Now, since we are using the interactive debugger, we could just use the step or continue debugger command to tell it to start at address $1008. In fact, we could. We know that. 

But how does an operating system know that? How does someone who has neither the source code nor a debugger know what address to start at? I mean, without the source, here's all you have:

% d 1000
1000: 7E 10 08 08 05 02 07 04  F6 10 03 FB 10 04 FB 10 ~...............
1010: 05 FB 10 06 FB 10 07 01  01 00 00 00 00 00 00 00 ................
1020: 00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00 ................
...

So, on the one hand, no the JMP is not exactly necessary. On the other hand, it is a useful convention. We will use it, at least until we have something better.

So, set a breakpoint, turn trace on, and watch it do its stuff:

% b 1018
Breakpoint set at 1018
% t on
% c 1000

          9 A=00 B=1A X=0000 SP=00FF ------ ENTRY    1000: 7E 10 08 JMP 1008  EA=1008(START) 
         10 A=00 B=1A X=0000 SP=00FF ------ START    1008: F6 10 03 LDB 1003  EA=1003(BYTE8) D=08 
         11 A=00 B=08 X=0000 SP=00FF ------          100B: FB 10 04 ADDB 1004 EA=1004(BYTE5) D=05 
         12 A=00 B=0D X=0000 SP=00FF ------          100E: FB 10 05 ADDB 1005 EA=1005(BYTE2) D=02 
         13 A=00 B=0F X=0000 SP=00FF ------          1011: FB 10 06 ADDB 1006 EA=1006(BYTE7) D=07 
         14 A=00 B=16 X=0000 SP=00FF H-----          1014: FB 10 07 ADDB 1007 EA=1007(BYTE4) D=04 
         15 A=00 B=1A X=0000 SP=00FF ------          1017: 01       NOP                      

Breakpoint!
>        16 A=00 B=1A X=0000 SP=00FF ------ DONE     1018: 01       NOP                      

6800 Monitor: Ctrl-C to exit, 'c' to continue, or type 'help'
% 

And we can see it adding those up in the B accumulator.

The 6801 version will be pretty much exactly the same, but go ahead and change directories to where you built my 6801-enabled version of EXORsim and run a session to prove it to yourself:

$ ./exor --6801 --mon
Load facts file 'facts'
'exbug.bin' loaded.
'mdos.dsk' opened for drive 0 (single sided)

Hit Ctrl-C for simulator command line.  Starting simulation...

>         0 A=00 B=00 X=0000 SP=FF8A ------ OSLOAD   E800: 8E FF 8A LDS #$FF8A                Load OS

Type 'help'
%                                                                                                         
% a 1000
1000: ENTRY	JMP	START
1003: *
1003: BYTE8	FCB	8
Huh?: BYTE5	FCB	5
% 05: BYTE2	FCB	2
% 06: BYTE7	FCB	7
% 07: BYTE4	FCB	4
% 08: *
% 08: START	LDAB	BYTE8
% dress at 1001 set to 1008
% 0b: 	ADDB	BYTE5
% 0e: 	ADDB	BYTE2
% 11: 	ADDB	BYTE7	
% 14: 	ADDB	BYTE4
% 17: 	NOP
% 18: DONE	NOP	
% 19: 
% u 1000   
% 00: 7E 10 08            JMP $1008
% 03: 08                  INX
% 04: 05                  LSLD
% 05: 02                  ???
% 06: 07                  TPA
% 07: 04                  LSRD
% 08: F6 10 03            LDB $1003
% 0B: FB 10 04            ADDB $1004
% 0E: FB 10 05            ADDB $1005
% 11: FB 10 06            ADDB $1006
% 14: FB 10 07            ADDB $1007
% 17: 01                  NOP
% 18: 01                  NOP
% 19: 00                  ???
% 1A: 00                  ???
...

And so forth. 

One thing you'll notice is that, on the 6801, op-codes $05 and $04 are defined, whereas they are not on the 6800.

But we aren't using those, and even if you had a real 6801, even then, the timing would be the same. 

You may be wondering why I switched to using B to accumulate the sums here. As I mentioned in the chapter on getting started on the 6801, conventions on which accumulator was less significant switched between the 6800 and the 6809, and the 6801 follows the 6809 byte order in the D register instructions. But it follows the hard-wired 6800 stacking order during interrupts, to avoid too many surprises when re-using code from the 6800.

From here on out, we'll also be following the 6809 conventions on byte order where we can, not that you'll really notice it very often.

Now, let's see what happens on the 6809. In the first line of the code, change the LDAB to LDB:

ENTRY	JMP	START
...

The rest is the same source code.

Change back to Joe Allen's updated EXORsim and run a 6809 session. Paste in the code, making sure the LDAB is changed to LDB:

$ ./exor09 --mon
Load facts file 'facts09'
'exbug09.bin' loaded.
  EXBUG09-2.1 detected
'mdos09.dsk' opened for drive 0 (double sided)

OSLOAD...

Hit Ctrl-C for simulator command line.  Starting simulation...

>         0 A=00 B=00 X=0000 Y=0000 U=0000 S=00FF P=00 --------            0020: 86 10        LDA #$10                   

6809 Monitor: Ctrl-C to exit, 'c' to continue, or type 'help'
% a 1000
1000: ENTRY	JMP	START
later = 1001
1003: *
1003: BYTE8	FCB	8
1004: BYTE5	FCB	5
1005: BYTE2	FCB	2
1006: BYTE7	FCB	7
1007: BYTE4	FCB	4
1008: *
1008: START	LDB	BYTE8
Address at 1001 set to 1008
100b: 	ADDB	BYTE5
100e: 	ADDB	BYTE2
1011: 	ADDB	BYTE7	
1014: 	ADDB	BYTE4
1017: 	NOP
1018: DONE	NOP	
1019: 
% u 1000
1000: 7E 1008             JMP $1008
1003: 08 05               ASL $05
1005: 02 07               ??? $07
1007: 04 F6               LSR $f6
1009: 1003 FB             COM $fb
100C: 1004 FB             LSR $fb
100F: 1005 FB             ??? $fb
1012: 1006 FB             ROR $fb
1015: 1007 12             ASR $12
1018: 12                  NOP 
1019: 00 00               NEG $00
101B: 00 00               NEG $00
...

YIKES! 

Oh. The op-code map is a little different on the 6809, and our small constant integers are actually two-byte op-codes instead of one-byte -- which throws the disassembly off at address $1007.

Easy. We can note that the JMP at ENTRY is to address $1008, and disassemble from there:

% u 1008
1008: F6 1003             LDB $1003
100B: FB 1004             ADDB $1004
100E: FB 1005             ADDB $1005
1011: FB 1006             ADDB $1006
1014: FB 1007             ADDB $1007
1017: 12                  NOP 
1018: 12                  NOP 
1019: 00 00               NEG $00
101B: 00 00               NEG $00
...

Okay, the op-codes are a little bit different in places, but that doesn't hurt us.

% b 1018
Breakpoint set at 1018
% t on
% c 1000

          0 A=00 B=00 X=0000 Y=0000 U=0000 S=00FF P=00 -------- ENTRY      1000: 7E 1008      JMP $1008                  
          1 A=00 B=00 X=0000 Y=0000 U=0000 S=00FF P=00 -------- START      1008: F6 1003      LDB $1003   EA=1003(BYTE8) D=08 
          2 A=00 B=08 X=0000 Y=0000 U=0000 S=00FF P=00 --------            100B: FB 1004      ADDB $1004  EA=1004(BYTE5) D=05 
          3 A=00 B=0D X=0000 Y=0000 U=0000 S=00FF P=00 --------            100E: FB 1005      ADDB $1005  EA=1005(BYTE2) D=02 
          4 A=00 B=0F X=0000 Y=0000 U=0000 S=00FF P=00 --------            1011: FB 1006      ADDB $1006  EA=1006(BYTE7) D=07 
          5 A=00 B=16 X=0000 Y=0000 U=0000 S=00FF P=00 --H-----            1014: FB 1007      ADDB $1007  EA=1007(BYTE4) D=04 
          6 A=00 B=1A X=0000 Y=0000 U=0000 S=00FF P=00 --------            1017: 12           NOP                        

Breakpoint!
>         7 A=00 B=1A X=0000 Y=0000 U=0000 S=00FF P=00 -------- DONE       1018: 12           NOP                        

6809 Monitor: Ctrl-C to exit, 'c' to continue, or type 'help'
%  

The 6809 version works exactly the same.

So we should be able to move on to the 68000. 

Except.

The 6809

      has

            something called PC-relative addressing.

OHHHHHHHH HORRORS!!!!

8-o

;->

Take a look at the disassembly in the code above. Where immediate mode addressing buried the actual constants in the code, you'll see the addresses of the constants buried in the code.

	LDB	BYTE8

becomes

F6 1003 

if you start the assembly at $1000.

But if you start the assembly at $1100, BYTE8 will become a label for $1108, and the object code will assemble as "FB 1103".

Which means that, once you have assembled the code, you can't just copy the code somewhere else to run it. You have to do something called relocation, where you dig through every address in the object code and adjust it for where it want it to end up.

Relocation is not as hard as it sounds. Sort-of. (Gurgle. Cough. Sigh.) Once you know what assumptions you can make. Which ...

We don't want to go there just yet.

PC-relative code can be moved without relocation math. Which is why it is called position-independent, or, sometimes, "relocatable". Erk.

We'll talk about this more later, but right now I think we should take a quick advance look at it.

Here's how PC relative code for the global constants looks for the code above:

ENTRY	JMP	START
*
BYTE8	FCB	8
BYTE5	FCB	5
BYTE2	FCB	2
BYTE7	FCB	7
BYTE4	FCB	4
*
START	LDB	BYTE8,PCR    ; address relative to PC
	ADDB	BYTE5,PCR
	ADDB	BYTE2,PCR
	ADDB	BYTE7,PCR
	ADDB	BYTE4,PCR
	NOP
DONE	NOP

Quit your EXORsim 6809 session and start a fresh one, and paste the above code in.

$ ./exor09 --mon
Load facts file 'facts09'
'exbug09.bin' loaded.
  EXBUG09-2.1 detected
'mdos09.dsk' opened for drive 0 (double sided)

OSLOAD...

Hit Ctrl-C for simulator command line.  Starting simulation...

>         0 A=00 B=00 X=0000 Y=0000 U=0000 S=00FF P=00 --------            0020: 86 10        LDA #$10                   

6809 Monitor: Ctrl-C to exit, 'c' to continue, or type 'help'
% a 1000
1000: ENTRY	JMP	START
later = 1001
1003: *
1003: BYTE8	FCB	8
1004: BYTE5	FCB	5
1005: BYTE2	FCB	2
1006: BYTE7	FCB	7
1007: BYTE4	FCB	4
1008: *
1008: START	LDB	BYTE8,PCR
Address at 1001 set to 1008
100b: 	ADDB	BYTE5,PCR
100e: 	ADDB	BYTE2,PCR
1011: 	ADDB	BYTE7,PCR
1014: 	ADDB	BYTE4,PCR
1017: 	NOP
1018: DONE	NOP
1019: 
% u 1000
1000: 7E 1008             JMP $1008
1003: 08 05               ASL $05
1005: 02 07               ??? $07
1007: 04 E6               LSR $e6
1009: 8C F8EB             CMPX #$f8eb
100C: 8C F6EB             CMPX #$f6eb
100F: 8C F4EB             CMPX #$f4eb
1012: 8C F2EB             CMPX #$f2eb
1015: 8C F012             CMPX #$f012
1018: 12                  NOP 
1019: 00 00               NEG $00
101B: 00 00               NEG $00
...
% u 1008 1008: E6 8C F8 LDB $1003,PCR 100B: EB 8C F6 ADDB $1004,PCR 100E: EB 8C F4 ADDB $1005,PCR 1011: EB 8C F2 ADDB $1006,PCR 1014: EB 8C F0 ADDB $1007,PCR 1017: 12 NOP 1018: 12 NOP 1019: 00 00 NEG $00 101B: 00 00 NEG $00 ...
% b 1018 Breakpoint set at 1018 % t on % c 1000 0 A=00 B=00 X=0000 Y=0000 U=0000 S=00FF P=00 -------- ENTRY 1000: 7E 1008 JMP $1008 1 A=00 B=00 X=0000 Y=0000 U=0000 S=00FF P=00 -------- START 1008: E6 8C F8 LDB $1003,PCR EA=1003(BYTE8) D=08 2 A=00 B=08 X=0000 Y=0000 U=0000 S=00FF P=00 -------- 100B: EB 8C F6 ADDB $1004,PCR EA=1004(BYTE5) D=05 3 A=00 B=0D X=0000 Y=0000 U=0000 S=00FF P=00 -------- 100E: EB 8C F4 ADDB $1005,PCR EA=1005(BYTE2) D=02 4 A=00 B=0F X=0000 Y=0000 U=0000 S=00FF P=00 -------- 1011: EB 8C F2 ADDB $1006,PCR EA=1006(BYTE7) D=07 5 A=00 B=16 X=0000 Y=0000 U=0000 S=00FF P=00 --H----- 1014: EB 8C F0 ADDB $1007,PCR EA=1007(BYTE4) D=04 6 A=00 B=1A X=0000 Y=0000 U=0000 S=00FF P=00 -------- 1017: 12 NOP Breakpoint! > 7 A=00 B=1A X=0000 Y=0000 U=0000 S=00FF P=00 -------- DONE 1018: 12 NOP 6809 Monitor: Ctrl-C to exit, 'c' to continue, or type 'help' % q Bye

And, even though we only see the offsets in the code, the processor found the constants and was able to add them up.

We'll try more interesting things with this idea later. For now, file it away, and let's move on to the 68000 version of this. The code for absolute addressing first:

	OPT LIST,SYMTAB	; Options we want for the stand-alone assembler.
	MACHINE MC68000	; because there are a lot the assembler can do.
	OPT DEBUG	; We want the debugger to know our labels.
	OUTPUT
***********************************************************************
*	EVEN		; byte data doesn't have to be aligned.
ENTRY	JMP	START
*
BYTE8	DC.B	8
BYTE5	DC.B	5
BYTE2	DC.B	2
BYTE7	DC.B	7
BYTE4	DC.B	4
*
	EVEN		; But 68K code does have to be even aligned.
START	MOVE.B	BYTE8,D1
	ADD.B	BYTE5,D1
	ADD.B	BYTE2,D1
	ADD.B	BYTE7,D1
	ADD.B	BYTE4,D1
	NOP
DONE	NOP
* One way to return to the OS or other calling program
	clr.w	-(sp)	; there should be enough room on the caller's stack
	trap	#1			quick exit

Note that, instead of 

Form Constant Byte (FCB), 

the 68000 uses 

Define Constant Byte (DC.B).

Also notice that I've added a little formal quitting code at the end. And that's another something to talk about later.

I'm saving the source code as sumconst.s under in the working directory I made under the Hatari C drive emulation directory. In the above example, that would be

~/asmwork/share/hatari/C/primer/sumconst.s

And I'm assembling it with

$ vasmm68k_mot -Ftos -o SUMCONST.PRG -L sumconst.lst sumconst.s

Here's the listing it produces:

Sections:
00: "text" (0-24)


Source: "sumconst.s"
                            	     1: 	OPT LIST,SYMTAB	; Options we want for the stand-alone assembler.
                            	     2: 	MACHINE MC68000	; because there are a lot the assembler can do.
                            	     3: 	OPT DEBUG	; We want the debugger to know our labels.
                            	     4: 	OUTPUT
                            	     5: ***********************************************************************
                            	     6: *	EVEN		; byte data doesn't have to be aligned.
00:00000000 6006            	     7: ENTRY	JMP	START
                            	     8: *
00:00000002 08              	     9: BYTE8	DC.B	8
00:00000003 05              	    10: BYTE5	DC.B	5
00:00000004 02              	    11: BYTE2	DC.B	2
00:00000005 07              	    12: BYTE7	DC.B	7
00:00000006 04              	    13: BYTE4	DC.B	4
                            	    14: *
                            	    15: 	EVEN		; But 68K code does have to be even aligned.
00:00000008 123AFFF8        	    16: START	MOVE.B	BYTE8,D1
00:0000000C D23AFFF5        	    17: 	ADD.B	BYTE5,D1
00:00000010 D23AFFF2        	    18: 	ADD.B	BYTE2,D1
00:00000014 D23AFFEF        	    19: 	ADD.B	BYTE7,D1
00:00000018 D23AFFEC        	    20: 	ADD.B	BYTE4,D1
00:0000001C 4E71            	    21: 	NOP
00:0000001E 4E71            	    22: DONE	NOP
                            	    23: * One way to return to the OS or other calling program
00:00000020 4267            	    24: 	clr.w	-(sp)	; there should be enough room on the caller's stack
00:00000022 4E41            	    25: 	trap	#1			quick exit

If you know how to read the op-codes, you'll notice that the assembler seems to be doing something you're not expecting. Let's check.

Get Hatari running, Ctrl-Z to escape GEM and get a TOS (CP/M) shell, Alt-Pause to enter the debugger, use the mouse to bring up the host shell you started Hatari in, and set the breakpoint (b) to break when the CPU jumps to the TEXT segment, then do a continue (c) to return to the TOS (CP/M) shell:

CPU condition breakpoint 1 with 1 condition(s) added:
	pc = TEXT
-> Break only once, and delete breakpoint afterwards.
> c
Returning to emulation...

Invoke the assembled code in the TOS shell:

C:\primer>SUMCONST.PRG

Use the mouse to get back to the host shell where the debugger has taken the break and is waiting for you. Disassemble (d):

> d
(PC)
ENTRY:
00013d10 6006                     bra.b #$06 == $00013d18 (T)
BYTE8:
00013d12 0805 0207                btst.l #$0207,d5
BYTE4:
00013d16 0400 123a                sub.b #$3a,d0
00013d1a fff8                     illegal 
00013d1c d23a fff5                add.b (pc,$fff5) == $00013d13 [05],d1
00013d20 d23a fff2                add.b (pc,$fff2) == $00013d14 [02],d1
00013d24 d23a ffef                add.b (pc,$ffef) == $00013d15 [07],d1
00013d28 d23a ffec                add.b (pc,$ffec) == $00013d16 [04],d1
00013d2c 4e71                     nop 
DONE:
00013d2e 4e71                     nop 
00013d30 4267                     clr.w -(a7) [0000]
00013d32 4e41                     trap #$01
00013d34 0000 0000                or.b #$00,d0
00013d38 0000 0000                or.b #$00,d0
...

Didn't remember a BRAnch instruction?

My neither. That was supposed to be a JuMP.

It's getting confused trying to disassemble constants as code, so look at the target for the branch ($00013d18) and disassemble from there. Except you forgot that the Hatari debugger expects decimal unless you tell it otherwise. So you try again, typing it in as proper hexadecimal:

> d 13d18
Extra characters in decimal based number '13d18'!
Invalid address value '13d18'!
> d $13d18
START:
00013d18 123a fff8                move.b (pc,$fff8) == $00013d12 [08],d1
00013d1c d23a fff5                add.b (pc,$fff5) == $00013d13 [05],d1
00013d20 d23a fff2                add.b (pc,$fff2) == $00013d14 [02],d1
00013d24 d23a ffef                add.b (pc,$ffef) == $00013d15 [07],d1
00013d28 d23a ffec                add.b (pc,$ffec) == $00013d16 [04],d1
00013d2c 4e71                     nop 
DONE:
00013d2e 4e71                     nop 
00013d30 4267                     clr.w -(a7) [0000]
00013d32 4e41                     trap #$01
00013d34 0000 0000                or.b #$00,d0
00013d38 0000 0000                or.b #$00,d0
...
>

Twilight Zone ? (Cue creepy music.)

LoL. Optimization. It's using the PC relative modes because they take fewer bytes. We wanted to do that later, but we wanted to do that explicitly. Not yet. Not here.

This is one of the reasons I was hesitant to use vasm.

Sigh. Well, there are switches to turn optimizations off. Not really a problem.

I'd go ahead and step through the optimized code, but we want to do that in a few more minutes, with explicit code. So, for now, let's (q)uit out of the debugger and Hatari and try again:

$ vasmm68k_mot -Ftos -no-opt -o SUMCONST.PRG -L sumconst.lst sumconst.s

Here's the listing it produces with the no-opts switch:

Sections:
00: "text" (0-32)


Source: "sumconst.s"
                            	     1: 	OPT LIST,SYMTAB	; Options we want for the stand-alone assembler.
                            	     2: 	MACHINE MC68000	; because there are a lot the assembler can do.
                            	     3: 	OPT DEBUG	; We want the debugger to know our labels.
                            	     4: 	OUTPUT
                            	     5: ***********************************************************************
                            	     6: *	EVEN		; byte data doesn't have to be aligned.
00:00000000 4EF90000000C    	     7: ENTRY	JMP	START
                            	     8: *
00:00000006 08              	     9: BYTE8	DC.B	8
00:00000007 05              	    10: BYTE5	DC.B	5
00:00000008 02              	    11: BYTE2	DC.B	2
00:00000009 07              	    12: BYTE7	DC.B	7
00:0000000A 04              	    13: BYTE4	DC.B	4
                            	    14: *
                            	    15: 	EVEN		; But 68K code does have to be even aligned.
00:0000000C 123900000006    	    16: START	MOVE.B	BYTE8,D1
00:00000012 D23900000007    	    17: 	ADD.B	BYTE5,D1
00:00000018 D23900000008    	    18: 	ADD.B	BYTE2,D1
00:0000001E D23900000009    	    19: 	ADD.B	BYTE7,D1
00:00000024 D2390000000A    	    20: 	ADD.B	BYTE4,D1
00:0000002A 4E71            	    21: 	NOP
00:0000002C 4E71            	    22: DONE	NOP
                            	    23: * One way to return to the OS or other calling program
00:0000002E 4267            	    24: 	clr.w	-(sp)	; there should be enough room on the caller's stack
00:00000030 4E41            	    25: 	trap	#1			quick exit
                            	    26: 
                            	    27: 
                            	    28: 


Symbols by name:
BYTE2                           00:00000008
BYTE4                           00:0000000A
BYTE5                           00:00000007
BYTE7                           00:00000009
BYTE8                           00:00000006
DONE                            00:0000002C
ENTRY                           00:00000000
START                           00:0000000C

Symbols by value:
00000000 ENTRY
00000006 BYTE8
00000007 BYTE5
00000008 BYTE2
00000009 BYTE7
0000000A BYTE4
0000000C START
0000002C DONE  

That looks much better. Let's get it into the debugger. 

Again, 

  1. Start Hatari.
  2. When the GUI comes up, use Ctrl-Z to break out of GEM into the TOS command line shell.
  3. Change to the primer directory:
    C:\>CD PRIMER
    C:\primer>
  4. Use Alt-PAUSE to break out of TOS shell into the debugger.
  5. Set a PC breakpoint at the beginning of the TEXT segment:
    >b pc = TEXT :once
  6. (c)ontinue back to TOS shell.
  7. Invoke the executable created by the assembler:
    C:\primer>SUMCONST.PRG

Then we can

  • (d)isassemble the code (from START this time)
  • (m)emory dump (notice I'm using the count argument this time)
  • and alternate
    • (r)egister dump
    • (s)tep
  • until we hit the landing pad

as follows:

    ----------------------------------------------------------------------
    You have entered debug mode. Type c to continue emulation, h for help.
    
    CPU=$e1d7e2, VBL=1385, FrameCycles=128, HBL=0, LineCycles=128, DSP=N/A
    00e1d7e2 46c0                     move.w d0,sr
    > b pc = TEXT :once
    CPU condition breakpoint 1 with 1 condition(s) added:
    	pc = TEXT
    -> Break only once, and delete breakpoint afterwards.
    > c
    Returning to emulation...
    1. CPU breakpoint condition(s) matched 1 times.
    	pc = TEXT :once
    Removed CPU breakpoint 1:
    	pc = TEXT :once
    Reading symbols from program '/home/nova/usr/share/hatari/C:/primer/SUMCONST.PRG' symbol table...
    TOS executable, DRI / GST symbol table, reloc=0, program flags: PRIVATE (0x0)
    Program section sizes:
      text: 0x32, data: 0x0, bss: 0x0, symtab: 0x70
    Trying to load DRI symbol table at offset 0x4e...
    Offsetting BSS/DATA symbols from TEXT section.
    Skipping duplicate address & symbol name checks when autoload is enabled.
    Loaded 8 symbols (8 TEXT) from '/home/nova/usr/share/hatari/C:/primer/SUMCONST.PRG'.
    
    CPU=$13d10, VBL=2479, FrameCycles=63376, HBL=62, LineCycles=384, DSP=N/A
    00013d10 4ef9 0001 3d1c           jmp $00013d1c
    > d $13d1c
    START:
    00013d1c 1239 0001 3d16           move.b $00013d16 [08],d1
    00013d22 d239 0001 3d17           add.b $00013d17 [05],d1
    00013d28 d239 0001 3d18           add.b $00013d18 [02],d1
    00013d2e d239 0001 3d19           add.b $00013d19 [07],d1
    00013d34 d239 0001 3d1a           add.b $00013d1a [04],d1
    00013d3a 4e71                     nop 
    DONE:
    00013d3c 4e71                     nop 
    00013d3e 4267                     clr.w -(a7) [0000]
    00013d40 4e41                     trap #$01
    00013d42 0000 0000                or.b #$00,d0
    00013d46 0000 0000                or.b #$00,d0
    ...
    > m $13d10 64
    00013D10: 4e f9 00 01 3d 1c 08 05 02 07 04 00 12 39 00 01   N...=........9..
    00013D20: 3d 16 d2 39 00 01 3d 17 d2 39 00 01 3d 18 d2 39   =..9..=..9..=..9
    00013D30: 00 01 3d 19 d2 39 00 01 3d 1a 4e 71 4e 71 42 67   ..=..9..=.NqNqBg
    00013D40: 4e 41 00 00 00 00 00 00 00 00 00 00 00 00 00 00   NA..............
    > r               
      D0 00000000   D1 00000000   D2 00000000   D3 00000000 
      D4 00000000   D5 00000000   D6 00000000   D7 00000000 
      A0 00000000   A1 00000000   A2 00000000   A3 00000000 
      A4 00013D42   A5 00013D42   A6 00077FC6   A7 00077FF8 
    USP  00077FF8 ISP  00007E64 
    T=00 S=0 M=0 X=0 N=0 Z=0 V=0 C=0 IMASK=3 STP=0
    Prefetch 4ef9 (JMP) 0001 (OR) Chip latch 00000000
    00013d10 4ef9 0001 3d1c           jmp $00013d1c
    Next PC: 00013d16
    > s
    
    CPU=$13d1c, VBL=2479, FrameCycles=63388, HBL=62, LineCycles=396, DSP=N/A
    00013d1c 1239 0001 3d16           move.b $00013d16 [08],d1
    > r
      D0 00000000   D1 00000000   D2 00000000   D3 00000000 
      D4 00000000   D5 00000000   D6 00000000   D7 00000000 
      A0 00000000   A1 00000000   A2 00000000   A3 00000000 
      A4 00013D42   A5 00013D42   A6 00077FC6   A7 00077FF8 
    USP  00077FF8 ISP  00007E64 
    T=00 S=0 M=0 X=0 N=0 Z=0 V=0 C=0 IMASK=3 STP=0
    Prefetch 1239 (MOVE) 0001 (OR) Chip latch 00000000
    00013d1c 1239 0001 3d16           move.b $00013d16 [08],d1
    Next PC: 00013d22
    > s
    
    CPU=$13d22, VBL=2479, FrameCycles=63404, HBL=62, LineCycles=412, DSP=N/A
    00013d22 d239 0001 3d17           add.b $00013d17 [05],d1
    > r
      D0 00000000   D1 00000008   D2 00000000   D3 00000000 
      D4 00000000   D5 00000000   D6 00000000   D7 00000000 
      A0 00000000   A1 00000000   A2 00000000   A3 00000000 
      A4 00013D42   A5 00013D42   A6 00077FC6   A7 00077FF8 
    USP  00077FF8 ISP  00007E64 
    T=00 S=0 M=0 X=0 N=0 Z=0 V=0 C=0 IMASK=3 STP=0
    Prefetch d239 (ADD) 0001 (OR) Chip latch 00000000
    00013d22 d239 0001 3d17           add.b $00013d17 [05],d1
    Next PC: 00013d28
    > s
    
    CPU=$13d28, VBL=2479, FrameCycles=63420, HBL=62, LineCycles=428, DSP=N/A
    00013d28 d239 0001 3d18           add.b $00013d18 [02],d1
    > r
      D0 00000000   D1 0000000D   D2 00000000   D3 00000000 
      D4 00000000   D5 00000000   D6 00000000   D7 00000000 
      A0 00000000   A1 00000000   A2 00000000   A3 00000000 
      A4 00013D42   A5 00013D42   A6 00077FC6   A7 00077FF8 
    USP  00077FF8 ISP  00007E64 
    T=00 S=0 M=0 X=0 N=0 Z=0 V=0 C=0 IMASK=3 STP=0
    Prefetch d239 (ADD) 0001 (OR) Chip latch 00000000
    00013d28 d239 0001 3d18           add.b $00013d18 [02],d1
    Next PC: 00013d2e
    > s
    
    CPU=$13d2e, VBL=2479, FrameCycles=63436, HBL=62, LineCycles=444, DSP=N/A
    00013d2e d239 0001 3d19           add.b $00013d19 [07],d1
    > r
      D0 00000000   D1 0000000F   D2 00000000   D3 00000000 
      D4 00000000   D5 00000000   D6 00000000   D7 00000000 
      A0 00000000   A1 00000000   A2 00000000   A3 00000000 
      A4 00013D42   A5 00013D42   A6 00077FC6   A7 00077FF8 
    USP  00077FF8 ISP  00007E64 
    T=00 S=0 M=0 X=0 N=0 Z=0 V=0 C=0 IMASK=3 STP=0
    Prefetch d239 (ADD) 0001 (OR) Chip latch 00000000
    00013d2e d239 0001 3d19           add.b $00013d19 [07],d1
    Next PC: 00013d34
    > s
    
    CPU=$13d34, VBL=2479, FrameCycles=63452, HBL=62, LineCycles=460, DSP=N/A
    00013d34 d239 0001 3d1a           add.b $00013d1a [04],d1
    > r
      D0 00000000   D1 00000016   D2 00000000   D3 00000000 
      D4 00000000   D5 00000000   D6 00000000   D7 00000000 
      A0 00000000   A1 00000000   A2 00000000   A3 00000000 
      A4 00013D42   A5 00013D42   A6 00077FC6   A7 00077FF8 
    USP  00077FF8 ISP  00007E64 
    T=00 S=0 M=0 X=0 N=0 Z=0 V=0 C=0 IMASK=3 STP=0
    Prefetch d239 (ADD) 0001 (OR) Chip latch 00000000
    00013d34 d239 0001 3d1a           add.b $00013d1a [04],d1
    Next PC: 00013d3a
    > s
    
    CPU=$13d3a, VBL=2479, FrameCycles=63468, HBL=62, LineCycles=476, DSP=N/A
    00013d3a 4e71                     nop 
    

    At this point, we can either (q)uit the whole Hatari session, or, since I have added that extra code that allows us to return to what called us, (c) and go back to the TOS shell.

    Why did I put the sum in D1 this time instead of D0? Good question. If we make a really fine point of it, I could have kept the sum in D7, instead. LoL. We have lots of registers in the 68000, and which one might correspond to B is just a matter of convention -- although there is stack order, and, in the 68020, there are 64-bit results that are put in register pairs, so ...

    But let's look at what happens when we (explicitly this time) use PC relative addressing. Here's the code:

    	OPT LIST,SYMTAB	; Options we want for the stand-alone assembler.
    	MACHINE MC68000	; because there are a lot the assembler can do.
    	OPT DEBUG	; We want labels for debugging.
    	OUTPUT
    ***********************************************************************
    *	EVEN		; byte data doesn't have to be aligned.
    ENTRY	JMP	START
    *
    BYTE8	DC.B	8
    BYTE5	DC.B	5
    BYTE2	DC.B	2
    BYTE7	DC.B	7
    BYTE4	DC.B	4
    *
    	EVEN		; But 68K code does have to be even aligned.
    START	MOVE.B	BYTE8(PC),D1
    	ADD.B	BYTE5(PC),D1
    	ADD.B	BYTE2(PC),D1
    	ADD.B	BYTE7(PC),D1
    	ADD.B	BYTE4(PC),D1
    	NOP
    DONE	NOP
    * One way to return to the OS or other calling program
    	clr.w	-(sp)	; there should be enough room on the caller's stack
    	trap	#1			quick exit
    

    and the listing file:

    Sections:
    00: "text" (0-28)
    
    
    Source: "sumconpc.s"
                                	     1: 	OPT LIST,SYMTAB	; Options we want for the stand-alone assembler.
                                	     2: 	MACHINE MC68000	; because there are a lot the assembler can do.
                                	     3: 	OPT DEBUG	; We want labels for debugging.
                                	     4: 	OUTPUT
                                	     5: ***********************************************************************
                                	     6: *	EVEN		; byte data doesn't have to be aligned.
    00:00000000 4EF90000000C    	     7: ENTRY	JMP	START
                                	     8: *
    00:00000006 08              	     9: BYTE8	DC.B	8
    00:00000007 05              	    10: BYTE5	DC.B	5
    00:00000008 02              	    11: BYTE2	DC.B	2
    00:00000009 07              	    12: BYTE7	DC.B	7
    00:0000000A 04              	    13: BYTE4	DC.B	4
                                	    14: *
                                	    15: 	EVEN		; But 68K code does have to be even aligned.
    00:0000000C 123AFFF8        	    16: START	MOVE.B	BYTE8(PC),D1
    00:00000010 D23AFFF5        	    17: 	ADD.B	BYTE5(PC),D1
    00:00000014 D23AFFF2        	    18: 	ADD.B	BYTE2(PC),D1
    00:00000018 D23AFFEF        	    19: 	ADD.B	BYTE7(PC),D1
    00:0000001C D23AFFEC        	    20: 	ADD.B	BYTE4(PC),D1
    00:00000020 4E71            	    21: 	NOP
    00:00000022 4E71            	    22: DONE	NOP
                                	    23: * One way to return to the OS or other calling program
    00:00000024 4267            	    24: 	clr.w	-(sp)	; there should be enough room on the caller's stack
    00:00000026 4E41            	    25: 	trap	#1			quick exit
    

    And I'm going to let you step through it yourself instead of showing you what it looks like here. It's basically just a different way to get at the same data, like on the 6809, but with 32-bit addresses and 16-bit offsets to deal with. And we'll actually talk about that all later.

    This has been another really long one. As we say in Japan, 「ご苦労様。」 (Go-kurō-sama.) You've worked hard, even if you just read through and didn't take time to enjoy plugging the code in to watch it run.

    Wait a minute. If you just read through and didn't step or trace through the simulations, you've really missed out on all the fun. Go back. Watch the code execute. Play with things just a little to see if you can make anything interesting happen. 

    And now we can really say, “ご苦労様。”

    The next thing we need to do is take a small detour to make sure you have tools to deal with all the hexadecimal math that has your eyes glazing over.


    (Title Page/Index)