The PDP-11 Programmable Clock
While I'm on the general topic of timing and clocks, there is a second clock type that was available for the PDP-11 called the programmable real-time clock. This was a piece of optional hardware that could be purchased and installed into a PDP-11, as opposed to the line clock, which came as standard. Nevertheless, the programmable real-time clock is supported by the simh emulation of the PDP-11, so I thought I'd give it a go.
It is probably worth reading the line clock article before reading this one, if you haven't already.
The programmable clock can operate in a few different modes, but the core of functionality involves counting up (or down) to (or from) a configured value.
How fast can it count?
In the original programmable clock hardware there were four clock speed options:
There was a built in 100kHz clock.
The 100kHz clock was also divided down to provide a 10kHz signal.
The 50/60Hz line clock signal was available as a third option.
There was an option to attach an external clock signal.
In the simh emulated PDP-11, all of these options are available, but with some conditions:
The 100kHz clock rate is available but not recommended.
The 10kHz clock rate is available.
The 50/60Hz line clock rate is available.
The external clock rate is arbitrarily tied to the 10kHz clock signal.
What does it do when the configured value is reached?
There are four main modes of operation that the programmable clock could be used for:
Single interrupt mode: The programmable clock counts up/down to/from a configured value. When the value is reached, a single interrupt is generated and the clock is stopped.
Repeat interrupt mode: The programmable clock counts up/down to/from a configured value. When the value is reached, an interrupt is generated, the clock is reset and the count up/down is repeated. Interrupts are generated each time the count value is reached.
External interrupt counter: The programmable clock can be used to count the number of events on the external clock line. The number of events that have been observed can be read as needed.
Timer/counter: The programmable clock can be used as a non-interrupt timer/counter whereby the clock can be configured to count at one of the configurable rates listed above. The clock is started at a certain moment and it will begin counting. The count value can be read as needed.
How is it configured?
There are three standard device address registers used by the programmable clock:
The Control and Status Register (CSR), found at address 772540, is used to configure the mode of operation of the device.
The Count Set Buffer (BUF), found at address 772542, is used to configure the value to count up/down to/from.
The Counter (CTR), found at address 772544, is used to read the current count from the programmable clock when the device is being used as a counter.
Example of usage
I wanted to implement the same program concept that I used when I was working with the line clock. In other words, write a program that increments the value displayed in the data register once every second, up to a maximum of 200 (octal) seconds after which the program loops back to zero and starts counting again.
When I was working with the line clock, here's the approach I used:
Since the line clock interrupts happen at 50Hz (i.e. 50 times per second), use the R0 register to count up to 50, with the line clock interrupt handler incrementing the value held in R0 each time there is an interrupt.
When the value in R0 reaches 50, increment the value in the R1 register.
If the value of R1 equals the threshold value, in my case 200 in octal, then set the R1 register value to zero, otherwise display the value of the R1 register on the panel.
Note that step 1 above is, in effect, implementation of a counter like the one in the programmable clock. Therefore, I decided to implement the following:
Configure the programmable clock to use the 10kHz clock and count down from 10,000. When the value is reached, generate an interrupt. This should generate an interrupt once every second.
In the interrupt handler, increment the value held in the R0 register.
If the value of R0 equals the threshold value, then set the value in R0 to zero, otherwise display the value of R0 on the panel.
Here is the full code listing:
set cpu 11/70,4M ;set realcons=localhost set realcons panel=11/70 set realcons interval=8 set realcons connected set pclk enabled set pclk 50HZ ; programmable clock interrupt vector D 104 001200 D 106 000340 ; main application D 1000 MOV #1000, SP D 1004 MOV #23420, @#172542 D 1012 MOV #113, @#172540 D 1020 NOP D 1022 CMP R0, #200 D 1026 BEQ 1040 D 1030 MOV R0, #177570 D 1034 BR 1020 D 1040 MOV #0, R0 D 1044 BR 1020 ; programmable clock interrupt handler D 1200 INC R0 D 1202 RTI RESET ALL SET CPU IDLE ; CPU priority is 5 D PSW 000240 D PC 001000
The first four lines are standard boilerplate that are at the beginning of all of my samples, so there is no need to describe these again. Refer back to previous posts.
set pclk enabled set pclk 50HZ
The programmable clock is not enabled by default, so you need to explicitly enable it in your boot.ini file. In addition, you can configure the line clock speed used by the programmable clock, just the same as for the line clock. The default line clock speed is 60Hz but here in Ireland the line clock would have operated at 50Hz so I have set it accordingly (not that it matters for emulation purposes!).
; programmable clock interrupt vector D 104 001200 D 106 000340
This is the interrupt vector for the programmable clock. Whenever the programmable clock generates an interrupt;
The current value of the Program Counter (PC) is pushed onto the stack
The current value of the Processor Status Word (PSW) is pushed onto the stack
The value at memory address 104 is loaded into the PC
The value at memory address 106 is loaded into the PSW
The upshot of these actions is that the processor will jump to the interrupt handling routine, which is found in this case, at memory address 1200 (the value pointed to by memory address 104).
Interrupts can have different priorities and the CPU will only service the interrupt if the current priority of the CPU is less than the priority of the interrupt. The current priority of the CPU is stored in the PSW. Moving the value of 340 into the PSW sets the relevant bits (bits 5-7) and thus sets the CPU priority to the maximum value of 7. This will mean that the CPU will not service any other interrupts until it has completed servicing the programmable clock interrupt and the original value of the PSW is restored.
D 1000 MOV #1000, SP
The first line of the program sets the stack pointer to point at memory address 1000. I have written an entire separate post on setting up the stack pointer so I won't describe it in detail again here.
D 1004 MOV #23420, @#172542
This line moves the value that we want to count down from into the Count Set Buffer (BUF) of the progammable clock. The value 23420 (in octal) is 10000 (in decimal). Since I'm planning on using the 10kHz clock, this means that the programmable clock will count down from this value to zero (and therefore generate an interrupt) once every second.
D 1012 MOV #113, @#172540
This line configures the programmable clock by moving a certain bit pattern, in this case 0ctal 113, into the Control and Status Register (CSR) of the programmable clock.
The CSR bits have the following meanings:
Bit 0 is the RUN bit. When set to 1 it starts the counter running.
Bits 1 and 2 are the RATE SELECT bits. They choose the speed of the programmable clock as follows (bit 1 = 0, bit 2 = 0) selects 100kHz, (bit 1 = 1, bit 2 = 0) selects 10kHz, (bit 1 = 0, bit 2 = 1) selects line clock speed and (bit 1 = 1, bit 2 = 1) selects the external clock input.
Bit 3 is the MODE bit. When set to 1 it selects repeated interrupt mode, when set to 0 it selects single interrupt mode.
Bit 4 is the UP/DN bit. When set to 1 is means that the counter should count up. When set to 0 it means that the counter should cound down.
Bit 5 is the FIX bit. This is used for single clocking mode used as a maintenance aid. This is a write only bit and whenever a 1 is written to it by the program the counter is clocked by one count.
Bit 6 is the INTR ENB bit. This bit is set to allow the programmable clock to generate interrupts.
Bit 7 is the DONE bit. This bit is read only and is set by the progammable clock to indicate that the count up/down to/from the configured value has been completed.
Bits 8-14 are unused.
Bit 15 is the ERROR bit. This bit is read only and is set by the programmable clock to indicate an error condition. Specifically, it is set when the programmable clock is in repeated interrupt mode (i.e. MODE = 1) and a second interrupt is generated before the preceding interrupt has been serviced by the CPU.
The value of 113 (binary 0 000 000 001 001 011) therefore means;
Start running the programmable clock (RUN = 1)
Set the clock speed to 1okHz (RATE SELECT = 01)
Generate repeated interrupts (MODE = 1)
Count down from the configured value (UP/DN = 0)
Enable generation of interrupts (INTR ENB = 1)
D 1020 NOP
Now we have a NOP instruction, which is a stylistic choice that I have made. I chose to develop this program in this way so that there is an explicit instruction location to branch back to. You could leave this line out and branch to the next instruction if you prefer.
D 1022 CMP R0, #200 D 1026 BEQ 1040
The next two lines compare our current R0 value to the (arbitrarily selected) upper limit value of 200. If we have reached our limit then we branch to address 1040 where we reset the value of R0 back to zero and start again.
D 1030 MOV R0, #177570 D 1034 BR 1020
If R0 has not yet reached the upper limit value then we display the value contained in R0 to the panel and branch back to the top of the loop again.
D 1040 MOV #0, R0 D 1044 BR 1020
If R0 has reached the upper limit value then we reset the value in R0 to zero and branch back to the top of the loop again.
; programmable clock interrupt handler D 1200 INC R0 D 1202 RTI
This is the code that handles an interrupt from the progammable clock. All we do is increment R0 and then return from the interrupt.
RESET ALL SET CPU IDLE ; CPU priority is 5 D PSW 000240 D PC 001000
Finally, we reset all devices and enable CPU idle detection in the emulator.
We set the initial PSW to a value of 240, which sets the initial CPU priority to 5. The programmable clock generates interrupts of priority 6, so the CPU will handle the programmable clock interrupts.
We set the intial value of the PC to 1000, the beginning of the program.
And that's it. To run the program start the PDP-11 emulator with this boot.ini file and type "go".
Hope you found this interesting!
The reference that I used while developing this post was the (pretty confusing) KW11-P programmable real time clock manual.