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)

     

    Friday, July 12, 2024

    ALPP 01-05 -- Getting a Simulator for the 68000 and Running Code on It (Hatari, vasm)

    Getting a Simulator for the 68000
    and Running Code on It
    (Hatari, vasm)

    (Title Page/Index)

     

    Again, I'm assuming you've gone through the previous chapters, bringing EXORsim up for the 6800 and 6809 and running code on it, and also bringing up my fork of EXORsim for the 6801, with code running on it as well. 

    I'll be implicitly referring here and there back to things we did in those chapters, so if you get lost, at least go back and make sure you've read and understood those and preferably got those emulators working before leaving questions here.

    Now, if someone had written an EXORmacs simulator in the same vein as Joe Allen's EXORsim, we'd be in business. I'd use that for the 68000 code here. Or even an EXORsim simulator for the MEX68KDM 68K developer module card for the EXORciser that Motorola mentioned in some of their materials.

    (I seem to remember a simulator for a Versabus or VME based computer in the SIMH project, but I'm not seeing that now. I probably should take a look at the SAGE simulator in openSIMH, but, again, that's a lot of reading documentation and building more code, and I'm not sure it would be any easier to use than Hatari.)

    There's also Easy68k or something, but it only runs on MSWindows, and I don't do Wine, and I don't recommend things I don't do.

    So, options lacking, doing this (myself) right for the 68000 would possibly involve something like writing up an instance of Musashi that would run in some sort of terminal arrangement, perhaps as an alternate history main processor board for the EXORciser if I can't find documentation for the MEX68KDM. 

    Or do something to emulate the single board MEX68KECB. 

    And I'm not sure how much of the disassembler, general debugger, interactive assembler, etc. I'd have to add, so maybe I might as well plan on writing the emulator proper, as well, instead of planning on using Musashi.

    And all of that would be more postponing this tutorial. 

    So, ...

    Hatari

    What's the difference between an emulator and a simulator?

    Some people say that emulators are done in hardware and simulators in software. But the Hatari project calls their Atari ST simulator an emulator.

    Hatari is in the Debian package repositories, and therefore in Ubuntu and many other Linux OS package repositories, as well. And there are RPMs for Cent OS and such. You can download it from the project site and work through getting all the prerequisite dependencies installed before building it yourself if you want. But that's another steep learning curve if you haven't done it before, and a fair amount of work even if you have.

    Although that's exactly what I have done in my current workstation. I installed the dependencies syetem-globally from packages, then built Hatari and installed it locally in my daily-driving user account. It keeps the side-effects local.

    So having it in packages is a really useful thing if you aren't building it yourself. Your package manager can work out the dependencies, and install them all together at once with Hatari, in just a few minutes or tens of minutes, depending on your internet connection.

    Fortunately for you who are running MSWindows, there are pre-compiled .exe executables for you in the downloads area of the website, in .zip archives.

    Go ahead and spend a few minutes looking around the Hatari site, especially the docs. Things to look for, 

    • EmuTOS
    • Keyboard mappings 
    • Disk images
    • Emulating disks in hosted directories
    • Getting Hisoft Devpac or similar software 
    • The built-in debugger

    Check to see if your package manager includes Hatari. (Cygwin for MSWindows does not, at present. Several package managers for Mac do. Most Linux OSses do. NetBSD and FreeBSD do.) 

    If it doesn't, spend some time checking the build instructions. Write down the list of dependencies and start collecting and either installing them from packages or building them. Etc. LoL. If you are running an OS for which you need to build it, I'm pretty sure you know how to do this without me explaining it. 

    If, for whatever reason, you do build it yourself, you can specify your local executable directory as the install target. In the example structure I've been talking about, that might be 

    ~/prg

    and in that case you would specify 

    /home/me/prg 

    as the target.

    Before we proceed, there is a not-so-minor issue with Hatari, in that we won't be able to do things like copying and pasting object or assembler source code into the running emulator window, the way we can with EXORsim. We'll have to edit source code files, save them in the directories Hatari is seeing as emulated disk, and assemble them with vasm or something similar.

    Speaking of directories, familiarize yourself with the directory structure your package system sets up for Hatari. In particular, as I mentioned, you want to know where to put EmuTOS so it can find it, or, more likely, you want to check where your package has installed EmuTOS. You also want to know where it will look for disk images and directories to emulate disks in. 

    If you have it installed properly, you can find a lot of answers by running

    $ hatari --help
    
    Hatari v2.4.0-devel (Dec 18 2021) - the Atari ST, STE, TT and Falcon emulator.
    
    Hatari is free software licensed under the GNU General Public License.
    
    Usage:
     hatari [options] [directory|disk image|Atari program]
    
    General options:
      --help or -h          Print this help text and exit
    ...
    

    EmuTOS

    EmuTOS is essentially a free-and-open-source replacement for TOS (in the short version of the story) -- the BIOS and OS kernel provided originally by Atari.

    You may find people telling you you need to download EmuTOS separately from Hatari, but that really isn't the case for our uses. The license for EmuTOS allows it to be packaged with Hatari and other emulators, so the tos.img that is in the package that your package manager makes for you, or is in the .zip or other archive you downloaded, should be EmuTOS. 

    That also means you don't have to extract the ROM from a real Atari ST or such to do what we want to do.

    Setup

    In fact, other than deciding where to set up the shared directory structure, it should just mostly work. Well, that and the keyboard mappings. You'll probably want to look carefully at keyboard mappings so you can get an idea where to find keys you need.

    Once it's installed (or built), running hatari from the command line without options should bring up an Atari desktop screen in emulation, like this:

     

    from which you can hit F12 to get a graphical dialog to set things up. 

    You may prefer to go that route, rather than sort through all the command-line options. Heh. You probably do want to use it. So I'll walk you through the relevant setup.

    By the way, F11 toggles full-screen mode off and on. This is useful, particularly when switching back and forth between Hatari and your host OS.

    You will usually want to use it with full screen off, because of all the things you'll be doing at the same time on the host machine. But, at least on my installation, with full-screen off, the mouse sometimes gets out of sync, and I have to toggle back to full-screen to use the mouse. However, I can pretty much always use the mouse in the setup dialog. Toggling is not usually a problem.

    Things you want to set up or check now include 

    • System -- Keep it ST mode to keep things simple.
      Blitter in ST mode, if it isn't already set. 
    • CPU -- 8 MHz can be slow, but more than 16 MHz can get weird.
      So, 16 MHz.
    • Memory -- You want at least 512K, and more than that can get weird for what we are doing.
      So, 512K.
    • Floppy disks and Hard disks -- You don't really need disk images, but, maybe you do ...

    We've got to talk about default directories for a moment. 

    When you install from your packages, it will likely set up several things it needs to keep track of under 

    /usr/local/share/hatari/

    or some such directory. This is in the file system area controlled by the operating system, and is outside your daily driving user's file system area. Handling file permissions to allow writing here can get strange. You should not want to do that. No, you really should not want to do that. So you'll want to make some adjustments. 

    Among the things it will by default save here, and look for here, are

    • the tos.img file it is using
    • the C: directory
    • keyboard mapping files 
    • disk image files

    I'm sure you can understand that we need these, and we need to know where they are.

    The tos.img file, we can leave alone, once we've noticed where it is. We won't need to touch it that much. (If we do need to, if we have a different tos.img file to use, the configuration menu lets select a different image in a different location without messing with /usr/local/share/.)

    The C: directory mapping, we will use extensively. We need to write to that directory a lot. So we need to point it somewhere within our daily-driver working directories, like (using unix/Linux syntax)

    ~/asmwork/share/hatari/C/

    which if you are working as user "me" on a LInux OS, might actually look like 

    /home/me/asmwork/share/hatari/C/

    I say might because it depends on how you set things up. I didn't set mine up exactly this way, but I'll use this as a basis to explain things. You should think a little about how you would want it, and set it up in a way that makes sense to you. 

    Your operating system gives you some sort of a home directory for the user you are logged in as, and the directory you decide to do most of your assembly language work in should reside under that. That directory would be "asmwork" in the paradigm above, or it could be "mywork/asm", or whatever you choose to call it. You'll probably want a directory under that which multiple programs you'll be working with can share, there under your assembly language working directory, and you might call it "share" -- thus, "asmwork/share" if you decide to do it that way.

    On MSWindows, you'll be wanting to work within the cygwin shell, so you'll use forward slashes instead of MS-DOS/CPM backslashes (or instead of yen mark -- ¥ -- in older Japanese language MSWindows OSses).  But looking at them or working on them in MSWindows command line shell, you will use the MSWindows slash -- "<something>\asmwork\share\hatari\C\" (or, in an older Japanese MSWindows OS, the yen mark --  "<something>¥asmwork¥share¥hatari¥C¥"). You'll need to get used to some differences between the Cygwin shell and the MSWindows shell.

    Not incidentally, I downloaded the 80 megabyte hard disk blank image that you can find on the Hatari download page and saved it in my local files, and saved a copy of it to be used as the hatari D: drive, for example, directly under 

    ~/asmwork/share/hatari/ 

    in the above directory conventions. I recommend you get that hard disk image and save it somewhere similar, also. You can then select it for your D: drive from the configuration dialog.

    So, continuing with the configuration dialog:

    • Hard disks -- write down the default for C: before you use eject and browse to set it appropriately in your own directory structure. You might want to remember where it was.
      Also, set up a D: drive with the blank image, as an IDE drive.
      When you're done with this, save the configuration once, just to be sure.
    • Floppy disks -- you can probably save yourself some error messages if you use the configuration dialog to create a couple of blank floppy images and set them up to be drives A: and B:. The setup dialog has buttons that do this for you.
    • Atari Screen -- look at what's here, in case you want to change something. But leave it at resolutions and colors the early ST models can handle, so things don't get weird.
    • Hatari Screen -- You probably want the status bar and GPU scaling.
    • Keyboard -- Remember that you can set the keyboard mapping here. In fact, go ahead and save a default configuration file in your local directories so you can look at it and play with it.
    • Load/Save config -- Note where it saves your configuration files in a hidden directory in your local directory structure, which is not under ~/asmwork/share/hatari/ in the above conventions. Quite likely, it will be under "~/.config/hatari/",

    So far, even if it isn't anything that seems all that interesting, you just need to see where things are, and, especially, move C: and save and set some keyboard mappings. 

    Go ahead and quit after saving anything you changed and want to save. It will give you one last chance to save things.

    Check again in your host OS that the C: directory is where you told it to be, for example, under the above conventions,

    ~/asmwork/share/hatari/C/

    Start Hatari again and when the desktop screen comes up, run EmuCON by selecting it from the File menu.

    Or you can just hit Ctrl-Z to run EmuCON. Either way works.

    It should put away the GUI screen and drop you into a command-line DOS (ergo, CP/M) shell. 

    Type some commands like HELP and DIR and such (You don't have to type in caps.), and see whether the keyboard needs configuration. If it does, look at the documentation and figure out what needs to be where.

    The following keyboard configuration file makes hyphen, brackets, and the caret available on my Japanese notebook keyboard, although not in optimal positions.

    -,12
    [,26
    ],27
    ^,13
    

    Some other key codes, I have to use the Fn key and the keypad to access. 

    You'll have to figure out what your keyboard needs, though, and it requires editing the keyboard configuration file as a text file in your host system, so don't hesitate to use pencil and paper if necessary to take notes. And be willing to make compromises, because they are trying to emulate the ST keyboard, not make the modern keyboard available in its entirety.

    When you have the keyboard working well enough, use EmuCON to make a working directory under the C: directory. You could do this in the host OS, as well, but we want to make sure the emulator is working and able to talk to the host OS on things like this. In the EmuCON console,do a DIR and see what the emulator thinks is there.

    C:\>DIR
      file not found

    make your working directory, maybe call it "PRIMER", and then change to it for fun:

    C:\>MD PRIMER
    C:\>DIR
    PRIMER
    C:\>CD PRIMER
    C:\primer>

    You can exit the command line shell with the EXIT command.

    Devpac

    I have been using Hisoft's Devpac for another project, and I was intending to discuss that, but when I went back to check where I got it from and such, I decided there is too much ambiguity and lack of provenance to recommend it. I found some evidence at the time that the folks at Hisoft had given explicit permission, but I can't find that evidence now.

    If you want to use it, you'll need to figure out the provenance and whether there is permission to use it for yourself. I can't help you with that.

    If you do decide to use Devpac, install it on the D: drive. It'll be easier to use that way. 

    More generally, you'll probably want to install any tools for working in the emulated Atari ST system in the emulated D: drive.

    Now, as I think I mentioned, Hatari has a built-in debugger. So we have the debugger requirement at least partially covered.

    But it doesn't do interactive assembly, and it doesn't include a separate assembler, so you'll need an assembler. I haven't written one of those for the 68000. But a guy named Volker Barthelmann and a guy name Frank Wille have worked together and made something that can help us, called vasm, and made it available for non-commercial use -- which I think covers what we are doing here.

    vasm

    I guess vasm is named for "Volker's Assembler". It's a really complicated beast, and does way more than we need, but we can use it for what we need. As I just said, they have made it available to be used for non-commercial purposes when used to target Atari ST. That's not a license that allows it to be included in your libre OSes's main/libre packages, but it should be sufficent for our purposes here. And it's not hard to build. And not really hard to run. either, once you've got it built. 

    That is, I'll help you figure out how.

    First, nose areound the website. Read the small tutorial. Open up the HTML manual and read the General entry first where it talks about permission to use and about installation. Then go to the entries on the Assembler, the (Motorola) Mot Syntax Module, the TOS output module, and the m68k CPU module, in particular, and read those. Then read the build instructions.

    Then download the latest release source to someplace like "~asmwork/vasm/" and unpack and build it.

    In brief,

    • Go to the directory that unpacking the tarball created and do a directory listing. Note all the makefiles.
    • Run make without arguments:
           $ make
      Ignore the feedback.
    • Now run
           $ make CPU=m68k SYNTAX=mot
    • Do a listing
           $ ls -lart
      and check that vasmm68k_mot has been built.
    • Link it into your local user file system's binary directory.

    Given the example directory structure I've been mentioning, that last step can be done with something like 

     ln -s /home/me/asmwork/vasm/vasm/vasm68k_mot /home/me/prg/

    Now we want our sample program that accumulates a few sums. This time we'll have to actually put it in a file instead of editing it interactively in the simulator.

    Here's the code again, with stuff that's necessary for vasm and for the Atari ST target, and a little extra code inspired by the extra we did with the 6801:

    	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		; Just for good measure -- 68K needs code even aligned.
    EXAMPLE:
    	MOVEQ	#8,D0	; MOVEQ lets us ignore register width.
    	ADDQ	#5,D0	; Default register width is okay.
    	ADDQ	#2,D0	; D0 is comparable to 6800 A? Sort-of? Maybe?
    	ADDQ	#7,D0
    	ADDQ	#4,D0
    	NOP		; one landing pad
    RESTART:
    	MOVE.W	#2056,D0	; the extra example for 6801
    	ADD.W	#1285,D0
    	ADD.W	#514,D0
    	ADD.W	#1799,D0
    	ADD.W	#1028,D0
    	NOP		; two landing pad
    DRESTART:
    	MOVE.L	#134744072,D0	; 32-bit version
    	ADD.L	#84215045,D0
    	ADD.L	#33686018,D0
    	ADD.L	#117901063,D0
    	ADD.L	#67372036,D0
    	NOP			; three landing pad
    DONE:
    	NOP			; You must "quit" the debugger.
    	NOP			; Don't continue.
    
    	END	EXAMPLE
    

    In your host OS, copy that into an empty text editor document and save it in your working directory for the primer code as something like sum85274.s. Given the above structure, that would be  

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

    Drag the corner of your terminal shell window to widen it to mostly fill the screen before you continue. It'll make it easier to shift back to the terminal when you start debugging.

    Assemble the file you just saved with vasm using the vasmm68k_mot command with parameters as follows:

    $ vasmm68k_mot -Ftos -o SUM85274.PRG -L sum85274.lst sum85274.s
    

    You can open the assembly listing file, sum85274.lst, in a text editor, to look at the results. 

    If there are no errors, start Hatari there in the primer working directory:

    $ hatari
    

    When the desktop comes up, hit Ctrl-Z to bring up EmuCON. In EmuCON, change to the primer working directory, whatever you named it.

    Then hit Alt-PAUSE to enter the debugger.

    That's the alt key, with the PAUSE function key that is usually above the ten-key pad on a long keyboard, or at the top-right corner of the notebook PC keyboard. If you are working on a notebook computer, you probably need to hold down the Fn key while you hit Alt-PAUSE.

    Sometimes, my computer seems to miss the Alt key and just pauses. If that happens, you may have to hit Alt-PAUSE again to enter the debugger.

    When you enter the debugger, the emulated Atari screen may see some pixel folding. That's okay, ignore it. 

    Move the host OS cursor over to the terminal shell window where you started Hatari, and click that window to bring it forward:

     

    At this point, you're debugging the Atari ST operating system itself, or perhaps EmuCON.

    ----------------------------------------------------------------------
    You have entered debug mode. Type c to continue emulation, h for help.
    
    CPU=$e1d7e2, VBL=790, FrameCycles=128, HBL=0, LineCycles=128, DSP=N/A
    00e1d7e2 46c0                     move.w d0,sr
    >

    Let's try the help command, just to see what help it gives us:

    > h
    
    Generic commands:
               cd (  ) : change directory
         evaluate ( e) : evaluate an expression
             help ( h) : print help
          history (hi) : show last CPU/DSP PC values & executed instructions
             info ( i) : show machine/OS information
             lock (  ) : specify information to show on entering the debugger
          logfile ( f) : set (memdump/disasm/etc) log file
            parse ( p) : get debugger commands from file
           rename (  ) : rename given file
            reset (  ) : reset emulation
       screenshot (  ) : save screenshot to given file
           setopt ( o) : set Hatari command line and debugger options
        stateload (  ) : restore emulation state
        statesave (  ) : save emulation state
            trace ( t) : select Hatari tracing settings
        variables ( v) : List builtin symbols / variables
             quit ( q) : quit emulator
    
    CPU commands:
          address ( a) : set CPU PC address breakpoints
       breakpoint ( b) : set/remove/list conditional CPU breakpoints
           disasm ( d) : disassemble from PC, or given address
          profile (  ) : profile CPU code
           cpureg ( r) : dump register values or set register to value
          memdump ( m) : dump memory
         memwrite ( w) : write bytes/words/longs to memory
          loadbin ( l) : load a file into memory
          savebin (  ) : save memory to a file
          symbols (  ) : load CPU symbols & their addresses
             step ( s) : single-step CPU
             next ( n) : step CPU through subroutine calls / to given instruction type
             cont ( c) : continue emulation / CPU single-stepping
    
    DSP commands:
       dspaddress (da) : set DSP PC address breakpoints
         dspbreak (db) : set/remove/list conditional DSP breakpoints
        dspdisasm (dd) : disassemble DSP code
       dspmemdump (dm) : dump DSP memory
       dspsymbols (  ) : load DSP symbols & their addresses
       dspprofile (dp) : profile DSP code
           dspreg (dr) : read/write DSP registers
          dspstep (ds) : single-step DSP
          dspnext (dn) : step DSP through subroutine calls / to given instruction type
          dspcont (dc) : continue emulation / DSP single-stepping
    
    If value is prefixed with '$', it's a hexadecimal, if with '#', it's
    a normal decimal, if with '%', it's a binary decimal. Prefix can
    be skipped for numbers in the default number base (currently 10).
    
    Any expression given in quotes (within ""), will be evaluated
    before given to the debugger command.  Any register and symbol
    names in the expression are replaced by their values.
    
    Note that address ranges like '$fc0000-$fc0100' should have no
    spaces between the range numbers.
    
    'help <command>' gives more help.
    >

    You can try various commands, like disassembling code (d), dumping memory (m), and looking at registers (r). But don't try setting memory or registers just yet. 

    If you do, you'll want to use the quit (q) command and start Hatari up again fresh. 

    The problem you might be wondering about is how to get the target program into memory where you can debug it. I think it might be possible to use the debugger's load command (l), but, if so, I haven't figured that out yet.

    Instead, as described in the debugger manual entry on breakpoints and variables, set a breakpoint to stop emulation after a program is loaded and before it is run with this break command: 

    b pc = TEXT :once

    Here's what it looks like typed in, with the results it displays:

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

    What that means is that it set a one-time breakpoint for the next time the PC is equal to the beginning of the TEXT segment. CP/M programs are started by starting execution at the beginning of the program's TEXT segment, so this will do what we want, if we make sure that the next program we run is the target program.

    Use the continue command (c) to let Hatari continue running,

    > c
    Returning to emulation...

     and then bring the Hatari window back in front using the task bar or whatever. EmuCON should be showing you an active prompt there. 

    At the prompt, type the name of the executable file that you just used vasmm68k_mot to create. If you used the same names as I did, above, that would be 

    C:\primer>SUM85274.PRG

     with or without the .PRG extension, really.

    Now use the task bar or whatever to get back to the console where the debugger will be showing you all sorts of things about the breakpoint, showing you the first instruction in the TEXT segment disassembled, and prompting you again:

    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/SUM85274.PRG' symbol table...
    TOS executable, DRI / GST symbol table, reloc=0, program flags: PRIVATE (0x0)
    Program section sizes:
      text: 0xc, data: 0x0, bss: 0x0, symtab: 0xe
    Trying to load DRI symbol table at offset 0x28...
    Offsetting BSS/DATA symbols from TEXT section.
    Skipping duplicate address & symbol name checks when autoload is enabled.
    Loaded 1 symbols (1 TEXT) from '/home/nova/usr/share/hatari/C:/primer/SUM85274.PRG'.
    
    CPU=$13d10, VBL=20873, FrameCycles=241160, HBL=237, LineCycles=368, DSP=N/A
    00013d10 7008                     moveq #$08,d0
    >

    Notice that disassembly of the first instruction at the end of all that. We're ready to do a MOVEQ, ready to load D0 with the value 8.

    We can do a disassembly, and the debugger will just assume, since we haven't done a disassembly during this session yet, that we want to see what the current PC is pointing out:

    > d
    (PC)
    EXAMPLE:
    00013d10 7008                     moveq #$08,d0
    00013d12 5a40                     addq.w #$05,d0
    00013d14 5440                     addq.w #$02,d0
    00013d16 5e40                     addq.w #$07,d0
    00013d18 5840                     addq.w #$04,d0
    00013d1a 4e71                     nop 
    RESTART:
    00013d1c 303c 0808                move.w #$0808,d0
    00013d20 d07c 0505                add.w #$0505,d0
    00013d24 d07c 0202                add.w #$0202,d0
    00013d28 d07c 0707                add.w #$0707,d0
    00013d2c d07c 0404                add.w #$0404,d0
    00013d30 4e71                     nop 
    DRESTART:
    00013d32 203c 0808 0808           move.l #$08080808,d0
    00013d38 d0bc 0505 0505           add.l #$05050505,d0
    00013d3e d0bc 0202 0202           add.l #$02020202,d0
    00013d44 d0bc 0707 0707           add.l #$07070707,d0
    00013d4a d0bc 0404 0404           add.l #$04040404,d0
    00013d50 4e71                     nop 
    00013d52 4e71                     nop 
    >

    Let's do a second disassembly to see if it has shown us all of the code. This time, it will pick up at where it left off:

    > d
    00013d54 0000 0000                or.b #$00,d0
    00013d58 0000 0000                or.b #$00,d0
    00013d5c 0000 0000                or.b #$00,d0
    00013d60 0000 0000                or.b #$00,d0
    00013d64 0000 0000                or.b #$00,d0
    00013d68 0000 0000                or.b #$00,d0
    00013d6c 0000 0000                or.b #$00,d0
    00013d70 0000 0000                or.b #$00,d0
    00013d74 0000 0000                or.b #$00,d0
    00013d78 0000 0000                or.b #$00,d0
    00013d7c 0000 0000                or.b #$00,d0
    00013d80 0000 0000                or.b #$00,d0
    00013d84 0000 0000                or.b #$00,d0
    00013d88 0000 0000                or.b #$00,d0
    00013d8c 0000 0000                or.b #$00,d0
    00013d90 0000 0000                or.b #$00,d0
    00013d94 0000 0000                or.b #$00,d0
    00013d98 0000 0000                or.b #$00,d0
    00013d9c 0000 0000                or.b #$00,d0
    00013da0 0000 0000                or.b #$00,d0
    00013da4 0000 0000                or.b #$00,d0
    00013da8 0000 0000                or.b #$00,d0
    00013dac 0000 0000                or.b #$00,d0
    >

    Yep, when there's nothing in memory, there's usually a lot of zeroes or a lot of garbage. And zeroes just happen to be the op-code for a byte-sized OR of immediate 0 into D0.

    Try single stepping (s):

    > s   
    
    CPU=$13d12, VBL=32348, FrameCycles=35072, HBL=34, LineCycles=528, DSP=N/A
    00013d12 5a40                     addq.w #$05,d0
    >

    Unfortunately, it doesn't show you which register(s) changed. It does show you the CPU and the next op-code, disassembled, along with a lot of display controller stuff. We want to see the registers, so, let's use the register command (r):

    > r
      D0 00000008   D1 00000000   D2 00000000   D3 00000000 
      D4 00000000   D5 00000000   D6 00000000   D7 00000000 
      A0 00000000   A1 00000000   A2 00000000   A3 00000000 
      A4 00013D54   A5 00013D54   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 5a40 (ADD) 5440 (ADD) Chip latch 00000000
    00013d12 5a40                     addq.w #$05,d0
    Next PC: 00013d14
    >

    So, if you step (s) and then show (r) the registers, you can watch the numbers add up (in hexadecimal) in data register D0.

    > s
    
    CPU=$13d14, VBL=32348, FrameCycles=35076, HBL=34, LineCycles=532, DSP=N/A
    00013d14 5440                     addq.w #$02,d0
    > r
      D0 0000000D   D1 00000000   D2 00000000   D3 00000000 
      D4 00000000   D5 00000000   D6 00000000   D7 00000000 
      A0 00000000   A1 00000000   A2 00000000   A3 00000000 
      A4 00013D54   A5 00013D54   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 5440 (ADD) 5e40 (ADD) Chip latch 00000000
    00013d14 5440                     addq.w #$02,d0
    Next PC: 00013d16
    > s
    
    CPU=$13d16, VBL=32348, FrameCycles=35080, HBL=34, LineCycles=536, DSP=N/A
    00013d16 5e40                     addq.w #$07,d0
    > r
      D0 0000000F   D1 00000000   D2 00000000   D3 00000000 
      D4 00000000   D5 00000000   D6 00000000   D7 00000000 
      A0 00000000   A1 00000000   A2 00000000   A3 00000000 
      A4 00013D54   A5 00013D54   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 5e40 (ADD) 5840 (ADD) Chip latch 00000000
    00013d16 5e40                     addq.w #$07,d0
    Next PC: 00013d18
    > s
    
    CPU=$13d18, VBL=32348, FrameCycles=35084, HBL=34, LineCycles=540, DSP=N/A
    00013d18 5840                     addq.w #$04,d0
    > r
      D0 00000016   D1 00000000   D2 00000000   D3 00000000 
      D4 00000000   D5 00000000   D6 00000000   D7 00000000 
      A0 00000000   A1 00000000   A2 00000000   A3 00000000 
      A4 00013D54   A5 00013D54   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 5840 (ADD) 4e71 (NOP) Chip latch 00000000
    00013d18 5840                     addq.w #$04,d0
    Next PC: 00013d1a
    > s
    
    CPU=$13d1a, VBL=32348, FrameCycles=35088, HBL=34, LineCycles=544, DSP=N/A
    00013d1a 4e71                     nop 
    > r
      D0 0000001A   D1 00000000   D2 00000000   D3 00000000 
      D4 00000000   D5 00000000   D6 00000000   D7 00000000 
      A0 00000000   A1 00000000   A2 00000000   A3 00000000 
      A4 00013D54   A5 00013D54   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) 303c (MOVE) Chip latch 00000000
    00013d1a 4e71                     nop 
    Next PC: 00013d1c
    >

    Exciting, isn't it?

     Just don't step past the end of code!!!!!!!

    Heh. Actually, it's not a problem, as long as you don't step too far. All those 
       OR #0,D0 

    instructions don't really do much. They just really aren't very interesting.

    But once you've stepped through the end of the code, then what? 

    If you try to continue executing code that isn't there, something bad will eventually happen, right?

    Sort, of. You'll get a segment error or a bus error or some such, and EmuTOS will try to catch you and send you back to the EmuCON window or something. But we can't guarantee that Hatari is in a stable state. (It's almost guaranteed for this one, but not quite.) So, instead, once you've stepped through all the code and convinced yourself it's doing what it's doing, just use the quit command (q) again and quit the Hatari session.

    We'll look at how to get the program to safely return to the OS later, among other things

    And that should get us started for working on the 68000.

    Amiga emulators would also be interesting, but the Amiga run-time has some complexities that I don't want to deal with. Likewise the classic Macintosh systems. 

    I'm not saying don't try them. Do, if you are inclined. They're fun, but distracting. MAME, too. MAME is really distracting.

    I really wish I had time to write an open-source emulator for the MEX68KECB single board.  That would be the least distracting for this level of code.

    Just for the record, MDOS for the EXORciser is approximately what TOS (and EmuTOS) for the Atari is. If the debugger for Hatari were able to assemble code interactively, we would not need, at this level, to be saving code in files and so forth on the Hatari. 

    But we will get there before we are done. Don't worry.

    I think we can get Hatari to do the job we need.

    Next, I think I want to show you one way to separate the numbers we're adding in this example from the code itself.


    (Title Page/Index)