When you setup the a Raspberry Pi as part of a PiDP-11 kit, it boots by default into a test blinkenlights application that makes the lights on the hardware panel flash. I wanted to understand a bit more about how this program works, but although I have some familiarity with assembly language I struggled. So, I decided to work through a number of intermediate steps to figuring out how it worked.
The first question I asked myself was how to get a trivially simple program running, and the trivially simple program I chose was the following:
Load a value into a register
Decrement the value
Loop until the value in the register is zero
Halt
So, below you will find the steps I followed to make that happen.
Step 1: Getting my own program running
The first thing I did was create a skeleton program that I can work on without breaking anything else. In my case, having followed the instructions for installation of software for the PiDP11, the simh and pidp11 software can be found in /opt/pidp11/ on the Raspberry Pi.
Within this folder is the "systems" folder, that contains the various software that is available to run on the PiDP11. In this folder is a file called "selections" that, in my case, looked like this:
0000 idled
0001 rsx11mplus
0002 rsts7
0003 rt11
0004 dos11
0102 211bsd
0105 unix5
0106 unix6
0107 unix7
0113 sysiii
0115 sysv
1000 nankervis
1001 idled
1002 blinky
This file tells the booting PDP11 which "system" to choose and boot. The first column represents the setting on the front panel at the time the device is powered on. The second column is the system to boot for each particular front panel setting. The entries in this second column are the names of subdirectories within the "systems" directory, within which can be found the files and settings to boot a particular system.
The default system, which is booted when nothing is selected on the PiDP11 front panel (or indeed, if a PiDP11 front panel is not connected to the Raspberry Pi), is the "idled" system. I decided to use this system as the basis for my experiment. So, I copied the entire contents of the idled folder to a folder called "decrement1". I then edited the "selections" file to make the "decrement1" system the default:
0000 decrement1
0001 rsx11mplus
0002 rsts7
0003 rt11
0004 dos11
0102 211bsd
0105 unix5
0106 unix6
0107 unix7
0113 sysiii
0115 sysv
1000 nankervis
1001 idled
1002 blinky
I rebooted the Pi and confirmed that nothing had changed, except that now I am running my own copy of the idled program which is located in the decrement1 folder.
Step 2: Pare the program back to the basics
The key file for each system is called boot.ini, which consists of a set of commands that are executed by the simh emulator. I found that the code in the idled boot.ini file required a significant amount of analysis to understand, so for the purposes of writing my simple example program, I deleted most of it. This is what I ended up with:
set cpu 11/70,4M
;set realcons=localhost
set realcons panel=11/70
set realcons interval=8
set realcons connected
D 1000 MOV #100, R0
D 1004 DEC R0
D 1006 BPL 1004
D 1010 HALT
RESET ALL
SET CPU IDLE
D PSW 000340
D PC 001000
I'm not really sure what the first four lines do but I infer from looking at the simh documentation that these are PiDP-11 specific. I won't be discussing these lines any more, but I would like to explain the other lines in more detail.
D 1000 MOV #100, R0
There's a lot to unpack there:
"D" is short for "DEPOSIT" and it instructs simh to put something at a certain location in memory.
"1000" is the location in memory at which something should be deposited. Note that all numbers, unless otherwise indicated, are octal.
"MOV #100, R0" is the instruction to be placed at memory location 1000.
The instruction "MOV #100, R0" means place the octal value 100 into register R0.
Why start my program at memory location 1000? Because the first memory values from 000 to 777 (remember, in octal) are reserved for (a) trap vectors, (b) interrupt vectors and (c) the hardware stack.
D 1004 DEC R0
The next line is another DEPOSIT instruction, to store at memory location 1004 an instruction to decrement register R0.
Here's a question, though; if PDP-11 instructions are two bytes long why does the second instruction go at memory location 1004 and not at memory location 1002? It's because of the "#100" in the first instruction. This is an example of what is known as "immediate addressing" but what it basically means is that the constant value 100 (in octal) is saved in the two bytes immediately following the instruction itself. Therefore the instruction "MOV #100, R0" plus its immediate operand (#100) actually takes up four bytes.
D 1006 BPL 1004
The third line deposits the instruction "BPL 1004" at memory location 1006. This means branch to address 1004 if the result of the previous instruction (i.e. the "DEC R0") was positive.
The DEC instruction may change the value of several flags in the Processor Status Word (known as the PSW), one of which is the "N" (or "Negative") flag. If this flag is set, that indicates that the result of the instruction just executed, in this example the DEC instruction, was negative. The BPL instruction looks at the value of this flag and if the value of the N flag is zero (in other words, the result of the decrement was NOT negative) it will jump to memory address 1004.
D 1010 HALT
The next line deposits the HALT instruction at memory location 1010. 1010 is two bytes after 1006 (remember, octal!!) so this is the next instruction to be executed when the BPL branch test fails, in other words when the N flag is set in the Processor Status Word after the DEC instruction.
This will end the execution of the program.
RESET ALL
So, now that the program is written we want to get ready to run it. This command resets the simulator and all connected devices.
SET CPU IDLE
This command tells the simulator to detect when the virtual CPU is idle. The simulator will not use any host system resources when it is idle.
D PSW 000340
D PC 001000
The final two lines deposit the initial value for the Processor Status Word (PSW) and the Program Counter (PC).
Note that the initial value of the Program Counter is the memory address of the first instruction of the program.
The PSW value sets, amongst other things, the priority of the CPU's execution relative to any interrupts received from external devices (either real or emulated). The value of 340 sets the CPU priority to 7. This is the highest priority possible which will basically mean that the CPU would not be interrupted by any external device interrupts (if there were any devices attached, which there are not in this case).
That concludes the walkthrough of this simple program file.
Step 3: Get the program running
If you now reboot the Pi and reconnect (in my case I ssh to the Pi) you'll see the following console output:
*** Start portmapper for RPC service, OK to fail if already running
*** booting decrement1 ***
*** Start client/server ***
*** RPi 4 detected
PDP-11 simulator V4.0-0 Current REALCONS build Aug 14 2019
Disabling XQ
Searching realcons controller "11/70" ...
Connecting to host localhost ...
sim>
As can be seen in this output, the simulator has loaded the decrement1 system. All of the memory locations have been populated as per the configuration file described in the previous step, but nothing has actually been run yet. From here, we can take a look at different aspects of the simulated PDP-11. For example:
sim> e r0-r5
R0: 000000
R1: 000000
R2: 000000
R3: 000000
R4: 000000
R5: 000000
"E" is short for "EXAMINE", so this command allows us to examine the values of registers R0 to R5. We can see that they all contain zero values.
sim> e PC
PC: 001000
We can also examine the Program Counter (PC) and we can see that the next instruction to be run is at memory location 1000, just as we set it up in the configuration file.
We can execute one instruction, using the step command:
sim> step
Step expired, PC: 001004 (DEC R0)
sim>
This has now executed the first instruction of our program and is telling us that the Program Counter now has the value 1004, and that the next instruction to be executed is "DEC R0". If we take a look at the register values now, we can see that R0 has the value 100:
sim> e r0-r5
R0: 000100
R1: 000000
R2: 000000
R3: 000000
R4: 000000
R5: 000000
Now, if we step again, the value of R0 will be decremented:
sim> step
Step expired, PC: 001006 (BPL 1004)
sim> e r0-r5
R0: 000077
R1: 000000
R2: 000000
R3: 000000
R4: 000000
R5: 000000
The next instruction is the branch if positive (BPL) instruction. Since the result of the decrement was 77 (as can be seen in R0 above), this is positive and therefore the execution will branch back to location 1004 and perform another decrement:
sim> step
Step expired, PC: 001004 (DEC R0)
sim> step
Step expired, PC: 001006 (BPL 1004)
sim> e r0-r5
R0: 000076
R1: 000000
R2: 000000
R3: 000000
R4: 000000
R5: 000000
sim>
This will keep looping around and around until the decrement results in a negative value, at which point the BPL test will fail and the execution will halt. I'll leave out all of the intermediate steps and skip to the end:
sim> e r0-r5
R0: 000002
R1: 000000
R2: 000000
R3: 000000
R4: 000000
R5: 000000
sim> step
Step expired, PC: 001004 (DEC R0)
sim> step
Step expired, PC: 001006 (BPL 1004)
sim> e r0-r5
R0: 000001
R1: 000000
R2: 000000
R3: 000000
R4: 000000
R5: 000000
sim> step
Step expired, PC: 001004 (DEC R0)
sim> step
Step expired, PC: 001006 (BPL 1004)
sim> step
Step expired, PC: 001004 (DEC R0)
sim> step
Step expired, PC: 001006 (BPL 1004)
sim> step
Step expired, PC: 001010 (HALT)
sim> step
HALT instruction, PC: 001012 (HALT)
sim>
Conclusion
OK, so strictly speaking this program will decrement R0 to the value of -1, and that might matter in some circumstances, but it doesn't matter here. The idea was to get a simple program running on the PDP-11 that I could understand and that could help me move forward.
Hope you find this useful!
If you change the "D 1006 BPL 1004" to "D 1006 BNE 1004", then the program will stop with zero in R0 rather than 177777.