Episode 12: Wrap-Up
October is over and so is Retrochallenge 2016/10.
Time to wrap up the project, to draw any conclusions, and to see what we may have learned from it.
Let's wrap this project up. First of all, we succeeded. The program worked out even better than we dared to hope. And we may flatter ourselves to have been October's most productive PDP-1 programmer. Big deal, since we were, at all odds, the only active one.
Seriously, I'm content with the result. Especially, since this isn't only my first complete program for the PDP-1, it's also my first assembler program since when the 6502/6510 wasn't retro. My knowledge of the PDP-1 as a programming platform originates entirely from digging around in Spacewar! code and a few other programming resources that have survived the course of time and are now available again thanks to institutions like the Computer History Museum and bitsavers.org. — First, I was just interested in digigging up some versions of the program that seemed to be lost, then, I found it interesting to have a look at how things were approached for the very first time. Also, we've have a unique chance of peeking over the shoulders of such ingenious programmers as Steve Russell, who had previously made LISP a real programming language, Dan Edwards and his JIT solution, or Peter Samson, who may be dubbed the father of all hackers, not only for his TMRC Dictionary (first edition 1959), which eventually became the base for the Jargon File. — Provided, I had a good start thanks to these outstanding examples.
From what I already knew of the PDP-1, I started with some anticipations into the project:
- Direct access to the machine — no intermediary OS or software, no devices to cope with (aside from the display).
- A straight forwad, simple instruction set.
- An as simple assambler, basically just adding up symbol values and literals in a line, plus simple macros.
- Playing around with universal inderect addressing for some nifty pointers, maybe even some object oriented approaches (using a structure of lists).
- Maybe stepping up to higher levels using some macros.
- Having an eye at runtime, stable frame rates, and memory usage.
- Maybe, if not asked to much, trying not to become lost in
- Hum, let's see, how this self-modifying code thing feels to a modern programmer.
- Try our active muscles on what we have learned to read passively so far.
As it Was — Things to Write Home About
The overall experience was great. The directness in programming while owning the entire machine is outstanding. Everything being based on absolute addresses (no relative offsets and so on) adds to it. "Riding the bare machine" is a cosy and centered state of mind. (It's long since I've been such annoyed by some update popup notices by new-fangled software than while being on this project. So, please, UX, …)
In fact, the experience was that pleasing that I realized at some point, I had entirely given up using macros (but "
random"). Stepping up to any higher levels turned out to be more of an irritation than a convenience.
Another cause for the generally rather direct approaches, I chose, was found in the very subject, simulating a 74xx-logic device in software. As the example device consists of directly wired gates, there was no real use case for indirect approaches, pointers, or any object oriented solutions. Some values are mangled by some routines, some of them serve the input for others, and so on. Just the same, the assembler was for me all about symbolic labels and named locations as provided by assembler variables.
While I did have an eye on the runtime very early on, taking care of any single instruction cycle, this proved of no greater importance while advancing in the project. As illustrated by a quite stable frame rate of about 60 frames per second!
Some of this is owed to the specific instruction set of the machine. No penalty whatsoever for any instruction, aside the extra cycle for any memory access. Especially the variable shift and rotate instructions poved to be very versatile. (Unlike with many other machine, there's no difference for a shift by 9 bit positions as compared to a shift by a single one.)
The speed gained from this is quite important for the aesthetics of the resulting game, as the granularity of any motions on the display is clearly documented by the CRT's long sustain. The aesthetics of this time preserving display technology (its a 3D display, X, Y, and delta t!) posed also some challenges on the algorithmic level, like smoothing any paths of the onscreen objects.
daps and other rather synonymous mnemonics didn't pose any challenges. In fact, I erred about three times. Speaking of errors, the lack of debugging tools wasn't much of a hassle. Most of the errors (typos) were identified by the assembler, and a quick glance at the code revealed the culprit almoust every time. On the other hand, this wouldn't have been possible without modern on-screen editing tools and frequent retries. I still can't imagine how this would have worked out with offline repunchs only.
The single time, I was in need of any debugging tools, was the incident with the static unit offsets for the character outline compiler. While this proved to be not related to the compiler code, still, I probably wouldn't have found out about it without any facility to inspect the object code generated by the routine in memory.
By the way, Im still wondering, how difficult could this be, really?
|oct||set calculator to octal mode|
|400||400||1 << 8 (256) one screen location|
|=||2400||screen offset (×1)|
"Calculators for Dummies" proudly presented by mass:werk.
daps, symbol names being limited to up to 3 characters only (and just a single name space for op-codes and asigned symbols and labels) proved to be a rather unsuspected restriction. Some naming conventions help, but become confusing on the other hand, when followed too strictly. (E.g. prefixing anything related to a longer routine by the same two characters doesn't help in memorization. And, if there are also some related opcodes, you may run out of meaningful permuations soon.)
As for self modifying code, this feels totally natural in regard to the machine and its instruction set. There's no irritation at all. Yes, we could do without it (with some severe penalties in runtime), as illustrated below. We could step up to even higher and higher levels of abstraction, even building a basic OS and a simple programming language from simple macros. (Mind that there are some common macros allready providing basic building blocks.) But there's not much meaning in this on a bare machine of 4K of memory. Really.
/ PDP-1 macros to emulate a cpu stack (nl, 2016) / init the stack pointer define stkinit lac (777700 / stack grows up towards 0777777 dac \tos / top of stack terminate / push a return address and continue at address A / 18 cycles (90 microseconds) define jpsr A dac \act / backup ac lap / transfer contents of program counter (pc) into ac dac i \tos / store it (indirect) as return address idx i \tos / increment it to point to next memory location idx \tos / increment tos law A / load target address (absolute) dac \jpv / set it up in a temporary jump vector lac \act / restore AC jmp i \jpv / jump (indirect) to subroutine terminate / pull a return address and continue there / 16 cyles (80 microseconds) define return dac \act / backup ac law i 1 / decrement tos (tos = -1 + tos) add \tos dac \tos lac i \tos / fetch (indirect) the return address dac \jpv / set up a jump vector lac \act / restore ac jmp i \jpv / jump (indirect) to return terminate / usage example (neither economical nor reasonable) ex1, stkinit lac val jpsr by2 dac res hlt by2, sal 1s return val, 3 res, 0
Heck, we could make this even relocatable code:
/ jump to subroutine, relocatable code, forward only define rjsf A B=A-R / offset to insertion point dac \act / backup ac lap / transfer contents of program counter (pc) into ac dac i \tos / store it (indirect) law B / compute offset + current loc add i \tos dac \jpv / store it as jump vector idx i \tos / increment contents at tos, now return address idx \tos / increment tos lac \act / restore AC jmp i \jpv / jump (indirect) to subroutine terminate / backwards ... define rjsb A B=R-A dac \act lap dac i \tos law i B / load (absolute) as a negative value add i \tos dac \jpv idx i \tos idx \tos lac \act jmp i \jpv terminate
But by this, we would lose al the fun. Nothing compares to the directness conveyed by the architecture of the PDP-1. Even such simple stack-oriented instructions as
pla (6502) alienate you somewhat from a machine. Make it 48-bit words (and, maybe,
IO a second accumulator) and the PDP-1 is a programmer's dream machine.
Memory restrictions weren't of any concern. Even with runtime generated object code of the size of the program itself we still have ample space left in the 4K 18-bit memory. That is, at the very end I was thinking of a message to be rendered at the bottom of the screen during attract mode and added the original character interpreter ("codeword display") found in the assorted subroutines from 1961. At this time, I actually ran out of memory. So, we're doing great without this message, even more, as there isn't any in the original Computer Space.
In the end, all worked out gracefully and beautifully. Some approaches were already lined out by Spacewar! and its code, and I don't know how I would have done without knowledge of it. But there's no way of finding out, because I'm already heavily contaminated. So, I'm resigned to gratefulness.
Did I mention the fantastic runtime speed of ~ 60 FPS already? — A testament to the realtime capabilities of the
If we're allowed to rephrase Elon Musk's argument of the chance of us not living in a computer simulation being one in billions, provided the progress of visual representation and computing powers in video games from Pong to now and its extrapolation into the future, we may put it this way:
Considering the decline in video game graphics and the computing power required in just a single decade, from Spacewar! to Pong, and extrapolating this into the future, we may heavily doubt that there will be electricity at all, 40 years from now.
The source code can be downloaded from the related links section of the emulation page and is public domain as long as a note regarding the original author (me) and the date of the program (2016, as opposed to 1962) are preserved.
Experience the program live, running in an in-browser emulation of the DEC PDP-1 and its Type 30 CRT display:
Usage note: Chrome may not be able to keep up with the frame rate of the emulated program and may lag and skip frames. Please try Safari (on Mac) or Firefox.
Hopes & Outlook
If dreams come true and there's also some footage of it thanks to modern dream capturing technologies, as digital video provides, we'll return on the purpose. Otherwise …
— finis —
… oh, we have another one …
▶ Next: Addendum: All New Pyrotechnics
◀ Previous: Episode 11: It Lives!
▲ Back to the index.
2016-11-02, Vienna, Austria
www.masswerk.at – contact me.
— This series is part of Retrochallenge 2016/10. —