Showing posts with label lists. Show all posts
Showing posts with label lists. Show all posts

Tuesday, October 29, 2024

Teaching Myself Python, part 1

Right in the middle of my assembly language tutorial, I decided to teach myself Python.

(A friend needed some help with his classwork.)

I'm running the interactive interpreter, and this is a partial record of the conversation.

account@computer:~$ python
Python 2.7.17 (default, Sep 30 2024, 12:35:16) 
[GCC 7.5.0] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>>

That's pretty much not unexpected. 

What did I just do? 

I invoked Python with just 

python

at the bash shell command-line $ prompt. And Python tells me what version I'm running and some information about how it was built, followed by a prompt about things people might be particularly interested in. 

And you see I'm running a slightly old python -- version 2.7. (I actually have python v. 3 running, as well, but python v. 2 is my default right now.)

Where did I get Python?

Python gets installed by default now on a lot of Posix OSses. It's available in pretty much every distribution's package manager. I'm running an older version of Ubuntu Linux. (I need to upgrade, trying to decide between going back to Debian or over to Devuan, or staying with Ubuntu, or heading back to one of the BSDs.) 

I think v. 2 was installed by default, and I installed v. 3 from the packages. Or maybe it was the other way around, or maybe I installed both from packages. I don't remember.

If you're running MacOS, you can get Python from python.org or through one of several 3rd party package managers. (Macs actually come with Python, and it should be good enough for what I'm doing here. But if you decide to use Python regularly, it's recommended that you install a separate version so that you don't disturb the system's version when you install Python stuff that Mac OS doesn't use.)

If you're running MSWindows, you can apparently get Python from the Microsoft Store as well as from python.org. Or you can install Cygwin and get Python from the Cygwin packages.

And Python is available from python.org and others in the Android and Apple iOS stores, from what I hear.

Anyway, Python has been installed, and I have been putting off actually learning it, for quite a while. 

And there are two fundamental ways of running Python --

  1. Run a program written in Python (whether by clicking an icon or by invoking it at the command line), and
  2. run the interactive Python interpreter itself (whether by clicking an icon or invoking it at the command line)

I'm doing the latter, invoking it at the command line. (See above.)

What I type once Python is running is on the lines that start

>>>

Let's see what happens when I type "help", like the prompt suggests:

>>> help
Type help() for interactive help, or help(object) for help about object.
>>> 

Huh? 

Hmm. (Head scratching ensues.) 

Maybe, type "help()"?

>>> help()

Welcome to Python 2.7!  This is the online help utility.

If this is your first time using Python, you should definitely check out
the tutorial on the Internet at http://docs.python.org/2.7/tutorial/.

Enter the name of any module, keyword, or topic to get help on writing
Python programs and using Python modules.  To quit this help utility and
return to the interpreter, just type "quit".

To get a list of available modules, keywords, or topics, type "modules",
"keywords", or "topics".  Each module also comes with a one-line summary
of what it does; to list the modules whose summaries contain a given word
such as "spam", type "modules spam".

help>

And now it seems to be prompting me with "help> instead of ">>>".

Okay, "spam" sounds interesting.

help> spam
no Python documentation found for 'spam'

help>

Oh. I guess I need to type "modules spam" for that.

help> modules spam

Here is a list of matching modules.  Enter any module name to get more help.

Gtk-Message: 07:40:20.836: Failed to load module "canberra-gtk-module"

help>

I guess that means that something is not installed. I don't feel like chasing that down right now, so let's forget about the module "spam".

Maybe I could read up on arrays in Python.

help> modules array

Here is a list of matching modules.  Enter any module name to get more help.

array - This module defines an object type which can efficiently represent
numpy.core._methods - Array methods which are called by both the C-code for the method
numpy.core.arrayprint - Array printing function
numpy.core.defchararray - This module contains a set of functions for vectorized string
numpy.core.info - Defines a multi-dimensional array and useful procedures for Numerical computation.
numpy.core.multiarray 
numpy.core.multiarray_tests 
numpy.core.records - Record Arrays
numpy.lib.arraypad - The arraypad module contains a group of functions to pad values onto the edges
numpy.lib.arraysetops - Set operations for arrays based on sorting.
numpy.lib.arrayterator - A buffered iterator for big arrays.
numpy.lib.format - Define a simple format for saving numpy arrays to disk with the full
numpy.lib.mixins - Mixin classes for custom array types that don't inherit from ndarray.
numpy.lib.recfunctions - Collection of utilities to manipulate structured arrays.
numpy.lib.twodim_base - Basic functions for manipulating 2d arrays
numpy.lib.ufunclike - Module of functions that are like ufuncs in acting on arrays and optionally
numpy.lib.user_array - Standard container-class for easy multiple-inheritance.
numpy.ma.extras - Masked arrays add-ons.
numpy.ma.testutils - Miscellaneous functions for testing masked arrays and subclasses

help> 

Well, maybe I'll chase down the arrays module later and just get back to trying out Python. It said I could type "quit" to go back:

help> quit

You are now leaving help and returning to the Python interpreter.
If you want to ask for help on a particular object directly from the
interpreter, you can type "help(object)".  Executing "help('string')"
has the same effect as typing a particular string at the help> prompt.
>>> 

Well, that's useful information about how to use the help function.

Let's see if I can get Python to say "hello".

>>> hello
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'hello' is not defined
>>> 

Not that way. Maybe tell it to print it out?

>>> print hello
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'hello' is not defined
>>>

that doesn't seem to be the way to do it, either. I guess it needs to be quoted:

>>> print "hello"
hello
>>> 

That seems to work. What next?

>>> name = "Joel"
>>> print "hello ", name
hello  Joel
>>> 

Let's see if I can stroke my ego some more:

>>> rank = 1
>>> print "hello", name, "you're number ", rank, "!"
hello Joel you're number  1 !
>>> 

Cheap thrills. Heh.

One more vanity one-liner, but it requires putting either the program or the name in a file. We'll put the program in a file. 

To do this, you open up a text editor. Some people like geany or kate. I generally use gedit or vim. All four and others are available in most Posix OSses' package managers, and can be downloaded for Mac OS or installed via 3rd party package managers. 

Of course, Mac OS has Xcode (vim, too!), and I have found Xcode quite useful on Mac, useful enough that I usually have not installed gedit. (Make sure you download the command-line tools when you download Xcode.)

And, if you must use MSWindows, all four can be installed from Cygwin packages if you install Cygwin. (Geany and Gedit are supported for MSWindows, and can be downloaded from their respective download sites, but Kate takes a bit more work if you don't get it through Cygwin.) Or you can use Microsoft's Visual Studio. (Bleaugh. Just kidding. Sort-of.)

Anyway, open up a text editor and type in the following one-liner and save it in a directory called something like "play/python" as "greet.py". You'll need to change directory in your command-line shell, as well. Oh. Yeah. You'll need to use a command-line shell for this one. I don't think it can be done easily when you start Python by clicking an icon.

Actually, this one-liner is a two-liner:

import sys
print "Hello", sys.argv[ 1 ]

As I say, save it as "greet.py". Then go to your command-line shell and type the command "python greet.py Joel:

account@computer:~/play/python$ python greet.py Joel
Hello Joel
account@computer:~/play/python$ 

By way of explanation --

(1) You can save your program in a file with a ".py" extension (ending on the name) and have python run the program for you. Interactive is fun, but there are some things that don't work well interactively (can't work well interactively).

(2) Python makes whatever you type after the name of the program file available to your program in a list (I think it's a list.) called "argv", in the system module called "sys".

If you're not used to using these command-line parameters, that may not be much explanation, but once you start using them, they're not hard to understand.

Well, let's see what else might be interesting. Can I make the thing count?

>>> mylist = [ 1, 2, 3, 4 ]
>>> print mylist, "I just plain adore ..."
[1, 2, 3, 4] I just plain adore ...
>>> 

Rim shot. Let's try an explicit loop:

>>> for i in range( 1, 4 )
  File "<stdin>", line 1
    for i in range( 1, 4 )
                         ^
SyntaxError: invalid syntax
>>> 

Ooooh, rejected!

Needs a colon on the end. Try again:

>>> for i in range( 1, 4 ):
...   print i
... 
1
2
3
>>> 

The "..." ellipsis prompt means it wants you to type more. So I typed the body of the loop.

And it's still not quite there. The last number in the range is not included.

Point one, for people who like their code blocks to have beginning braces and end braces or BEGIN and END keywords, Python uses indentation to demarcate blocks.

Yeah. The whitespace. The stuff you've trained yourself to not see. Or at least I have.

That's one of the reasons I got miffed at Python in the past. But I have a friend this time, so I'll just go with it.

I do see that the colon seems to be a sort of BEGIN marker. And after some playing around late last night, I figured out that, even though python doesn't require it, you can put comment characters in place to show where the indentation hits. It's kind of like painting the fingers of your invisible robot hands so you can see what they're doing, but it seems to sort of do the job.

My friend needs help with working through a list, summing up a column, and taking an average. So let's look at two ways to sum a list in Python. This is a kind of a sudden jump, but we've seen most of the essential elements of the language that I'm using, so, hang on to your hat, and I'll hang on to mine:

Grabbing input from the keyboard can get confusing, so we're gong to save this as a program, call it "totaloop.py":

total = 0
count = 0
for i in range( 1, 10):
  numstr = input()
  previous = total
  total = total + numstr
  count = count + 1
  print count, ": ", previous, " + ", numstr, " = ", total ;
  # end of loop

average = float( total ) / count
print "average of ", total, " / ", count, " is ", average

What this does is

  • read a number from the keyboard,
  • add it immediately to the total, and
  • print out the number, count, and running total
  • until 9 numbers have been read; 
  • at which point, the average is calculated and printed out.

Make sure you save it with the empty trailing line. Call it from the command line, like this:

$ python totaloop.py

And then type in a bunch of numbers

$ python totaloop.py
2
1 :  0  +  2  =  2
6
2 :  2  +  6  =  8
3
3 :  8  +  3  =  11
2
4 :  11  +  2  =  13
3
5 :  13  +  3  =  16
6
6 :  16  +  6  =  22
4
7 :  22  +  4  =  26
9
8 :  26  +  9  =  35
3
9 :  35  +  3  =  38
average of  38  /  9  is  4.22222222222

You can see that I typed in the numbers 2 6 3 2 3 6 4 9 3.

Now, instead of immediately calculating the running total, we'll input the entire list first, and then use a built-in function to sum up the whole list:

numbers = []
for i in range( 1, 10 ):
  numstr = input()
  numbers.append( float( numstr ) )
  print i, ": ", numstr, numbers 
  # End of loop

total = sum( numbers )
average = total / len( numbers )

print "list: ", numbers
print "sum is ", total, " and average is ", average

Re-emphasizing, the program 

  • reads in a number,
  • appends it to the list,
  • prints the number read and the list
  • until 9 numbers are read,
  • and then, after the loop is finished, sums them all up and
  • prints the results 

Call it from the command line and type in the list of numbers typed in before just so we can compare:

$ python totalist.py
2
1 :  2 [2.0]
6
2 :  6 [2.0, 6.0]
3
3 :  3 [2.0, 6.0, 3.0]
2
4 :  2 [2.0, 6.0, 3.0, 2.0]
3
5 :  3 [2.0, 6.0, 3.0, 2.0, 3.0]
6
6 :  6 [2.0, 6.0, 3.0, 2.0, 3.0, 6.0]
4
7 :  4 [2.0, 6.0, 3.0, 2.0, 3.0, 6.0, 4.0]
9
8 :  9 [2.0, 6.0, 3.0, 2.0, 3.0, 6.0, 4.0, 9.0]
3
9 :  3 [2.0, 6.0, 3.0, 2.0, 3.0, 6.0, 4.0, 9.0, 3.0]
list:  [2.0, 6.0, 3.0, 2.0, 3.0, 6.0, 4.0, 9.0, 3.0]
sum is  38.0  and average is  4.22222222222

The first way uses less memory.

The second way is what you use if you need to do more than one thing with the list of numbers.

Let's say you want to remember what the 4th number entered was and print it out at the end. 

You could add code to the first one like this:

total = 0
count = 0
memo = 0
for i in range( 1, 10):
  numstr = input()
  previous = total
  total = total + numstr
  count = count + 1
  if count == 4:
    memo = numstr
    # end of conditional
  print count, ": ", previous, " + ", numstr, " = ", total ;
  # end of loop

average = float( total ) / count
print "average of ", total, " / ", count, " is ", average
print "4th number:", memo

Essentially, watch the count, and when it hits 4 put the input number in the memo variable.

Note that Python wants an empty line at the same indentation at the end of the loop and at the end of the conditional if. I'm marking the empty line and the indentation with a comment. (Comments have no code, and can serve as empty lines.)

If you fail to indent the trailing empty line, it terminates everything (and makes a fuss about syntax).

Here's a way we can do it if we keep the whole list in memory:

numbers = []
for i in range( 1, 10 ):
  numstr = input()
  numbers.append( float( numstr ) )
  print i, ": ", numstr, numbers 
  # End of loop

total = sum( numbers )
average = total / len( numbers )

print "list: ", numbers
print "sum is ", total, " and average is ", average
# first is numbers[ 0 ], 4th is numbers[ 3 ]
print "4th number:", numbers[ 3 ]

Since we still have the whole list in memory, we can just index the 4th element. Remember, lists start with index 0, so the first one is numbers[0]. Therefore, the 4th one is at index 3, numbers[3].

Hopefully, this will be enough for my friend to work it through.

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)