Saturday, June 15, 2024

ALPP XX-XX -- Getting Simulators for the M6800, 6801, 6809, and 68000

This chapter also got scratched. Just didn't work right. But I want to keep it around for my own reference. 

Getting Simulators for the
6800, 6801, 6809, and 68000

(Title Page/Index)

 

Okay, so we've introduced assembly language by talking a little about adding small integers. But if we want to understand what's going on, we really need some way to watch what the processor does when it executes the code.

If I had more time and money, I would design my own hardware and build my own simulator software, and offer the hardware for sale and put the software up for download and all that, and it would all be real easy to use. (Of COURSE!)

Fortunately, there are useful simulators (software-based emulators) already available, as I have mentioned.

What I will use for 6800 and 6809 code, especially in the first part of this, will be Joe. H. Allen's EXORciser simulator, EXORsim. For the 6801, it will be my hack on EXORsim, exorsim6801

For the 68000, I will be using the Atari ST (and beyond) simulator, Hatari.

I am choosing these because they seem to me to be the most generally available and accessible.

There are other options, such as XRoar for the 6809 and 6801, which simulates the Dragon, the Tandy Color Computers 1, 2, and 3, and the Radio Shack MC-10, and the original Matra Alice 4K. If Ciaran doesn't beat me to it, I may try (again) to add hosted debugging to XRoar, but you can always run a self-hosted debugger (such as Disk EDTASM) on the emulated computer. 

Now, Ciaran and his friends do provide an on-line emulator, and you can download Disk EDTASM and run it on the on-line simulator, but I don't want to be encouraging lots of people to put a heavy load on Ciaran's server. (I'll explain getting XRoar and building it locally on your own hardware later. It's a pretty steep start-up for a topic that already has a steep learning curve.)

Similarly, VCC for MSWindows requires compiling (if I read the website right), and only emulates the Color Computer 3 (6809). I think I have heard that it supports direct debugging fairly well, but cannot vouch for that. It might be an option if you're running MSWindows.

You may find you need to build Hatari, but, if you are running a Linux OS, there are probably packages for it in your OS's repository. So you aren't necessarily faced with gathering all the dependent libraries yourself and all that. 

For Mac OS, you can get it pre-built from the project website, if you haven't yet got a package manager running on your Mac. 

For MSWindows, there is a 3rd party project, that the Hatari project links to, providing Hatari pre-built -- if you really have to use MSWindows.

There are other options for the 68000, as well, but Hatari seems to be the easiest to get, and to get started working on. And it seems to have the least in the way of advanced OS capabilities to get in the way of introductory-level practice. 

(I really should figure out Musashi, but I want to get this tutorial written. That will come later. Also, Amiga under emulation is cool, but you want to know what you're doing before you dig into writing code on Amiga. Likewise classic Macintosh.)

MAME, of course, is an option. It does everything, and supports direct debugging pretty well (I hear), but it's huge, and a bit daunting to get started with.

But!   (OS and Compiling)

For any of these, you'll need something to run the simulators on, both hardware (PC/workstation) and software (support in the operating system). And you'll need compilers to build anything that needs to be built.

Compilers are definitely one thing you'll need anyway, as we go along. There are more tools we want to use, and you'll need to compile some of them.

If you can run a Linux OS, or Mac OS, or freeBSD or netBSD, getting the compiler and other tools is really straightforward. They can usually just be selected during the install process, or installed later through some sort of package manager.

If you absolutely must run MSWIndows --

Cygwin

Microsoft's compiler toolchain (VS) has one not-small problem. Pretty much anything you try to do with it will try to drag you off into Microsoft solutions, and if Microsoft solutions were sufficient, you wouldn't be reading this blog. 

If you must use MSWindows, the first tool you need to download is Cygwin. (LSW with a full Linux OS install might work, but I've never tried it. And LSW is Microsoft. I have tried Cygwin.)

Make sure you're accessing the real https://cygwin.com/ site, and check the checksum after you download it. 

For MSWindows 10, the command-line command for checking checksums was 

certutil -hashfile <TYPE>

search the web for how to specify the type of checksum, etc. 

Once Cygwin is installed, open up the installer again and get either the gnu C compiler or the Clang compiler. Either should work. And you'll need the ANSI and Posix libraries. While you're at it, get bc, too. (I think it's in the repositories.)

Also, if you don't already have a favorite text editor, get either gedit or kate, or maybe Geany. Vim or EMacs will also work, if you already know them.

At which point I'm going to repeat myself. 

Ubuntu or Debian or Devuan or Mint OS, ... 

There are lots of Linux OSses to choose from, they are mostly easy to install on older hardware you might have lying around -- or on inexpensive ARM hardware like Raspberry Pi, and they all have great compiler toolchains and other tools and repositories that are easy to install from. You can also dual boot, but, really, if you don't have a ten-year old PC in a closet somewhere that you're no longer using, a Pi or something similar is pretty inexpensive.

FreeBSD or NetBSD should work pretty well, too. OpenBSD, on the other hand, is so security oriented that I'm not sure compiling EXORsim will be very easy.

All of these have install instructions on their websites, and are easy to install. (Almost too easy. Do be careful to save any important data before wiping an old computer clean.) 

Make sure you choose a minimal developer's install, with either the gnu or clang compilers, and the basic set of libraries. And, as I mentioned for those using Cygwin, install a programmer's text editor, as well -- kate, gedit, geany, vim, emacs, or whatever.

Or if you're inclined toward Mac

Doing this under Mac OS will also be pretty straightforward, and you don't have to worry about installing the OS. You will need to install the BSD developer tools subsystem  (cough), well, I mean, XCode, which has the compiler toolchain. But that's straightforward, too. All the libraries and bc should just come with XCode. And XCode has its own integrated editor, so you don't need to choose a text editor. (But there are lots of great 3rd party text editors available, too.)

Yeah, that's a lot of stuff, just to get started, isn't it? But it's tools that you need anyway, if you are interested in assembly level programming and/or retro computing.

Once you have your system and  toolchain ready to work with, 

  1. Start with downloading and compiling EXORsim6801. 
  2. Then download and compile the most recent EXORsim. 
  3. And then install Hatari.

Well, you can install Hatari first, but please compile EXORsim6801 before compiling EXORsim, and I should explain why.

EXORsim6801




(Title Page/Index)

 

Thursday, March 28, 2024

ALPP 01-07 -- Working in Binary, Hexadecimal, Etc.: bc, gforth, and Other Options

Working in Binary, Hexdecimal, Etc.:
bc, gforth, and Other Options

(Title Page/Index)

We've started dealing with such things as global constants in memory, and now we find ourselves unable to avoid working with binary numbers and their shorter forms, octal or hexadecimal. For addresses and large numbers, decimal is not nearly as efficient as binary, and almost all modern CPUs (meaning since the 1960s) use binary -- base two.

(There is a lot of stuff I'm sweeping under the rug here. We generally prefer not to go deep diving in unfamiliar waters.)

All the CPUs we have been talking about so far, and all the ones I am (at this time) considering demonstrating here do their math (and other operations) in binary. 

We western earthling humans of the current common cultures do not tend to find it comfortable to operate in binary. Therefore, it would be good to have tools to help us.

Binary may be efficient, but it is very much not a compact way of writing numbers on paper or in blogs. For example, one million in decimal base (base ten) is one followed by six zeroes:

1000000ten

But in binary base, it is

11110100001001000000two

Twenty, count them, twenty digits. 

In octal base (base eight), it is a more compact seven digits, not quite as compact as in decimal:

3641100eight

And in hexadecimal base (base sixteen), it is even more compact than in decimal:

F4240sixteen

-- only five digits.

Converting from binary to octal is rather straightforward. First write the binary number in groups of three digits. Start from the right and go left. The leftmost group need not be a full group of three:

11 110 100 001 001 000 000two

 Then convert each of the groups to octal according to the following table:

binary
octal
binary
octal
000 == 0 *
100 == 4
001 == 1 *
101 == 5
010 == 2 *
110 == 6
011 == 3 *
111 == 7

11 => 3,  110 ~> 6, 100 => 4, 001 => 1, 001 >> 1, 000 => 0, 000 => 0
=> 3641100
And converting from octal to binary just uses the above table in the opposite direction.

Converting between binary and hexadecimal is just as straightforward, grouping the binary digits in groups of four instead of three. Again, the leftmost group does not have to be full four (although it is for one million):

1111 0100 0010 0100 0000two

Convert by groups according to the following table:

binary
hex
binary
hex
0000 == 0 *
1000 == 8
0001 == 1 *
1001 == 9
0010 == 2 *
1010 == A
0011 == 3 *
1011 == B
0100 == 4 *
1100 == C
0101 == 5 *
1101 == D
0110 == 6 *
1110 == E
0111 == 7 *
1111 == F

1111=>F, 0100=>4, 0010=>2, 0100=>4, 0000=>0
=> F4240

I know you're hoping for a simple method like this for converting between decimal and binary, but, unfortunately, converting between base ten and base two is not as easy. There is a reason (ten is not an even power of two), but let's not go too far wide in our detour. Not here. It may become more clear when we practice conversion routines, a little down the road.

But it's not exactly hard, if you're okay with doubles and halves in decimal, keep track of evens and odds, and can work from left-to-right and from right-to-left, and keep at least one thing in memory.

Let's try converting a million from decimal to binary.

First, write down a million in decimal, a one followed by six zeroes:

1000000

Is it even or odd? It's even, so the rightmost binary digit is zero:

0

Half of a million is five hundred thousand:

500000 

That's even, so the next binary digit is 0:

00

Half of five hundred thousand is two hundred fifty thousand:

250000

Hmm. Let's make this a bit more followable:

step decimal odd? half remainder binary
0 1000000 even 500000 r 0 0
1 500000 even 250000 r 0 00
2 250000 even 125000 r 0 000
3 125000 even 62500 r 0 0000
4 62500 even 31250 r 0 00000
5 31250 even 15625 r 0 000000
6 15625 odd 7812 r 1 1000000
7 7812 even 3906 r 0 01000000
8 3906 even 1953 r 0 001000000
9 1953 odd 976 r 1 1001000000
10 976 even 488 r 0 01001000000
11 488 even 244 r 0 001001000000
12 244 even 122 r 0 0001001000000
13 122 even 61 r 0 00001001000000
14 61 odd 30 r 1 100001001000000
15 30 even 15 r 0 0100001001000000
16 15 odd 7 r 1 10100001001000000
17 7 odd 3 r 1 110100001001000000
18 3 odd 1 r 1 1110100001001000000
19 1 odd 0 r 1 11110100001001000000
20 0 even 0 r 0 011110100001001000000

Okay, that's a bit tedious, but it is straightforward. And the reverse is also straightforward and a bit tedious. Not hard, but tedious, and easy to get distracted and make mistakes. (I cheated and used a bit of Forth to generate the above conversion.)

Since you probably don't find it easy to do the conversions in your head, nor easy to do math in your head in non-decimal base, let's look around for tools to do it for us. (I don't find it all that easy either.)

As I mentioned already, your personal workstation, whether it runs a Microsoft OS or a Linux OS or a BSD OS or an Apple OS, or something else, probably comes with a fancy calculator installed. That calculator probably has a programmer mode, and the programmer mode will (usually) have the option to work in binary, octal, and/or hexadecimal, in addition to decimal.

On my current Linux OS desktop (Gnome/Ubuntu from some time back), there is such a calculator, and there is a mode button in the center of the menu/tool bar at top that says "Basic" or something in English. (「基本」 == "kihon" in Japanese. Sorry, but I work in Japanese, my screenshots will be Japanese.) 


If you click on 基本 there and select "Programming" or whatever it is (「プログラミング」) it gives a set of programmer-oriented functions:   

And then you can select the base. Below, I have selected base 16 (「16 進数」 == "jū-roku shinsū"):

And this calculator does me the convenience of showing me the number I'm entering in binary in that big patch in the center. It also shows whichever of octal, decimal, and hexadecimal I am not entering the number in, over to the right, just below the entry area. Below, I have just typed in one million in decimal, but haven't yet hit the enter key: 

I had kind of hoped I could drag-select and copy those displayed conversions, but this version isn't that nice. On the other hand, once I hit the enter key, and the entered number is bolded,


I can select a new base, and it will convert it for me:

And I can select and copy that number out of the entry window. So it's actually pretty nice after all.

Most modern desktop calculator applications in desktop workstations do something similar for you. You should be able to figure out how after playing around with them for a bit.

So, this is one way to get some help working with hexadecimal and binary, so we don't get too lost in the math.

But there are other options, including programming languages such as Forth and bc. (The interactive modes of Ruby, Python, Pearl, Julia, and many other languages are also useful, but I find Forth and bc particularly helpful to me.)

We'll discuss Forth more a bit down the road, and I don't want to confuse things with post-fix mathematical expressions and such just now, so I'll focus on bc.

bc

The Unix "basic calculator", bc, is a part of the usual set of tools for the programmer that works in *nix environments. It's been around since about the time the first Unix systems were called "Unix". It's almost always present on any OS derived from or developed to be compatible with Unix, including the various Berkeley Systems Distributions, Mac OS X and beyond, Linux OSses, Minix, and so forth. 

The reason is that system maintenance scripts sometimes need to work with very large numbers, and that's something bc does fairly well and predictably.

It was not, the last time I checked, among the tools available in your standard distribution of Microsoft Windows, but you can get it for MSWindows with Cygwin, as I have mentioned already.

I'm not sure if it is in Microsoft's WSL. I don't go there. It will be in Microsoft's Linux-OS-on-Windows distributions, I'm sure, if you go that direction, since those just basically use the native distribution's repositories. I still recommend Cygwin.

Instructions for installing Cygwin are on the Cygwin site. I may sometime write up a bit more on the ins and outs of that, but I'm focusing on assembly language right now. I strongly recommend checking both the signature and the hash before installing, both of which are noted on the instructions page.

Back to bc.

There are two major versions of bc, one from the GNU tools and the other from the BSD tools. Aside from the way they start up, for what we will be using them for here, they are pretty similar. (OpenBSD's bc is a bit more minimalist than the rest, but I'm assuming if you use OpenBSD for this project, you'll understand why and what to do about it.)

Invoking bc from the command line is fairly straightforward. Just type "bc" and hit enter. Except that doesn't give you some things I find convenient, so type the "-l" ("--mathlib") option, as well, and bring in some minimal library functions:

$ bc -l
bc 1.07.1
Copyright 1991-1994, 1997, 1998, 2000, 2004, 2006, 2008, 2012-2017 Free Software Foundation, Inc.
This is free software with ABSOLUTELY NO WARRANTY.
For details type `warranty'.

The BSD versions are much more quiet on startup. If you don't need the reminders, the GNU versions will be quiet if you give them the "--quiet" or "-q" option.

To quit bc, you can type the "quit" command. (Or you can hit the Ctrl-D key combination at the beginning of the line, in *nix. I think that will be Ctrl-Z in MSWindows. End Of File marker.)

I'm not going to give a full introduction to bc here. If you want to learn more, open a fresh command-line shell terminal window and type "man bc" to get a look at the on-line manual page. Or look it up on your favorite web search engine, using, say,

man page bc ubuntu

for your search terms if your OS is Ubuntu.

So, you have started bc in a terminal shell and are wondering what to do next.

For starters, just think of it as a calculator. 

The addition operator is what you would expect, "+". Type "8 + 8" and hit the enter key and it will show you "16":

8 + 8
16

Subtraction is also no surprise, it's the usual hyphen key:

128 - 32
96

Likewise division is the usual slash:

44859318 / 57734
777.00000000000000000000

Multiplication may be a surprise if you haven't done much with computers, but most likely you've already seen the asterisk as the substitute for the tilted cross "×", as the multiply operator commonly used today on computers:

4294967295 * 3415927 
14671294747107465

Finally, for integer powers only, there is the caret as exponent:

2 ^ 128
340282366920938463463374607431768211456

You can save results in variables:

m = 2^20

and then use them the same as numbers:

32 * m
33554432

Note that when you make an assignment, bc does not print the result. When you don't, it does.

bc has some internal variables. One is scale, which determines how many decimal places of accuracy to maintain to the right of the decimal point:

scale=102
4*a(1)
3.141592653589793238462643383279502884197169399375105820974944592307\
816406286208998628034825342117067980

Heh. Talk about cheating at calculating π. π here is accurate up to the last two digits, though. 

Oh, and when it has to use more than a line for a result, it shows that by putting a backslash at the end of the incomplete line to escape the end-of-line character.

And, uh, yea, the a() library function is the arctangent. (See the man page.)

Oh, and yeah, that, too, bc operates internally in base ten. LoL. Less surprise that way.

Another internal variable is obase, which determines the base it converts output to:

obase=2
2^32
100000000000000000000000000000000
obase=16
2^64
10000000000000000
2^64-1
FFFFFFFFFFFFFFFF

You've been wondering when I would get back to binary and hexadecimal, haven't you?

Input base conversion is a separate variable, ibase. That's why we could still type it in as

obase = 16 

up there. Once we start playing with ibase, we need to be more careful, and write it as something like

obase = 8 + 8

instead.  

bc allows  C-style comments inside /* ... */ pairs.

obase = 5 + 5 /* Just in case. */
ibase = 8
777  /* Enter at the end of this line produces output. */
511
ibase = 8 + 8 /* Can we do this? Yes, we can. */
F4240 /* Enter at the end of this line produces output. */
1000000

Since we're here, let's check the math we've been having the CPU do:

ibase = obase = 8 + 8
8 + 5 D 8 + 5 + 2 F 8 + 5 + 2 + 7 16 8 + 5 + 2 + 7 + 4 1A

Yep. Checks out.

bc is friendly enough to interpret single digits of 2 to 9 correctly no matter what ibase is, which can be helpful, or can be confusing. But hexadecimal A through F must be upper-case (capital). Lower-case letters are assumed to be variable names. 

Now we have two ways to work with hexadecimal and binary numbers.

Try something amusing?

obase = ibase = 5 + 5
obase = ibase = 36
FRIENDS + ENEMIES
 30 14 33 01 05 28 20

Okay, didn't work, did it? At least, not the way we thought it would. When obase goes over sixteen, the output is written in groups of decimal numbers instead of using the alphabet after F.

Hmm.

Forth (gforth)

I said I wasn't going to distract you with post-fix math. I lied. :)

If you took my hints and installed gforth, you can follow along. You can quit out of bc and use the same terminal shell you were running bc in:

$ gforth
Gforth 0.7.3, Copyright (C) 1995-2008 Free Software Foundation, Inc.
Gforth comes with ABSOLUTELY NO WARRANTY; for details type `license'
Type `bye' to exit

In post-fix, the numbers come first, then the operators.

5 5 + .

The plus is addition, as you suspected. It adds two numbers on the stack and replaces them with their sum. The period (dot) is an operator that takes the top number on the stack and converts and outputs it in the current input/output numeric conversion base

Hit enter after the dot, and it prints the result, followed by "ok":

5 5 + . 10  ok

You can set a variable with the ! operator. And you can enclose comments in parentheses.

4 4 * base ! ( Now hexadecimal base. )  ok
1000 F34 + . 1F34 ok

In some ways, not having input base and output base separate is easier than bc. In others, it's messier. But just remember that 

10 base !

is always redundant, and doesn't change anything.

5 5 + base ! ( Back to decimal. )  ok
36 base ! ( Now what? )  ok
FRIENDS ENEMIES + . UEX15SK  ok

gforth does not do arbitrary precision unless you program it to do so, but, on a 64-bit processor, it can handle a little more than 12 digits of base 36 in integer math.

Okay, that was not necessary, I suppose. :-p

I'll explain more about how to use bc and gforth more when we need them. 

For now, play with bc and gforth a little, especially converting some numbers of your own choice between binary, hexadecimal, and decimal.

And soon it will be time to talk about treating that list of small integers we've been working with as a real list.


(Title Page/Index

Friday, March 1, 2024

ALPP 01-01 -- Accumulating Integer Results in the M6809, 6800, 6801, and M68000

Accumulating Integer Results
on the M6809, 6800, 6801, and M68000

(Title Page/Index)

So what is an accumulator?

When you're working through a problem of some kind, you generally need some place to keep a record of your current results. As an example, think of adding up a total:

8 + 5 + 2 + 7 + 4 = 26 (if I didn't mess up the math)

If you can do that in your head, you probably don't even think about it, but you probably do keep a running total:

8
+ 5 = 13
+ 2 = 15
+ 7 = 22
+ 4 = 26 (Okay, I didn't mess up the math)
If you do that on a piece of paper in something like the way I just did it here, your "accumulator area" is the space to the right of the = sign next to the current number you're adding at each step. 

But if you do it in you're head, unless you've trained yourself (or have natural photographic memory or something), you only keep the running total (the accumulated value) and the number you are currently adding, not the previous lines and their results. That current result is your effective accumulator in your head. 

It's the natural current focus point of calculations.

In the 6800/6801 and the 6809, you have two small dedicated accumulators, usually called A and B. (In the 6801 and 6809, you can concatenate them together for a single, medium-sized Double accumulator D, for some operations, more on that later.)

In the 68000, you have eight large data registers which can be used as accumulators, usually called D0 to D7. (Usually. Some assemblers use different conventions.)

As a totally contrived sequence of operations, let's do the above in 6800 assembly language:

* Adding a sequence of constant values:
	LDAA	#8	; Gotta start somewhere.
	ADDA	#5
	ADDA	#2
	ADDA	#7
	ADDA	#4
	NOP		; landing pad for breakpoint 

(6801 is the same, or can be.) 

  • LDAA is the mnemonic for the "Load accumulator A" (LoaD Accumulator A) instruction operation code.
  • The # hash mark indicates that the numeric value is immediate.
  • ADDA is for "ADD to accumulator A". 
  • NOP is a No-OPeration op-code, in this case, as noted in the comment, for a landing pad where I can set a breakpoint.
  • And the notes after the semicolon are comments for human readers (mostly), and the computer (mostly) ignores them.

Now, this entire sequence is known in advance, and should usually be optimized to a single instruction

	LDAA	#26	; optimized version

But if we optimize away the operations now, we can't see what they are doing. So we won't do that. Not yet.

If you are wondering where the result is supposed to show up, good. 

Let's break out a debugger and trace it through:

% t on
% b 100a
Breakpoint set at 100A
% c 1000

          0 A=00 B=00 X=0000 SP=00FF ------          1000: 86 08    LDA #08   EA=1001 D=08
          1 A=08 B=00 X=0000 SP=00FF ------          1002: 8B 05    ADDA #05  EA=1003 D=05
          2 A=0D B=00 X=0000 SP=00FF ------          1004: 8B 02    ADDA #02  EA=1005 D=02
          3 A=0F B=00 X=0000 SP=00FF ------          1006: 8B 07    ADDA #07  EA=1007 D=07
          4 A=16 B=00 X=0000 SP=00FF H-----          1008: 8B 04    ADDA #04  EA=1009 D=04

Breakpoint!
>         5 A=1A B=00 X=0000 SP=FF8A ------          100A: 01       NOP                      

Looks good, right? Nothing happening in B, X, and SP. The H half-carry flag gets set and then goes away, we see the PC counting through the code, and the machine code is there with the assembler mnemonics. 

(Above, EA is something we call the effective address of the operand, and D is the data operand referenced at the effective address, more on that later.)  

But, wait, ... since when does 8 + 5 = what? D? And we don't see 26 in there at the end, or anywhere?

What went wrong? Computers are never wrong! (cough.) 

Must be something we did. 

Let's try a different sequence of numbers. Here's a well-known one:

1 + 2 + 3 + 4 + 5 + 6 = 21

Yep. ( 7 × 6 ) ÷ 2 is 21. Code:

	LDAA	#0	; Clear A out.
	ADDA	#1	; Add 1 to A.
	ADDA	#2	; Add 2.
	ADDA	#3	; Etc.
	ADDA	#4
	ADDA	#5
	ADDA	#6
	NOP		; landing pad
That ought to work. Trace it in the debugger:
% b 100e
Breakpoint set at 100E
% t on
% c 1000
          0 A=00 B=00 X=0000 SP=FF8A ------          1000: 86 00    LDA #00   EA=1001 D=00
          1 A=00 B=00 X=0000 SP=FF8A ---Z--          1002: 8B 01    ADDA #01  EA=1003 D=01
          2 A=01 B=00 X=0000 SP=FF8A ------          1004: 8B 02    ADDA #02  EA=1005 D=02
          3 A=03 B=00 X=0000 SP=FF8A ------          1006: 8B 03    ADDA #03  EA=1007 D=03
          4 A=06 B=00 X=0000 SP=FF8A ------          1008: 8B 04    ADDA #04  EA=1009 D=04
          5 A=0A B=00 X=0000 SP=FF8A ------          100A: 8B 05    ADDA #05  EA=100B D=05
          6 A=0F B=00 X=0000 SP=FF8A ------          100C: 8B 06    ADDA #06  EA=100D D=06

Breakpoint!
>         7 A=15 B=00 X=0000 SP=FF8A H-----          100E: 01       NOP                      

Much better. Load in a 0 and the result in A is 0. Add 1 to get 1, add 2 to get 3, and 3 to get 6, add 4 to get A ...

Erk. Giving 8 + 5 = D a D grade is understandable. What grade do we give to 6 + 4 = A?

Heh. The computer is not (this time) wrong. 

So let's talk about

Hexadecimal!

Simple debuggers like the one I'm using here display many results in hexadecimal instead of decimal, to save time, space on the screen, writing code, and just to make things more meaningful. (You doubt that last one? Trust me on this.)

For the time being, and maybe a quick refresher, here's a quick hexadecimal-to-decimal conversion chart, up to 31ten:

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
0 1 2 3 4 5 6 7 8 9 A B C D
E F
 
16
17 18
19
20
21
22
23
24
25
26 27
28
29
30
31
10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F

So, 8 + 5 does, in fact, equal Dsixteen (D base sixteen) in the usual (modern) way of writing hexadecimal.

And 6 + 4 does equal Asixteen (A base sixteen).

Yes, it's inconvenient when you've spent so much of your life getting used to decimal, but we'll try to take time to make sure we don't get lost in the hexadecimal. 

So, from the conversion chart, 22ten is 16sixteen. Maybe that's confusing, but let's look at why.

Remember that each column in base ten is a power of ten, increasing to the left. (That's most-significant column first.) 

22ten is 2 times ten plus 2 times one: 

twenty plus two, or 20ten + 2ten.

And each column in base sixteen is a power of sixteen, increasing to the left.

16sixteen is 1 times sixteen plus 6 times one: 

sixteen plus six, or 10sixteen + 6sixteen

(To help us not get confused, we can read that as "one-zero base sixteen plus six base sixteen".)

So, working in hexadecimal (and checking in decimal),

8sixteen
+ 5sixteen = Dsixteen (13ten)
+ 2sixteen = Fsixteen (15ten)
+ 7sixteen = 16sixteen (16ten + 6ten = 22ten)
+ 4sixteen = 1Asixteen (16ten + 10ten = 26ten)

There's our 26!

And, 

0sixteen
+ 1sixteen = 1sixteen (1ten)
+ 2sixteen = 3sixteen (3ten)
+ 3sixteen = 6sixteen (6ten)
+ 4sixteen = Asixteen (10ten)
+ 5sixteen = Fsixteen (15ten)
+ 6sixteen = 15sixteen (16ten + 5ten = 21ten)

And that's enough of non-decimal numeric bases for the moment.  

For comparison, the 6809 version of the first sequence is almost the same in assembler:

	LDA	#8	; 6809 mnemonic: LoaD accumulator A
	ADDA	#5	; Mnemonic shared with 6800/6801.
	ADDA	#2
	ADDA	#7
	ADDA	#4
	NOP		; landing pad for breakpoint 

Looking at that in a 6809 simulator:

% b 100a
Breakpoint set at 100A
% t on
% c 1000

          0 A=00 B=00 X=0000 Y=0000 U=0000 S=00FF P=00 --------            1000: 86 08        LDA #$08
          1 A=08 B=00 X=0000 Y=0000 U=0000 S=00FF P=00 --------            1002: 8B 05        ADDA #$05
          2 A=0D B=00 X=0000 Y=0000 U=0000 S=00FF P=00 --------            1004: 8B 02        ADDA #$02
          3 A=0F B=00 X=0000 Y=0000 U=0000 S=00FF P=00 --------            1006: 8B 07        ADDA #$07
          4 A=16 B=00 X=0000 Y=0000 U=0000 S=00FF P=00 --H-----            1008: 8B 04        ADDA #$04

Breakpoint!
>         5 A=1A B=00 X=0000 Y=0000 U=0000 S=00FF P=00 --------            100A: 12           NOP                        

That looks a lot like the 6800 emulation session, which is no real surprise. An extra index register (Y), an extra stack register (U), the direct page register (P). But it's a long line already, so the effective address and data are left out. And the accumulated sums are the same, as they should be.

And the 68000 version looks like this, using default register width instructions (which we will need to talk about later):

	MOVEQ	#8,D0	; MOVEQ lets us ignore register width.
	ADDQ	#5,D0	; Default register width is okay.
	ADDQ	#2,D0	; D0 is equivalent to 6800 A? Not quite?
	ADDQ	#7,D0
	ADDQ	#4,D0
	NOP		; landing pad for breakpoint     

MOVEQ is for "MOVE Quick"; ADDQ is for "ADD Quick". Both are somewhat optimized versions of the move and add instructions. MOVEQ affects the entire register, so any register size for the following ADD instructions is okay for the range of numbers we are using here.  (MOVEQ operands can be from -128 to 127; ADDQ operands can be from 1 to 8. Again, more later.)

With all the registers in the 68000, doing a one-line complete register dump becomes rather awkward. You'd need a screen able to show something like 300 characters per line, and then you'd wear your eyes out looking for what changed on each line. So you don't do that.

Here's what the top part of the screen looks like in the devpac debugger on the Hatari Atari ST emulator screen:

d0 = 00000000  ....    a0 = 00000000 602E 0206 00E0 0030 0001  ..........
d1 = 00000000  ....    a1 = 00000000 602E 0206 00E0 0030 0001  ..........
d2 = 00000000  ....    a2 = 00000000 602E 0206 00E0 0030 0001  ..........
d3 = 00000000  ....    a3 = 00000000 602E 0206 00E0 0030 0001  ..........
d4 = 00000000  ....    a4 = 00000000 602E 0206 00E0 0030 0001  ..........
d5 = 00000000  ....    a5 = 00000000 602E 0206 00E0 0030 0001  ..........
d6 = 00000000  ....    a6 = 00000000 602E 0206 00E0 0030 0001  ..........
d7 = 00000000  ....    a7 = 00000000 602E 0206 00E0 0030 0001  ..........
SR:0000    U          ssp = 00000000 602E 0206 00E0 0030 0001  ..........
PC:00E00030  bra $E0004E

After the data register on each line is the ASCII interpretation of the contents of the data register. Then, after the address register on  each line is the contents of ten bytes of  memory in hexadecimal, then the same ten bytes shown as if they were ASCII characters. SR is the status register, ssp is the system stack pointer, PC is the program counter. And that's a lot to take in.

Even without the extra interpretations and memory content, you can see the problem, right?

If the debugger would just show you the registers that changed, that would be great. But I don't have one of those kinds of debuggers for the 68000. 

I can pretend to be one, and I guess that will have to do for now. Here's what it might look like:

% t on
% b 1800a
Breakpoint set at 0001800A
% c 18000

  0 D0=00000000 A7=00017FF8 ----- 00018000: 7008 MOVEQ   #8,D0
  1 D0=00000008 A7=00017FF8 ----- 00018002: 5A40 ADDQ    #5,D0
  2 D0=0000000D A7=00017FF8 ----- 00018004: 5440 ADDQ    #2,D0
  3 D0=0000000F A7=00017FF8 ----- 00018006: 5E40 ADDQ    #7,D0
  4 D0=00000016 A7=00017FF8 ----- 00018008: 5840 ADDQ    #4,D0

Breakpoint!
> 5 D0=0000001A A7=00017FF8 ----- 0001800A: 4E71 NOP

Now, you're probably wanting to try some of this fun stuff yourself!

For the hexadecimal math, I plan to show how to build a hexadecimal calculator as part of the tutorial, but that's a bit down the road, yet.

Until then, hexadecimal math is supported by many desktop calculators on personal and workstation computers. Look for such things as programmer mode, and you'll probably find binary and octal modes, as well, within the programmer mode. 

Or, if you have a Unix or Linux OS workstation, you probably also have the Unix command-line arbitrary precision basic calculator utility, bc. That allows you to set the input and output conversion bases to arbitrary positive bases (greater than one), and can be fun and instructive to play with:

obase=16
ibase=16
8 + 8
10
8 + 5 + 2 + 7 + 4
1A

Or, the programming language Forth has a similar conversion base, but it's just one base for both input and output:

16 BASE !  ok
8 5 + 2 + 7 + 4 + . 1A  ok

(You might find post-fix math confusing, I suppose.) 

Many versions of Forth are available, I tend to use gforth, because it is available in the Debian and Ubuntu repositories.

Playing with hexadecimal math is fun, but what about using a debugger like I have shown above to learn assembler? 

There are, of course, many ways to get hardware and/or software to practice on. 

Hardware is good (if you have a way to get code onto the hardware), but I don't have room in our apartment for hardware. 

So I am using Joe H. Allen's emulator EXORsim for the 6800 and 6809. I'm using a hack I did on EXORsim for the 6801. And I'm using the Atari ST emulator Hatari for the 68000. And I'll explain how to get each of these in the next chapter, since this chapter is long enough already.

What about other CPUs, like the 8080, Z-80, 6502, 8086, ARM, RISC-V, Power PC, 6805, ... ? 

This is a fairly time-intensive project. Doing just the four I've already described above is going to be plenty to fill my plate. 

But when I get to a good pause point, I plan to come back and add chapters to demonstrate code for some other processors.

So,  let's see how to get the emulators (when I get the chapters ready).

First is EXORsim for the 6800 and 6809.


(Title Page/Index


ALPP -- Assembly Language Programming Primer -- Title Page and Index

Assembly Language Primer

Joel Matthew Rees

Amagasaki, Japan, 2024

Copyright 2024 Joel Matthew Rees

All rights reserved, except as explicitly specified:

******

This work, including all expository text, graphics, and code,
may be used for personal, individual study.
It may also be used for individual, private hobby purposes.
All other uses, including classroom, require permission.

Certain example code will specify additional permissions, for those examples only.

******

An introduction to assembly language programming under the split stack discipline, or the paradigm of maintaining dynamic parameters on a separate stack, accompanied by a record of my research on a certain project requiring assembly language.

Initially presenting the 6800, 6801, 6809, and 68000 in parallel.

Preface

  1. (Why am I writing this? etc.)

Basics (01)

  1. Accumulating Integer Results in the M6809, 6800, 6801, and M68000
  2. Getting a simulator for the 6800 and 6809
  3. Running code on the 6800 and 6809
  4. Getting a Simulator for the 6801 and Running Code on It  
  5. Getting a Simulator for the 68000 and Running Code on It (Hatari)
  6. Global Constants on the 6800, 6801, 6809, and 68000
  7. Working in Binary, Hexadecimal, Etc.: bc, gforth, and Other Options  
  8. Simple Lists on the 6800, 6801, 6809, and 68000 (array of bytes)
  9. Sequentially Accessing a Simple List on the 6800, 6801, 6809, and 68000
  10. Hello, World! (Not Yet on the Beach) -- 6800, 6801, 6809 (strings and getting output)
  11. Hello, World! (Not Yet on the Beach) -- 68000
  12. Hello, World! (Not Yet on the Beach) -- 6801 (EXORsim6801)  
  13. Hello, Bugs! Debugging Example, 6800 and 6809
  14. Doing This with Other Processors 

Beachhead (02)

  1. Foothold! (Split Stacks ... barely on the Beach) -- 6800 (strings and output, split stack)
  2. Foothold! (Split Stacks ... barely on the Beach) -- 6801  
  3. Foothold! (Split Stacks ... barely on the Beach) -- 6809
  4. Foothold! (Split Stacks ... barely on the Beach) -- 68000
  5. Introduction to Byte Arithmetic on the 6800, 6801, and 6809
  6. Introduction to Byte Arithmetic on the 68000
  7. A Note on Byte Widening and Scratch Registers on the 6800, 6801, and 6809  
  8. On the Beach with Parameters -- 16-bit Arithmetic on the 6800 (more split stack) 
  9. On the Beach with Parameters -- 16-bit Arithmetic on the 6801  
  10. On the Beach with Parameters -- 16-bit Arithmetic on the 6809
  11. On the Beach with Parameters -- 16-bit Arithmetic on the 6809 with Direct Page Moved
  12. On the Beach with Parameters -- 16/32-bit Arithmetic on the 68000
  13. Balancing on the Beach -- Comparing Pointers, Checking the Stack 
  14. One Foot on the Beach, One in the Surf -- Frame Pointer for Split Stack (incomplete)
  15. Switching Feet on the Beach -- Ephemeral Frame Pointer for Split Stack (incomplete)
  16. One Foot on One Beach, One Foot on Another -- Stack Frame for Single Stack: 68000 and 6809
  17. Unsteady Footing -- Stack Frame for Single Stack: 6801 (not recommended for code)
  18. Ascending the Wrong Island -- Single-stack Stack Frame Example: 68000 (and split stack frames)
  19. Ascending the Wrong Island -- Single-stack Stack Frame Example: 6809 (and split stack frames)
  20. Some Address Math for the 6800 (no discipline)
  21. Some Address Math for the 6801
  22. Some Address Math for the 6809  
  23. Synthesizing Multibyte NEG on 6809 (Applies to 6800 and 6801)
  24. Some Address Math for the 68000
  25. Ascending the Wrong Island -- Single-stack Stack Frame Example: 6801  
  26. Walking the Pontoons -- Split-stack Stack Frame Example: 6801
  27. Ascending the Wrong Island -- Single-stack Stack Frame Example: 6800
  28. Walking the Pontoons -- Split-stack Stack Frame Example: 6800  
  29. Putting the Wrong Island in the Rear-view Mirror -- Single-stack No Frame Example: 6800
  30. Ascending the Right Island -- Split-stack No Frame Example: 6800
  31. More Looking in the Rear-view Mirror -- Single-stack No Frame Example: 6801  
  32. More Ascending the Right Island -- Split-stack No Frame Example: 6801
  33. Ascending the Right Island -- Frameless Examples (Single- & Split-stack): 6809
  34. Ascending the Right Island -- Frameless Examples (Single- & Split-stack): 68000 
  35. Tentative Op-code Map of RK0801 CPU (Extension of M6801)

Framework Rigging (03)

  1. Binary Output on the 6800, Left-to-right; Framework-by-include (asm68c) (split stack)
  2. Binary Output on the 6801, Left-to-right; Framework-by-include
  3. Binary Output on the 6809, Left-to-right; Framework-by-include (LWTools)
  4. Binary Output on the 68000, Left-to-right; Framework-by-include (error in code!)
  5. Hexadecimal Output on the 6800, without General Divide
  6. Hexadecimal Output on the 6801, without General Divide
  7. Hexadecimal Output on the 6809, without General Divide (Register Parameters)
  8. Hexadecimal Output on the 68000, without General Divide (Register Allocation) (error in code!)
  9. Keyboard Input on the 6800, 6801, and 6809 (EXORsim) 
  10. Keyboard Input Routines and Character Code Output on the 6800 
  11. Keyboard Input Routines and Character Code Output on the 6801 
  12. Keyboard Input Routines and Character Code Output on the 6809 
  13. Keyboard Input Routines and Character Code Output on the 68000 (Debug Session -- Init Error) (fixing the errors in chapters 4 and 8, part 1)
  14. Keyboard Input Routines and Character Code Output on the 68000 (Debug Session -- Dealloc Error) (fixing the errors in chapters 4 and 8, part 2)   
  15. Converting Numbers for Output and Input with Multiplication and Division (Theory)

 Framework Rigging Notes (03-XX)

  1. Numeric Output Conversion and Multiplying by Constants (Theory) (to be replaced/moved/rewritten)
  2. Multiplying by Powers of Two (Shift Left) (to be replaced/moved/rewritten) 
  3. Demonstrating Left Shift -- 6800 (to be replaced/moved/rewritten) 
  4. Multiplying by Small Constants (Shift Left and Add) (to be replaced/moved/rewritten)














Sunday, February 25, 2024

Optimizing Direct-call Forth in 6809. 68000, and 6800/6801

(This is a little doodling inspired by a post suggesting stackless Forth in the Minimalist Computing Facebook group. Doing this for 6809, 68000, and 6800.) 

Start with a bit of fig Forth to practice optimizing. All words called by COUNT are leaf words, making them easy to in-line. 

The fig source code ai am working with, borrowed from my transcriptions of the 6800 model, with a few comments on what I am doing:

* From Dave Lion, et. al, fig Forth model for 6800:
* Converting to direct call, in-lining, and optimizing.
* This is low-hanging fruit, but easy to see how it could work.
* Should be possible to do this all mechanically, I think.
* I'm going to pretend I know what the optimizer that
* does not yet exist will do for 6809, 68000, 6801 and 6800.
figCOUNT:
	FDB	DOCOL,DUP,ONEP,SWAP,CAT
	FDB	SEMIS
* 
figCOUNTlinearsource:
	FDB	DOCOL
	FDB	DUP
	FDB	ONEP
	FDB	SWAP
	FDB	CAT
	FDB	SEMIS

I'll start with the 6809 because it should be easy and straightforward. It probably isn't important, but, to emphasize that I'm doing away with the VM overhead and just working within a run-time that projects the VM onto the 6809 IPA, I'm showing the direct calls in the 6809 assembler, along with the functions called.

Below the in-line source, I'm showing the results of each theoretical optimization pass, starting with removing the easy extraneous data movement on the stack and in registers, and proceeding with moving things around and combining operations. Then I show the final source, along with an optional version that may not be easy to reach along mechanical paths:

******************************
* First, 6809
6809COUNTcall:
*	LBSR	DOCOL	; direct call
	LBSR	DUP
	LBSR	ONEP
	LBSR	SWAP
	LBSR	CAT
*	LBSR	SEMIS
	RTS

* Leaf routines defined as
6809DUP
	LDD	,U
	STD	,--U
	RTS

6809ONEP
*	INC	1,U	; harder to optimize, keep it simple.
*	BNE	ONEPNC
*	INC	,U
*ONEPNC	RTS
	LDD	,U
	ADDD	#1
	STD	,U
	RTS

6809SWAP
*	PULU	D,X	; harder to optimize, keep it simple.
*	EXG	D,X	; could also do separate stores, etc.
*	PSHU	D,X
*	RTS
*	
	LDD	,U
	LDX	2,U
	STD	2,U
	STX	,U
	RTS

6809CAT
	CLRA
	LDB	[,U]
	STD	,U
	RTS

* bringing the calls in-line:
6809COUNTinline:
	LDD	,U	; DUP
	STD	,--U
*
	LDD	,U	; 1+
	ADDD	#1
	STD	,U
*
	LDD	,U	; SWAP
	LDX	2,U
	STD	2,U
	STX	,U
*
	CLRA		; C@
	LDB	[,U]
	STD	,U
*
	RTS

* Vacuum out the data motion on the stack:
6809COUNTpass1
	LDD	,U	; DUP
*	STD	,--U
	LEAU	-2,U
*
*	LDD	,U	; 1+
	ADDD	#1
*	STD	,U
*
*	LDD	,U	; SWAP
	LDX	2,U
	STD	2,U
	STX	,U
*
	CLRA		; C@
	LDB	[,U]
	STD	,U
	RTS

* Combine and simplify:
6809COUNTpass2
	LDD	,U	; DUP
	LEAU	-2,U
*
	ADDD	#1	; 1+
*
	LDX	2,U	; SWAP
	STD	2,U	; Misordering possible.
*	STX	,U
*
	CLRA		; C@
*	LDB	[,U]
	LDB	,X
	STD	,U
	RTS

* Postpone stack operations:
6809COUNTpass3
	LDD	,U	; DUP
*	LEAU	-2,U
*
	ADDD	#1	; 1+
*
*	LDX	2,U	; SWAP
	LDX	,U	; SWAP
*	STD	2,U
	STD	,U
*
	CLRA		; C@
	LDB	,X
*	STD	,U
	STD	,--U
*
	RTS

6809COUNTrearrange
*	LDD	,U	; DUP
	LDX	,U
*
*	ADDD	#1	; 1+
*
*	LDX	,U	; SWAP
*	STD	,U
*
	CLRA		; C@
*	LDB	,X
	LDB	,X+
	STX	,U
*
	STD	,--U
	RTS
*
6809COUNTfinal
	LDX	,U
	CLRA		; C@
	LDB	,X+
	STX	,U
	STD	,--U
	RTS
*

* compare (Could this be done mechanically, too?):
6809COUNTmaybe:
	LDX	,U
	CLRA
	LDB	,X+
	STD	,--U
	STX	2,U
	RTS

The 68000 code follows the 6809 code's optimization paths rather closely, since they both support high-level run-time models quite well, and in similar ways.

******************************
* Now 68000:
68KCOUNTcall:
*	BSR.W	DOCOL	; direct call
	BSR.W	DUP
	BSR.W	ONEP
	BSR.W	SWAP
	BSR.W	CAT
*	BSR.W	SEMIS
	RTS

* Leaf routines defined as
68KDUP
*	MOVE.L	(A6),-(A6)	; Harder to optimize, keep it simple.
*	RTS
	MOVE.L	(A6),D0
	MOVE.L	D0,-(A6)
	RTS

68KONEP
*	ADD.L	#1,(A6)	; Harder to optimize, keep it simple.
*	RTS
	MOVE.L	(A6),D0	; Keep it simple
	ADD.L	#1,D0
	MOVE.L	D0,(A6)
	RTS

68KSWAP
*	MOVEM.L	(A6),D0/D1	; Harder to optimize, keep it simple
*	EXG	D0,D1
*	MOVEM.L	D0/D1,(A6)
*	RTS
	MOVE.L	(A6),D0
	MOVE.L	2(A6),D1
	MOVE.L	D0,2(A6)
	MOVE.L	D1,(A6)
	RTS

68KCAT
	CLR.L	D0	; zero-extend
	MOVE.L	(A6),A0
	MOVE.B	(A0),D0
	MOVE.L	D0,(A6)
	RTS

* in-line:
68KCOUNTinline:
	MOVE.L	(A6),D0	; DUP
	MOVE.L	D0,-(A6)
*
	MOVE.L	(A6),D0	; 1+
	ADD.L	#1,D0
	MOVE.L	D0,(A6)
*
	MOVE.L	(A6),D0	; SWAP
	MOVE.L	2(A6),D1
	MOVE.L	D0,2(A6)
	MOVE.L	D1,(A6)
*
	CLR.L	D0	; C@
	MOVE.L	(A6),A0
	MOVE.B	(A0),D0
	MOVE.L	D0,(A6)
*
	RTS

* Vacuum out the data motion on the stack:
68KCOUNTpass1
	MOVE.L	(A6),D0	; DUP
*	MOVE.L	D0,-(A6)
	LEA	-4(A6),A6
*
*	MOVE.L	(A6),D0	; 1+
	ADD.L	#1,D0
*	MOVE.L	D0,(A6)
*
*	MOVE.L	(A6),D0	; SWAP
	MOVE.L	2(A6),D1	; Misordering possible.
	MOVE.L	D0,2(A6)
*	MOVE.L	D1,(A6)
*
	CLR.L	D0	; C@
*	MOVE.L	(A6),A0
	MOVE.L	D1,A0
	MOVE.B	(A0),D0
	MOVE.L	D0,(A6)
*
	RTS

* Combine and simplify:
68KCOUNTpass2
	MOVE.L	(A6),D0	; DUP
	LEA	-4(A6),A6
*
	ADD.L	#1,D0	; 1+
*
*	MOVE.L	4(A6),D1	; SWAP
	MOVE.L	4(A6),A0	; SWAP
	MOVE.L	D0,4(A6)
*
	CLR.L	D0	; C@
*	MOVE.L	D1,A0
	MOVE.B	(A0),D0
	MOVE.L	D0,(A6)
*
	RTS

* Postpone stack operations:
68KCOUNTpass3
	MOVE.L	(A6),D0	; DUP
*	LEA	-4(A6),A6
*
	ADD.L	#1,D0	; 1+
*
*	MOVE.L	4(A6),A0	; SWAP
	MOVE.L	(A6),A0	; SWAP
*	MOVE.L	D0,4(A6)
	MOVE.L	D0,(A6)
*
	CLR.L	D0	; C@
	MOVE.B	(A0),D0
*	MOVE.L	D0,(A6)
	MOVE.L	D0,-(A6)
*
	RTS

68KCOUNTrearrange
*	MOVE.L	(A6),D0	; DUP
	MOVE.L	(A6),A0
*
*	ADD.L	#1,D0	; 1+
*
*	MOVE.L	(A6),A0	; SWAP
*	MOVE.L	D0,(A6)
*
	CLR.L	D0	; C@
*	MOVE.B	(A0),D0
	MOVE.B	(A0)+,D0
	MOVE.L	A0,(A6)
	MOVE.L	D0,-(A6)
*
	RTS

68KCOUNTfinal
	MOVE.L	(A6),A0
	CLR.L	D0	; C@
	MOVE.B	(A0)+,D0
	MOVE.L	A0,(A6)
	MOVE.L	D0,-(A6)
	RTS


* compare (Could this be done, too?):
68KCOUNTmaybe:
	MOVE.L	(A6),A0
	CLR.L	D0
	MOVE.B	(A0)+,D0
	MOVE.L	D0,-(A6)
	MOVE.L	A0,4(A6)
	RTS

The 6801's 16-bit support, with more primitive resources, induces a different path:

******************************
* Next, 6801

* Somewhere, preferably in the direct page,
* Must NOT be used by interrupt-time routines!
* -- Either save it or have interrupts use another PSP
PSP	RMB	2	; parameter stack pointer
* DTEMPA	RMB	2	; temp for SWAP, ...

6801COUNTcall:
*	JSR	DOCOL	; direct call
	JSR	DUP
	JSR	ONEP
	JSR	SWAP
	JSR	CAT
*	JSR	SEMIS
	RTS

* Leaf routines defined as
6801DUP
	LDX	PSP
	LDD	0,X
	DEX
	DEX
	STD	0,X
	STX	PSP
	RTS

6801ONEP
*	LDX	PSP
*	INC	1,X	; harder to optimize, keep it simple.
*	BNE	ONEPNC
*	INC	0,X
*ONEPNC	STX	PSP
*	RTS
	LDX	PSP
	LDD	0,X
	ADDD	#1
	STD	0,X
	RTS

6801SWAP
*	LDX	PSP	; this uses no static local variable,
*	LDAA	0,X	; but it will be harder to optimize
*	LDAB	2,X
*	STAA	2,X
*	STAB	0,X
*	LDAA	1,X
*	LDAB	3,X
*	STAA	3,X
*	STAB	1,X
*	RTS
	LDX	PSP
	LDD	0,X
*	STD	DTEMPA	; Faster, but uses statically allocated variable
	PSHB		; avoid opportunities to make interrupt-time issues
	PSHA
	LDD	2,X
	STD	0,X
*	LDD	DTEMPA
	PULB
	PULA
	STD	2,X
	RTS
 
6801CAT
	LDX	PSP
	LDX	0,X
	CLRA
	LDB	0,X
	LDX	PSP
	STD	0,X
	RTS

* in-line:
6801COUNTinline:
	LDX	PSP	; DUP
	LDD	0,X
	DEX
	DEX
	STD	0,X
	STX	PSP
*
	LDX	PSP	; 1+
	LDD	0,X
	ADDD	#1
	STD	0,X
*
	LDX	PSP	; SWAP
	LDD	0,X
*	STD	DTEMPA	; Faster, but uses statically allocated variable
	PSHB		; avoid opportunities to make interrupt-time issues
	PSHA
	LDD	2,X
	STD	0,X
*	LDD	DTEMPA
	PULB
	PULA
	STD	2,X
*
	LDX	PSP	; C@
	LDX	0,X
	CLRA
	LDB	0,X
	LDX	PSP
	STD	0,X
*
	RTS

* Vacuum out the data motion on the stack:
6801COUNTpass1
	LDX	PSP	; DUP
	LDD	0,X
	DEX
	DEX
*	STD	0,X
	STX	PSP
*
*	LDX	PSP	; 1+
*	LDD	0,X
	ADDD	#1
*	STD	0,X
*
*	LDX	PSP	; SWAP
*	LDD	0,X
*	STD	DTEMPA	; Faster, but uses statically allocated variable
	PSHB		; avoid opportunities to make interrupt-time issues
	PSHA
	LDD	2,X
	STD	0,X
*	LDD	DTEMPA
	PULB
	PULA
	STD	2,X
*
*	LDX	PSP	; C@
	LDX	0,X
	CLRA
	LDB	0,X
	LDX	PSP
	STD	0,X
*
	RTS

* Combine and simplify is stuck at this point,
6801COUNTrearrange
	LDX	PSP	; DUP
*	LDD	0,X
	DEX
	DEX
	STX	PSP
*
*	ADDD	#1	; 1+
*
**	STD	DTEMPA	; SWAP
*	PULB
*	PULA
*	LDD	2,X
*	STD	0,X
**	LDD	DTEMPA
*	PULB
*	PULA
*	STD	2,X
	LDD	2,X	; SWAP
	STD	0,X
*
	LDX	0,X	; C@
	CLRA
	LDB	0,X
*
	LDX	PSP
	STD	0,X
*
	LDD	2,X
	ADDD	#1	; 1+
	STD	2,X
*
	RTS

* Postponing stack operations is already done:
6801COUNTfinal
	LDX	PSP
	DEX
	DEX
	STX	PSP
	LDD	2,X	; DUP {SWAP}
	STD	0,X
	LDX	0,X	; C@
	CLRA
	LDB	0,X
	LDX	PSP
	STD	0,X
	LDD	2,X	; 1+
	ADDD	#1
	STD	2,X
	RTS

*6801COUNTmaybe	; No obvious alternate paths.

The 6800's lack of 16-bit support induces yet different paths, which are (surprisingly?) similar to the 6809's and 68000's paths:

******************************
* And, 6800

* Somewhere, preferably in the direct page,
* Must NOT be used by interrupt-time routines!
* -- Either save it or have interrupts use another PSP
PSP	RMB	2	; parameter stack pointer
DTEMPA	RMB	2	; temp for SWAP, ...

6800COUNTcall:
*	JSR	DOCOL	; direct call
	JSR	DUP
	JSR	ONEP
	JSR	SWAP
	JSR	CAT
*	JSR	SEMIS
	RTS

* Leaf routines defined as
6800DUP
	LDX	PSP	; will doing this one byte at a time
	LDAA	0,X	; be easier to optimize or harder?
	LDAB	1,X
	DEX
	DEX
	STAA	0,X
	STAB	1,X
	STX	PSP
	RTS

6800ONEP
	LDX	PSP
	INC	1,X	; Have to add byte at a time anyway.
	BNE	ONEPNC
	INC	0,X
ONEPNC	STX	PSP
	RTS
*	LDX	PSP
*	LDAA	0,X
*	LDAB	1,X
*	ADDB	#1
*	ADCA	#0
*	STAA	0,X
*	STAB	1,X
*	RTS

6800SWAP
*	LDX	PSP	; Use accumulaters for intermediates
*	LDAA	0,X	; Requires special case to recognize what's where.
*	LDAB	2,X
*	STAA	2,X
*	STAB	0,X
*	LDAA	1,X
*	LDAB	3,X
*	STAA	3,X
*	STAB	1,X
*	RTS
	LDX	PSP	; Should be easier to optimize.
	LDAA	0,X	; SWAP should almost always optimize out.
	LDAB	1,X
	PSHB		; avoid opportunities to make interrupt-time issues
	PSHA
	LDAA	2,X
	LDAB	3,X
	STAA	0,X
	STAB	1,X
	PULB
	PULA
	STAA	2,X
	STAB	3,X
	RTS
 
6800CAT
	LDX	PSP
	LDX	0,X
	CLRA
	LDB	0,X
	LDX	PSP
	STAA	0,X
	STAB	1,X
	RTS

* in-line:
6800COUNTinline:
	LDX	PSP	; DUP
	LDAA	0,X
	LDAB	1,X
	DEX
	DEX
	STAA	0,X
	STAB	1,X
	STX	PSP
*
	LDX	PSP	; 1+
	INC	1,X
	BNE	ONEPNC
	INC	0,X
ONEPNC
	STX	PSP
*
	LDX	PSP	; SWAP
	LDAA	0,X	; SWAP should almost always optimize out.
	LDAB	1,X
	PSHB		; avoid opportunities to make interrupt-time issues
	PSHA
	LDAA	2,X
	LDAB	3,X
	STAA	0,X
	STAB	1,X
	PULB
	PULA
	STAA	2,X
	STAB	3,X
* 
	LDX	PSP	; C@
	LDX	0,X
	CLRA
	LDB	0,X
	LDX	PSP
	STAA	0,X
	STAB	1,X
*
	RTS

* Vacuum out the easy data motion:
6800COUNTpass1
	LDX	PSP	; DUP
	LDAA	0,X
	LDAB	1,X
	DEX
	DEX
	STAA	0,X
	STAB	1,X
	STX	PSP	; Make the push permanent.
*
*	LDX	PSP	; 1+
	INC	1,X
	BNE	ONEPNC
	INC	0,X
ONEPNC
*	STX	PSP
*
*	LDX	PSP	; SWAP
	LDAA	0,X	; SWAP should almost always optimize out.
	LDAB	1,X
	PSHB		; avoid opportunities to make interrupt-time issues
	PSHA
	LDAA	2,X
	LDAB	3,X
	STAA	0,X
	STAB	1,X
	PULB
	PULA
	STAA	2,X
	STAB	3,X
* 
*	LDX	PSP	; C@
	LDX	0,X
	CLRA
	LDB	0,X
	LDX	PSP
	STAA	0,X
	STAB	1,X
*
	RTS

* Some easy combinations and data movement tracking:
6800COUNTpass1_1
	LDX	PSP	; DUP
	LDAA	0,X
	LDAB	1,X
	DEX
	DEX
	STAA	0,X
	STAB	1,X
	STX	PSP	; Make the push permanent.
*
*	INC	1,X	; 1+
	INCB		; 1+
	BNE	ONEPNC
*	INC	0,X
	INCA
ONEPNC
*
*	LDAA	0,X	; SWAP should almost always optimize out.
*	LDAB	1,X
*	PSHB		; avoid opportunities to make interrupt-time issues
*	PSHA
*	LDAA	2,X
*	LDAB	3,X
*	STAA	0,X
*	STAB	1,X
*	PULB
*	PULA
	STAA	2,X	; SWAP
	STAB	3,X
* 
	LDX	0,X	; C@
	CLRA
	LDB	0,X
	LDX	PSP
	STAA	0,X
	STAB	1,X
*
	RTS

* Combine one more,
6800COUNTrearrange
	LDX	PSP	; DUP
	LDAA	0,X
	LDAB	1,X
	DEX
	DEX
	STAA	0,X
	STAB	1,X
	STX	PSP	; Make the push permanent.
*
	INCB		; 1+
	BNE	ONEPNC
	INCA
ONEPNC
	STAA	2,X	; SWAP
	STAB	3,X
* 
	LDX	0,X	; C@
*	CLRA
	LDB	0,X
	LDX	PSP
*	STAA	0,X
	CLR	0,X
	STAB	1,X
*
	RTS

* Final
6800COUNTfinal
	LDX	PSP	; DUP
	LDAA	0,X
	LDAB	1,X
	DEX
	DEX
	STAA	0,X
	STAB	1,X
	STX	PSP	; Make the push permanent.
	INCB		; 1+
	BNE	ONEPNC
	INCA
ONEPNC
	STAA	2,X	; SWAP
	STAB	3,X
	LDX	0,X	; C@
	LDB	0,X
	LDX	PSP
	CLR	0,X
	STAB	1,X
	RTS

*6800COUNTmaybe	; No obvious alternate paths.

JFTR, this code has not been particuly tested.

Sunday, February 11, 2024

ALPP -- Assembly Language Programming Primer -- Preface

Preface

(Title Page/Index)

Lance Leventhal wrote a series of books on assembly language programming that many regard as the Assembly Language Programming Bible for their favorite microprocessor.

I theoretically own, somewhere, if the rain hasn't destroyed them yet, a copy of two of his books, the one on the 6809 and the one on the 68000. Thirty years or so ago, I kept trying to use them to help me with a certain project that I have given way too much of my life to, until I realized two things:

The first was that I already understood everything in both of those books, just from studying Motorola's programmers' manuals. Motorola had some really amazing documents at the time. Not perfect, but amazing.

The second was that the approach he had taken was leading me away from my goals in that project in subtle ways. He wasn't teaching assembly language programming, he was teaching a specific discipline that was being taken up by many in the industry as intellectual meta-infrastructure.

Not incidentally, I was a fan of an internal combustion engine that I read about in Popular Science that a guy named Turner designed, that a friend once looked at and said, "That's a U-joint turned into an engine." I don't remember if it was Turner or the author of the Popular Science article on the engine or someone else (Bricklin?) who called it a rotary V engine -- as in rotary V-8, which made it sound like a contradiction in terms. 

Another friend doubted that the torsional stresses could be properly taken care of. I asked him about torsion in camshafts, and he said, because we've already taken care of that with camshafts. (In other words, the engineering had already been done on cam shafts, who was going to pay for the engineering on this new way to transfer power?)

Maybe I'm a hopeless fan of underdogs.

Turning that engine into a product for ordinary consumers would have required creating a support infrastructure -- mechanics and repair shops trained in subtle technical differences that would not just know how to work on it, but how it works. In addition, there would be parts suppliers, and sales networks and such. But the ordinary internal combustion engine support infrastructure is different, and somehow gets in the way of building support structures for odd-ball engines like that Turner rotary V, and the Wankel engine that Mazda finally (mostly) gave up on (for cars), among many others. 

With no infrastructure, trying to commercialize that engine was a little like walking on air.

Infrastructure for electric vehicles has been significantly helped by a certain billionaire's involvement. (And rotary engines seem to be being brought along, in a comeback as range extenders.)

Established infrastructure gets in the way of many interesting things in our society.

Somebody famous has said, 

The perfect is the enemy of the good.

Many somebodies have parroted this truistic aphorism. 

We can logically invert this. 

The mediocre good is the enemy of perfection, and therefore the enemy of progress.

Now, mind you, human ideals can never be realized. Ideal is not real. God's ways are not our ways, and his thoughts are not our thoughts (Isaiah 58: 8, 9). But that is not the same thing as saying we should prioritize the mediocre good because perfection is hard to achieve. 

Nor is it saying that we can get along without ideals. Ideals are necessary, just not the ultimate end. Ideals keep us from becoming mediocre.

I'm rambling.

What does this side ramble have to do with assembly language primers?

I'm trying to find a step forward on making something real of at least parts of that project that has eaten too much of my life. (Am I a glutton for punishment?) 

Some FB friends who like Leventhal's book on the 6809 asked why I said it's not my favorite, and have challenged me to write a better primer. 

So I'm thinking maybe such a set of tutorials would be a workable step forward on that project.

That's kind of what I have in mind for this series of blog posts -- a public record of my work, but trying to make it accessible to people who try to read it.

So I need to lay out a warning up front that my approach to assembly language is subtly incompatible with most of the existing programming tools. It looks really good until you realize that, if you use my approach, you will sooner or later find yourself trying to, as it were, walk on air (no infrastructure), unless you take the time to understand the differences and are willing to develop some of your own tools and share them.

I don't have the means to develop all the missing support tools by myself.

The existing CPUs aren't quite there, either.

My favorite existing processor, Motorola's venerable 6809, isn't really quite up to my approach, because it's missing an indexing mode that would allow using the Load Effective Address instructions to take the address of a variable in the run-time direct page. We can work around this, but it consumes instructions and processor cycles.

(Speaking of the 6809, it's surprising how many professional engineers pick it up and see the U index register as a frame pointer rather than a second stack pointer.)

The 68000 isn't quite up to it, either. This is not because the 68020 is too complex and the original 68000 doesn't have 32-bit constant offset index modes and it's hard to get Motorola's CPU32 without a lot of SOC stuff that you don't want to mess with initializing, etc.  (This is all true, but.) The problem is that the 68000 has all these index registers, but using them as segment registers gets in the way of using them as index registers. LoL. Not really a problem, but a problem. We can work around this, as well.

Speaking of segment registers, Intel's x86 could have been a bit more workable -- if still clunky, but BP and SP are stuck in the same segment unless you use segment override. And you really don't want to do that because it's dangerous and gets in the way -- BP designed as frame base pointer instead of parameter stack pointer is definitely one of the things we would be fighting against on x86. There are workarounds, such as ignoring that the two stacks are in the same segment. (The Extra Segment doesn't work for this, the last time I tried it. That is, you really don't want to be losing access to the parameter stack every time you want to move strings.) I think I'll let someone else play with that. At least, I'm not interested in doing so again right now. I've seen what happens when that siren song takes its toll.

Existing CPUs that implement the primitives of Forth-like languages or LISP kernels don't quite do the job either. At least, I have never found one such that included support for such things as virtualizing address spaces. 

Real processors are all designed to work within the existing meta-infrastructure.

Motorola's 6800 and 6801 actually get closer than you might expect (ignoring the lack of direct-page opcodes for the unary operators), if you define the use of a few direct page variables as virtual registers -- software parameter stack pointer and such -- and define support routines for the virtual registers. They would be even closer if Motorola hadn't taken a false optimization in the design and left out unary op-codes for the read-modify-write group of operators. (Hindsight is twenty/twenty, particularly from the passenger seat.)

RISC processors can be similarly supported, but need macros instead of routines for missing operators because of the cost of branches in deeply pipelined architectures. Modified RISC, like ARM and PowerPC, support building the new infrastructure rather well, but will still need macros for certain common operations.

Of course, that's also true of any processor that doesn't include the necessary registers and operations as part of the native CPU architecture, but it consumes cycles and memory resources. And there is always that siren song of false optimizations that lures you away, into the existing meta-infrastructure.

I keep hearing from people rediscovering "stackless". That's one of those false optimizations. Why it's a false optimization takes a lot of explanation, though. I expect I'll at least partly cover that -- implicitly -- in the process here.

As we go, I'll demonstrate the necessary virtual registers and support routines for the 6800 and 6801. Once we see what we can do with them and what walls we run into without, we can talk about why and how to apply it other CPUs.

But tutorials need to start simply. Explaining why one needs a parameter stack while you are teaching how to build a software stack while you are explaining what LDAA 0,X means gets kind of unwieldy.

And if you are looking for a primer, all this talk of esoteric problems isn't getting you started writing assembly language programs.

Anyway, be warned:

If you proceed with this series, I'm going to teach you things that will make you dissatisfied with the tools you have available, and I don't have the means of creating the missing tools by myself. I hardly have the time to write the tutorial chapters.

So, what does the example I just gave of the 6800/6801 indexed mode load accumulator A

    LDAA 0,X

mean? And the 6809 equivalent, 

    LDA    ,X

And the 68000 near-equivalent, 

    MOVE.B (A0),D0

and the difference between that and, say,

    MOVE.L (A0),D0

on the 68000?

Ick. Jumping in too deep already, perhaps. Definitely needs context and underlying concepts. 

Maybe start with accumulators and data registers.

(Title Page/Index)