SBC6120 PDP-8SBC6120 PDP-8
“Deep Thought” for the SBC6120/FP6120

A “blinkenlights” program for Bob Armstrong's beautiful PDP-8 recreation.
DeepThought Source Code

Being a rather simple “blinkenlights” program, this toggle toy is the smaller of the two. (The puzzle setup for LightsOut wound up becoming rather complex.) So this is probably a useful example for someone who is mildly curious about the look of PDP-8 assembly language source code.

Since most SBC6120/FP6120 owner interest will likely be in equipping systems with this front panel utility, the source code can be skipped by jumping down to the "Installing DeepThought into your SCB6120/FP6120" section below.

PDP-8 / SBC6120/FP6120 “DeepThought” Source Code

/ "Deep Thought" | The Ultimate PDP-8 Front Panel "Blinkenlights" Program
/                | SBC6120 version assuming 32K RAM, KL8, and no EAE present.
/ This reads the front panel switch register to determine the speed and speed
/ variation of the flashing lights. It implements a simple but sufficient linear
/ congruential pseudo random number generator (LCPRNG) to generate visually
/ unpredictable values for the AC and Memory Address to contain while we wait
/ for the next serial port (KL8) interrupt. (See detailed explanation below.)
/                 Contemporary PDP-8 FREEWARE written and placed
/                  into the PUBLIC DOMAIN by Steve Gibson, 2009

/ Implementation notes - This program uses an SBC6120/FP6120-specific feature
/ that will not work on a regular PDP-8: The program manually takes over control
/ of the data lights.  This is done because the 30hz sampling of the accumulator
/ sometimes catches the AC at a bad time causing the display to flicker annoyingly.
/ This would NOT be a problem on a real PDP-8, since its AC display is not being
/ sampled and held.

CALLBIOS=6206           / Call SBC6120 BIOS function, function code follows call
LIGHTS=12               / BIOS function to take over the data display lights
/ Since we don't have programmatic control over the memory lights, if we want to
/ control them it's necessary for us to actually GO somewhere and remain there.
/ This program functions by filling fields 1-7 (not field 0) with nothing but
/ "JMP ." (jump to yourself) instructions. Thus, when we jump somewhere we loop
/ at that single location until a serial port interrupt brings us back to location
/ zero.  At that point we determine whether we've waited long enough, and either
/ immediately return to waiting at the same location, or generate new randomness.

RTF=6005                / This "Restore Flags" opcode, which we are defining here,
                        / is unknown to the PAL-8 assembler because it is only
                        / valid when the PDP-8 is equipped with extended memory.
                        / It is used for cross-field interrupt returns by loading
                        / fields of the AC into the extended memory field regs.
                        / whose effects are deferred until the next jump. inst.

*0                                      / load at loc 0
IntReturn,      0                       / interrupt return loc
                / this is our super-minimal inner loop to keep the address
                / lights as fully on or off as possible. We do the bare minimum
                / here in order to get right back to our waiting location

                ISZ     AccumLow        / bump our inner-loop counter & probably
                JMP     Continue        / get back to waiting immediately...
                CLA OSR                 / get the current switch register
                CIA                     / negate it for a change detection compare
                TAD     LastSwitchReg   / for mid-delay changes in the timing
                SZA                     / if no change, just keep delaying
                JMP     ChangeLights    / the register changed, abort this cycle
                ISZ     AccumHigh       / bump our outer-loop counter
                JMP     Leave           / restore values and resume waiting
                / either our time is up for changing the lights (with both the
                / inner "AccumLow" loop and the outer "AccumHigh" loop having
                / wrapped around to zero) ... *OR* the user has changed the
                / switch register to change the blinking characterization, in
                / either event, we compute a new set of lights and light-delay.
ChangeLights,   JMS     GetRand         / get a new random value
                DCA     IntReturn       / save new waiting location. we will
                                        / return indirectly to this location
                                        / once everything else is set up
                JMS     GetRand
                CALLBIOS                / call the SBC6120 BIOS
                LIGHTS                  / to set the data register lights
                1                       / under our program's control
                JMS     GetRand         / establish the next instruction field
                AND     CInstField      / retain only bits 6-8 (IF)
                SNA                     / if we're not in page 0
                TAD     C0010           / collision possible, so set to field 1
                TAD     C0200           / turn on the Interrupt Enable bit
                SKP                     / skip over 0026, our startup entry. Our
                                        / funky SBC6120 OS/8 boot requires this
                JMP     RunFromHere     / fixed 0026 entrypoint, but it's easily
                                        / accommodated with a SKP instruction
                DCA     LoopField       / set next loop field to 4

                OSR                     / read the switches
                DCA     LastSwitchReg   / save for mult & mid-delay testing
                TAD     LastSwitchReg   / get the switch reg
                AND     C0077           / mask the six speed control bits
                IAC                     / make sure it's not zero
                DCA     SpeedControl    / save for the random # multiply
                TAD     LastSwitchReg   / get the switch register again
                BSW                     / swap 6-bit halves
                AND     C0077           / keep the lower 6 bits
                DCA     SpeedVariation  / save the amount of variation
                JMS     GetRand         / pickup another 12-bit random number
                AND     SpeedVariation  / retain only the lower specified bits
                IAC                     / now we have AC = 1 to ...
                JMS     MUY             / Multiply: AC <- LSB (AC * [.+1])
SpeedControl,   0                       / the multiply subroutine's multiplicand
                CMA                     / 1's compliment for an up-counter
                DCA     AccumHigh       / save the new outer-loop delay count

Leave,          TAD     CBaudDelay      / reset our inner-loop counter
                DCA     AccumLow
Continue,       TLS                     / clear interrupt and start sending
                                        / a (nother) character out to the TTY
                TAD     LoopField       / put the RTF's memory field into AC
                RTF                     / set the restored pending field & ION
                JMP I   IntReturn       / return to the place we'll be waiting
                                        / with memory field and location set
/ GetRand ----------------------------------------------------------------------
/               This is the simplest way I know of to generate highly random
/               looking 12-bit values.  It's a Linear Congruential Pseudo Random
/               Number Generator (LCPRNG).  Each time it's called, it evaluates
/               the expression:  NextRand = LastRand * 5545 + 541 (all octal)
GetRand,        0                       / subroutine return
                TAD     LastRand        / get the last PRNG value
                JMS     MUY             / multiply by the following constant:
                5545                    / 2917 base 10 - LCPRNG multiplicand
                TAD     CRandAdd        / sum in our LCPRNG addend
                DCA     LastRand        / save this for next time
                TAD     AccumHigh       / return the HIGH 12-bits as our result
                JMP  I  GetRand         / return the AC to the caller

/ MUY --------------------------------------------------------------------------
/               This is a full 12x12 multiply, needed because the HD6120 PDP-8
/               emulation chip used by the SBC6120 inexplicably lacks the EAE
/               "Extended Arithmetic Element" multiplier.  Annoying as this is,
/               it does mean that these "ToggleToys" will be usable on ALL real
/               PDP-8 systems, including those without EAE's.
/  On Entry:    AC contains Multipler & the word after the call has Multiplicand
/    Return:    least significant 12-bits in AC, most significant in AccumHigh.

MUY,            0                       / subroutine return
                DCA     Multiplier      / save the multiplier for shifting              
                TAD     C7764           / setup our -12 loop counter
                DCA     PhaseCount
                DCA     AccumLow        / clear our 24-bit results accumulator
                DCA     AccumHigh       

MuyShift,       TAD     Multiplier      / get a bit from the multiplier
                CLL RAL                 / move the high-bit into LINK
                DCA     Multiplier      / put the updated multiplier back
                SNL                     / we do need to add-in the multiplicand
                JMP     Iterate         / no multiplicand add-in
                TAD  I  MUY             / add the multiplicand into accumulator
                TAD     AccumLow        / this *may* overflow, clearing the LINK
                DCA     AccumLow        / either way, put the updated low 12 back
                SNL                     / if LINK is still '1', no overflow
                ISZ     AccumHigh       / bump the high-half if we carried out
Iterate,        ISZ     PhaseCount      / see whether we've done all 12 bits
                JMP     Shift24         / not done, so shift and iterate again

                CLL CLA                 / return the lower 12-bits in AC
                TAD     AccumLow
                ISZ     MUY             / return to the instruction after multiplier
                JMP  I  MUY
Shift24,        TAD     AccumLow        / get the lower 12-bit half
                CLL RAL                 / shift it left, high bit into LINK
                DCA     AccumLow        / put back the new low half
                TAD     AccumHigh       / get the upper 12-bit half
                RAL                     / shift it left, LINK into low bit
                DCA     AccumHigh       / put back the new high half
                JMP     MuyShift
/ At program startup we call the SBC6120's BIOS to inform it that we'll be
/ taking over display of the data lights and to shut-down its 30hz sampling
/ of the AC.  We then fill the seven 4K word memory fields (1-7) with nothing
/ but "JMP ." (jump to yourself) instructions so that when we jump out to any
/ such location we'll simply remain there (with the memory address lights frozen)
/ until we're yanked back by the completion of the serial port character that we
/ always start sending just before we jump out to never-never land.

RunFromHere,    CLA
                DCA     LastRand        / setup the start of our filling

                TAD     CFirstDatField  / init to data field zero 
                DCA     NewDataField
NewDataField,   CDF     10              / set our new storage target data field
                CLA                     / (need to clear after switching fields)

                / fill memory with current page "JMP ." instructions

FillMem,        TAD     LastRand        / get the next target location
                AND     C0177           / keep only the target loc's page bits
                TAD     CJmpCurrentPg   / convert into a "JMP ." instruction
                DCA  I  LastRand        / save the "JMP ." inst @ TargetLoc
                ISZ     LastRand        / bump to next target location
                JMP     FillMem
                / we've wrapped into the next 4K word field

                TAD     NewDataField    / get the current data field
                TAD     C0010           / bump to the next data field
                DCA     NewDataField    / save the next data field
                TAD     NewDataField    / get back the new data field
                AND     CInstField      / did we wrap out of the last one?
                SZA                     / if zero, we're all done
                JMP     NewDataField    / let's keep filling in the next field
                CDF     00              / and leave us back at data field 0
                JMP     ChangeLights    / ... and off we go ...
/------------------------ Initialized Constant Values -------------------------

C0010,          0010                    / constant 0010 for data field inc
C0077,          0077                    / constant 0077 for masking 6-bits of SW
C0177,          0177                    / constant 0177 for masking page bits
C0200,          0200                    / constant 0200 for RTF's ION bit
C7764,          7764                    / constant 7764 for -12 multiply counter

CInstField,     0070                    / GTF/RTF instruction field bitmask
CRandAdd,       541                     / 353 base 10
CJmpCurrentPg,  5200                    / inst for jmp to current page loc 0
CFirstDatField, CDF     10              / "change to data field 1"

CBaudDelay,     7770                    / baud rate compensation value
                                        / 7740 - 38400
                                        / 7760 - 19200
                                        / 7770 -  9600 baud
                                        / 7774 -  4800
                                        / 7776 -  2400
                                        / 7777 -  1200 or slower

/-------------------------- Uninitialized Variables ---------------------------

SpeedVariation, 0                       / 6-bits of switch register
PhaseCount,     0                       / our multiplier-shift counter
AccumLow,       0                       / low 12-bits of 12x12 mult
AccumHigh,      0                       / high 12-bits of 12x12 mult
Multiplier,     0                       / temp used by multiplication
LoopField,      0                       / the memory Field while waiting
LastRand,       0                       / our previous random value
LastSwitchReg,  0                       / the last SW Reg reading

The assembled listing for this source code, DeepThought.lst, is also available.

Due to the size of the OS/8 operating system, and the fact that three hours of 9600 baud transfer is required to move it in hex ASCII format from a PC to the SBC6120, I wrote the OS/8 Windows utilities to make that process instantaneous by allowing the SBC6120's IDE drive to be attached through any means available to a Windows system, and to then perform a raw physical transfer of the OS/8 partition data to the SBC's drive.

However, the “DeepThought” and “LightsOut” toggle toys, the custom boot sector, and the sector read/write utilities are all short enough to be feasibly entered through a terminal console. This also means that there is no need for a Windows-centric approach, so Apple Mac and Linux/Unix users will not be disadvantaged.

Installing DeepThought into your SCB6120/FP6120

The following instructions will allow you to deposit the “DeepThought” program in RAM, then write it to any one or more IDE drive partitions of your choosing. Then the modified multi-boot sector will allow you to load and run the program any time you wish using only the front panel switches. This code was developed on SBC6120's having an attached eight gigabyte drive, yielding more than 4300 (octal) partitions. So, for ease of switch register use, the toggle toys were written to partitions 4000 and 4001 (octal). Before proceeding, you should determine where — into which partition(s) — you wish to write these toggle toys. (You should probably have already installed the custom multi-boot sector before installing the “DeepThought” and “LightsOut” toggle toys since their subsequent loading and running requires the multi-boot sector.)

If the SBC6120 is currently running (the front panel RUN light is illuminated), briefly lower and raise the front panel HALT switch to return control to the BIOS and console. Establish communication with the BIOS so that pressing “Enter” on your terminal or terminal emulator returns the BIOS '>' prompt.

Begin by either resetting the SBC6120 or performing a master reset (mr) command to bring the SBC6120 into a known state:

Then clear all of memory. This will take a few seconds, during which the front panel's address register will count all the way up to 77777 as zeroes are being written into the PDP-8's RAM:
You can verify that memory has been cleared by displaying the first sector's-worth of RAM with the examine memory (e) command. You'll receive a nice block display of zeroes, not shown here:
>e 0-377
Now we need to deposit the DeepThought program into RAM. The commands to do this are a series of deposit (d) commands shown below. You can enter each line manually, but terminal emulation programs often understand “copy & paste” semantics which make copying the commands from this page and pasting them into the terminal for sending to the SBC6120 much quicker, easier, and less error prone.

If you are a Windows user, the free, open source, and very nice “Tera Term” terminal emulator can be used. With Tera Term, the keystroke combination “Alt-V” will paste the Windows' clipboard into the terminal window while simultaneously sending it to the SBC6120, exactly as if it had been manually entered. (Here is a locally archived copy in case the file's home site ever disappears:

The following series of SBC6120 deposit (d) commands will place the DeepThought program into SBC RAM:
>d 00000 0000,2165,5053,7604,7041,1172,7440,5012
>d 00010 2166,5051,4057,3000,4057,6206,0012,0001
>d 00020 4057,0156,7450,1151,1154,7410,5124,3170
>d 00030 7404,3172,1172,0152,7001,3046,1172,7002
>d 00040 0152,3163,4057,0163,7001,4067,0000,7040
>d 00050 3166,1162,3165,6046,1170,6005,5400,0000
>d 00060 1171,4067,5545,1157,3171,1166,5457,0000
>d 00070 3167,1155,3164,3165,3166,1167,7104,3167
>d 00100 7420,5107,1467,1165,3165,7420,2166,2164
>d 00110 5115,7300,1165,2067,5467,1165,7104,3165
>d 00120 1166,7004,3166,5075,7200,3171,1161,3130
>d 00130 6211,7200,1171,0153,1160,3571,2171,5132
>d 00140 1130,1151,3130,1130,0156,7440,5130,6201
>d 00150 5012,0010,0077,0177,0200,7764,0070,0541
>d 00160 5200,6211,7770
Verify the accuracy of the data you just entered by asking the BIOS to compute the checksum (ck command) of the first two PDP-8 pages of RAM, locations 00000 through 00377:
>ck 0-377
You should receive the reply:
Checksum = 3116
Next, we need to write these first two pages of PDP-8 RAM to non-volatile storage so that we can reload and run it at any time with just a few flips of our front panel switches. The partition writer (PartWrite) code given below is quite short. Its source code and documentation can be found on the custom OS/8 boot sector page. So, as before, deposit the partition writer into RAM (starting at location 00400) using the following sequence of SBC6120 BIOS deposit (d) commands:
>d 00400 7604,7450,7402,3223,6206,0010,0000,0423
>d 00410 4000,0027,0001,6206,0004,4207,0000,0000
>d 00420 7430,7402,7402
With the partition writer code in RAM at location 00400, we're ready to write DeepThought to its own partition. FIRST set the front panel data switches to the address of the target partition. If you have a very large drive, you might use something like 4000, but 0007 or 0010—or whatever—would be fine too). Verify that the front panel's “HALT” switch is UP (not enforcing a halt), then, with the front panel switches set to the target partition, start the partition writer with the BIOS start (st) command:
>st 0400
Note that to prevent any chance of inadvertently overwriting partition zero, where most users will have installed the OS/8 operating system, the little partition writer will not write to partition zero. So if the switches were inadvertently set to 0000, you will receive:
?Halted at 00403
PC>0403 PS>1000 AC>0000 MQ>0000 SP1>0000 SP2>0000

(That's NOT what you want.)

With a non-zero partition number set into the switches, you should receive:
?Halted at 00423
PC>0423 PS>1000 AC>0000 MQ>0000 SP1>0000 SP2>0000
You can (and should) verify that the two-page sector was written to the IDE drive by dumping the target partition's first sector with the following command, where <partition> is replaced with the number of the partition where the sector was written:
>dd <partition> 0 1
So, for example, assuming that “DeepThought” had been saved into partition 4000, you would give the following command and receive the following dump:
>dd 4000 0 1
0000.000/ 0000 2165 5053 7604 7041 1172 7440 5012
0000.010/ 2166 5051 4057 3000 4057 6206 0012 0001
0000.020/ 4057 0156 7450 1151 1154 7410 5124 3170
0000.030/ 7404 3172 1172 0152 7001 3046 1172 7002
0000.040/ 0152 3163 4057 0163 7001 4067 0000 7040
0000.050/ 3166 1162 3165 6046 1170 6005 5400 0000
0000.060/ 1171 4067 5545 1157 3171 1166 5457 0000
0000.070/ 3167 1155 3164 3165 3166 1167 7104 3167
0000.100/ 7420 5107 1467 1165 3165 7420 2166 2164
0000.110/ 5115 7300 1165 2067 5467 1165 7104 3165
0000.120/ 1166 7004 3166 5075 7200 3171 1161 3130
0000.130/ 6211 7200 1171 0153 1160 3571 2171 5132
0000.140/ 1130 1151 3130 1130 0156 7440 5130 6201
0000.150/ 5012 0010 0077 0177 0200 7764 0070 0541
0000.160/ 5200 6211 7770 0000 0000 0000 0000 0000
0000.170/ 0000 0000 0000 0000 0000 0000 0000 0000
0000.200/ 0000 0000 0000 0000 0000 0000 0000 0000
0000.210/ 0000 0000 0000 0000 0000 0000 0000 0000
0000.220/ 0000 0000 0000 0000 0000 0000 0000 0000
0000.230/ 0000 0000 0000 0000 0000 0000 0000 0000
0000.240/ 0000 0000 0000 0000 0000 0000 0000 0000
0000.250/ 0000 0000 0000 0000 0000 0000 0000 0000
0000.260/ 0000 0000 0000 0000 0000 0000 0000 0000
0000.270/ 0000 0000 0000 0000 0000 0000 0000 0000
0000.300/ 0000 0000 0000 0000 0000 0000 0000 0000
0000.310/ 0000 0000 0000 0000 0000 0000 0000 0000
0000.320/ 0000 0000 0000 0000 0000 0000 0000 0000
0000.330/ 0000 0000 0000 0000 0000 0000 0000 0000
0000.340/ 0000 0000 0000 0000 0000 0000 0000 0000
0000.350/ 0000 0000 0000 0000 0000 0000 0000 0000
0000.360/ 0000 0000 0000 0000 0000 0000 0000 0000
0000.370/ 0000 0000 0000 0000 0000 0000 0000 0000
The “4266” at the end is a checksum of the block just dumped, and it (and everything else) should match what's shown above.

You're all done!
You now have the “DeepThought” program safely written, and verified, to an IDE drive partition of your choosing. If you have already installed the modified FlexBoot sector, you can fire up the “DeepThought” toggle toy by setting the target partition into the switch register then depressing the front panel “BOOT” switch twice in succession.

Have fun enjoying all of the variations made possible
by DeepThought's characterization switch settings.

If your serial terminal is NOT set for 9600 baud, you will probably want to make a change in the program: DeepThought gets its underlying timing from serial terminal interrupts. It needs to do this so that it's able to spend most of its time idling out at various locations in RAM waiting for an interrupt to bring it back to the program to see whether it has waited long enough.

If you look near the end of the source code you'll find a label CBaudDelay ('C' stands for Constant). This is an inner loop “up-counter” whose initial value can be tuned to fit the baud rate of individual SBC6120 serial ports. To make it easy to find, the counter initialization value is the last non-zero word of the program. You can see it's default value of '7770' in the dump block above  . . . at location 0162 of the program. You can also see it at the end of the program's file listing. The source code, above, shows which values can be used to compensate for specific baud rates.

This ZIP file (352kb) contains all of the recently-written bits and pieces of
SBC6120 & FP6120 PDP-8 code for DeepThought, LightsOut, the custom OS/8
boot sector, and other PDP-8 code snippet utilities I created for this project.

GRC's PDP-8 Pages:
CLICK HERE to learn how YOU can acquire & build
one of these complete PDP-8 systems for yourself !!!

Jump to top of page
Gibson Research Corporation is owned and operated by Steve Gibson.  The contents
of this page are Copyright (c) 2014 Gibson Research Corporation. SpinRite, ShieldsUP,
NanoProbe, and any other indicated trademarks are registered trademarks of Gibson
Research Corporation, Laguna Hills, CA, USA. GRC's web and customer privacy policy.
Jump to top of page

Last Edit: Jun 30, 2012 at 19:05 (663.00 days ago)Viewed 6 times per day