No new projects this week; I’m still decompressing after the Spectrum project. Instead, I tried to reset a bit by reading through some of the books I’d picked up from the Vintage Computer Fair over the past few years. There’s a booth that’s been there for quite some time selling vintage reference books and other technical works, and I often grab a book or two when I’m there. Their signage suggests that their clientele would be buying these books mostly for decoration. They probably aren’t far wrong, to be honest, but what say we break tradition and actually read a few?

We will admittedly end up needing some generous definitions of “read” for some of these.

MC68000 Programmer’s Reference Manual

I mostly picked this up so that I could have a physical copy to reference. NXP itself actually has more recent and updated PDFs on their own site covering the entire family, but it’s nice to have the physical book when I need to look something up.

Machine and Assembly Language Programming on the PDP-11, by Arthur Gill

I’ve never really looked at minicomputer-class CPUs over the years, so I picked this up to see how it compared to the chips I’m more used to. The TI-99/4A’s TMS9900 CPU was based on their TMS990 minicomputer design, as I understand it, and it was pleasantly mind-bending to work with. This was the wrong approach here, for a couple reasons.

The first error involved the book. I thought I had purchased some kind of guide, or at least a reference. It is actually a textbook, complete with problem sets and projects, intended as a companion text in a class on computer organization and design—effectively a 1978-era standalone version of the MIPS appendices in the textbook from my own school days. A quick search through Google and Amazon suggests that Patterson and Hennessey’s classic 90s-era text is still in use today, but the appendices now may involve ARM and RISC-V instead.

My second mistake was about the PDP-11 itself. While I had expected it to be alien—an artifact of a previous generation of computer design—I had failed to take into account that it was also extremely influential, with its shadow clearly cast over the 16-bit microcomputer CPUs and even echoed a bit in the 8-bit space.

You can still clearly tell that it’s 1978 here, though. The text prefers octal to hex throughout, and the machine instructions end up reflecting this a bit, too, with 3-bit fields at 3-bit alignments in many places, as if they expected most code to be hand-assembled. On the other hand, it didn’t have to be hand-assembled, and the MACRO-11 assembler it describes in its lab sections is recognizable and comfortable to any 21st-century assembly language programmer. Its coverage of relocatable object files, position independent code, and linkers (“linkage editors”) would need only light edits to be fully relevant today. 1978 is late enough that while the microcomputer revolution is just getting started, industrial and commercial computing is in full swing, and we still use direct descendants of a lot of the tools and standards in that space.

The focus of the book, though, was as a primer in assembly language programming for students who potentially had never worked at that level before. The principles of programming at that level really aren’t meaningfully different from anything from the 1980s and 1990s that we’ve spent a decade here taking for granted; the things it wants to teach were taught elsewhere long ago. The fun I had here came from seeing how the PDP-11 presented itself to a novice programmer, and what MACRO-11 or even simpler assemblers would layer on top of the CPU’s core capabilities. That part had some things I’d never seen before, and the way the system papers over the apparent shortcomings of its own instruction set design were frankly delightful.

As such, the rest of the review here is going to just be talking about the PDP-11’s instruction format and instruction set, and what assemblers were expected to do to make the CPU’s apparent capabilities richer than they were from a naïve reading of the instruction encodings.

What the Machine Code Offers

Instructions have between zero and two arguments, and all arguments involve one of the 8 registers, an addressing mode, and potentially an optional argument after the first instruction word. That means that instructions themselves are between one and three 16-bit words long, and the first word ends up being both very rigid and octal-friendly. 8 registers and 8 addressing modes means each argument is exactly two octal digits, and they’re placed in the instruction so that they’ll appear in octal dumps in just that way. The rest of the instruction is the operation code, with, at minimum, 4 bits of space for instruction name, and more for instructions with fewer arguments. A 16-bit word in octal has five digits that can be anything and a sixth digit that is either 0 or 1. The designers decided to use that top bit, for instructions where it makes sense, to mark whether this is a word-level instruction or a byte-level instruction.

So that’s cute. That said: 8-bit byte operations in octal are terrible, goodness gracious. So much of the problem sets in this book drew their tedium from having to extract high bytes from octal words.

The addressing modes are a sort of “4×2” system. It’s easiest to explain them with C syntax, though I can bring in examples from later microprocessors, too. If the variable is named s…

  1. Register Mode. You can just use s itself. This is most Z80 modes, and the 68000 lets you use the “data registers” that way by simply naming them in the instruction, d0. Interestingly, the 6502 has nothing like this at all; the only register-register operations it permits are transfers.
  2. Postincrement Mode. In C, that would be *s++. You treat the register as a pointer, and once it reads the value, you advance to the next value in memory. MACRO-11 uses the same syntax for this as the 68000: (a0)+. Unlike the 68000, the PDP-11 can use all registers here and registers are just named r0 instead of a0 or d0.
  3. Predecrement Mode. In C, that’s *--s. Same as Postincrement, but you subtract and you do it before you read the value. The 68000 has -(a0) with identical semantics. C offers postdecrement and preincrement too, but neither the PDP-11 nor the 68000 does.
  4. Indexed Mode. In C, this is roughly s[i]. Treat s+i as an address and then dereference it. The PDP-11 and 68000 again use the same syntax: 4(a0) for i=4,s=a0. It also matches the 6809’s n,X syntax for index combinations, and, if you squint, the Z80’s [IX+d] option.

All four of these can also be “deferred”, which means that the value you would normally return is instead treated as an address and the contents of that address are returned. This is, in the Z80 and 68000, the difference between a0 and (a0), or (again if you squint and ignore 8- vs 16-bit values) between HL and (HL). PDP-11 syntax writes Register Deferred in that way, but prepends an @ for the other three modes. The Indexed Deferred mode turns out to match the 6502’s weird and mostly-useless (nn,X) mode, but it’s got more juice when nn and X both can reach the entire memory space.

What’s Missing, and How We Get It

There’s a pretty glaring omission here: there’s no way to specify actual values! The only constants that get provided as part of instructions are the offsets in the Indexed modes. Pretty much every CPU we’ve looked at solves this under the hood by having an additional class of instruction whose job it is to contain values like that for direct use as either values or as addresses. (Indeed, the branch instructions for the PDP-11 work like that for their offsets, but they’re the only instructions that permit this.) To deal with this, the PDP-11 has a very elegant trick up its sleeve: the program counter is R7 and it works with all eight address modes, just like everything else. It also advances as the opcode is read, so it’s already pointing at the next word at the time the argument is being decoded.

And so: consider what happens if we use (R7)+ as an argument to an instruction. It will take the next word in the instruction stream as the value, and then execution itself will skip over it. That means that the instruction carried an immediate-mode argument! Similarly, @(R7)+, the deferred autoincrement, will treat the value as a memory address to read, matching the 6502 and 68000’s “absolute mode” operation. Indexing the program counter, with i(R7), doesn’t grant us anything that absolute mode does not, immediately, but it makes the code position independent by encoding locations based on their distance from the current instruction. The 68000 has special modes for that, too, usually written i(pc). The PDP-11 can enhance that a bit by putting an extra pointer dereference into one instruction via its Indexed Deferred mode.

MACRO-11, and, I’d assume, all other assemblers for the platform, don’t require the programmer to directly manage the program counter like that. Instead, they invent new syntax for these not-quite-actually-there addressing modes, with #nn for immediate constants, @#nn for absolute, nn alone for PC-relative values, and @nn for PC-relative deferred. This isn’t a hack or a trick by the assembler, here; DEC very obviously knew what they were doing here and intended it to be used in that way. But it’s a very clean and clever way to get those results out of what seems like less capable hardware. It feels like a clever hack even though it’s core design.

Apple Graphics Games, by Paul Coletta

This is a book of type-in BASIC programs for Applesoft BASIC. As was common at the time, you could pay extra for a version that had a floppy disk attached that had all the programs typed in for you already. This appears to have earned it a spot in MobyGames as a game title in its own right, with the actual book of listings as some kind of extensive manual. This is the only time I think I’ve ever seen this happen, though given the advertisement scans that MobyGames also has on its site, it does seem like the publisher was presenting them this way. True to that idea, the listings actually do have some cross-linking and a sort of “main menu” program that ties it all together.

The overall organization is interesting. There are 23 listings in the book, but 10 of them are only intended to be run once. These programs are almost entirely DATA statements, and the programs read those statements and save them out as binary blobs that are then BLOADed by the main programs. This is mostly graphics data, with the single largest file being the font. That said, it does also do its best to take proper advantage of the full bitmapped graphics screen, too.

So… how does the software hold up? From a user perspective, I’d say “fair to middling.” The book is quite explicit that these programs are for the Apple II+, and as a result it makes some assumptions about the hardware that do not survive the transition to the Apple IIe or IIc very well. For instance, the IIe has a more complete keyboard, including both square brackets and up-and-down arrow keys. (The II+ only had left and right. This fact explains a great deal, I think, about why Applesoft BASIC is very line-oriented despite looking like it should have a full-screen editor. It also makes the keyboard controls in these games comically awkward for motion about the screen.) It also assumes that non-keyboard input will be managed via paddles instead of joysticks; this results in some amazingly janky input mechanism where the paddle’s range gets divided into 7 regions or so and each region makes the button do something different. This moves from “curiosity” to “farce” once we contend with how the hardware and its relationship to Applesoft BASIC evolved—the X and Y coordinates on the analog joystick’s input are mapped to the paddles of players 1 and 2 respectively, read with PDL(0) and PDL(1) within BASIC. It wasn’t impossible to control some of these games with a joystick—CATCH and LANDER both did just fine—but games like BOXES or POKER are obvious disasters.

From a developer’s perspective, reading through the source code reveals a mixture of clever technique and avoidable pain. Coletta gets Applesoft BASIC to do some things that aren’t quite in its documentation, and while some of the results aren’t pretty, they are still impressive. My two favorite are actually both in MENU.

First, Applesoft BASIC instead offers two different high-resolution graphics modes (accessed with HGR and HGR2), which on the surface differ because HGR has a four-line text mode at the bottom and HGR2 takes up the whole display. True to its name, Apple Graphics Games only really ever usees HGR2 and its full-screen display—but MENU starts in HGR. But there’s another difference between the two modes: they actually live in different locations in memory! With a few clever POKEs, the MENU program convinces BASIC to draw the full display on the other graphics page, then flips over once it’s done to get the entire menu to appear all at once instead of being painstakingly drawn. That’s proper double-buffering.

The other thing it does that I liked was POKEing a special value into otherwise unused memory. The end of every other game in the book checks that location, and if it sees the special value, it reloads MENU the same way the menu program had loaded it, and it all acts like a single seamless program. If, however, you’d just been running them directly from the BASIC prompt, it sends you back to BASIC with no fuss.

These are both pretty cute, and they also don’t really use Applesoft BASIC’s intended features. Coletta was no slouch with those either, though. MENU relies on error interception systems to be able to gracefully handle a user trying to load a program that they didn’t actually type in. Almost all the high-speed graphics rely on the “shape table” facility, and the programs all make good use of DOS 3.3 to share that between them and ensure that programs don’t all start with five minutes of DATA reads.

It also relies on DOS to load in a small machine language routine that provides for beeps of various frequencies and durations. This routine uses roughly the same logic as my first attempt in that genre, and it’s precise enough to power a virtual piano program in the book. This is the only place where the book departs from BASIC, and there’s really no way around this one. On the whole I’m quite impressed with the level of complexity he aimed at and hit.

I am less impressed by the overall implementation discipline, though. The programs overall have an internal structure that varies between “formulaic to a fault” and “not nearly formulaic enough.” There’s a fairly rigid program order, with an opening that GOSUBs to a bunch of initialization routines in turn, then GOTOs to the main loop. There’s a special block of game-specific DATA statements that don’t work as binary blobs. The programs then close with a collection of support routines, and finally that epilogue that returns control to the MENU program if that’s how it was started.

Normally that kind of structure is nice, but it’s very awkward here. It’s not uncommon for the GOTO 100 that starts the program to actually appear on the line immediately before line 100, producing a sort of awkward no-op. These various sections are also generally around the same line numbers irrespective of the actual size of the program, and these line numbers are very low, generally around the 500-700 range. This results in a lot of programs having extremely chaotic and irregular line numbering schemes as various bits of game logic attempt to cram themselves into the space available. Compounding all this, the code within each block is often rather messy, with repeated lines or dead code not at all uncommon. The overall sense here is of code that was written live, iterated upon until it worked, and then printed out as a listing without a final editing or refactoring pass to clean things up.

The support routines in particular are also extremely similar. Normally if I saw a collection of programs where the main logic seemed awkwardly crammed into place, and the initialization and support routines were more strongly uniform, I would suspect the author of relying on an application shell. This is a BASIC program that doesn’t actually do anything but it does define all your support routines and overall operations in advance, so each new project may be begun simply by loading the shell and proceeding from there. There’s a lot of things that need to be done in all the programs here, so a shell would have plenty to cover.

Which makes it baffling that a shell was clearly not used, because these support routines are only similar, not identical. Not only the line numbers but the internal logic is often very slightly different between each version, even though there existed a version in one program that was generic enough to handle all cases. Entirely too many of the programs in this book actually implement the “draw text on the graphics screen” routine twice: once for canned strings read out of DATA statements during initialization, and another for arbitrary strings (usually to print the score out). There’s really no reason these couldn’t be the same routine, and there’s even less reason to keep all your program’s strings in a little table that you copy into a different part of memory each time you start. It would literally be easier to simply use inline string constants.

There is a place where this design wouldn’t be baffling, though: if this were a machine language program and not a BASIC one, a table of your read-only string constants is par for the course and code that handled those would plausibly be different from code that handled arbitrary strings and would very likely be different from code that handled anything numeric (since STR$ won’t be a thing once you’re no longer in BASIC). I kind of wonder if these programs actually started out as BASIC programs before publication.

To the Rescue?

These are the kinds of considerations that often lead me to attempt a type-in rescue, but I don’t think that’s in the cards here. The programs I actually typed in to try out were already basically fine for the end user, after all. I don’t think that there’s much to gain by pushing these from DOS 3.3 into ProDOS or to address IIe-specific issues, and there wasn’t enough compelling about any of them to completely redesign the game engine core to work with proper joysticks or arrow keys.

If I do go take a closer look at Applesoft BASIC (which I’m sure I will, at some point), this will provide a nice foundation of technique to guide my journey, but that’s all.