Title: 8080 RTC Timer
Date: November 27, 2018
Tags: altair hardware programming
========================================

The next thing I was interested in learning on the Altair was hardware
interaction and dealing with interrupts.  The Altair clone has the 88-VI-RTC
included which is a Vector Interrupt board and Real Time Clock.

I decided to play with the Real Time Clock and make a simple timer that can be
set via the "sense switches" on the front of the Altair and displays the
countdown on the high memory address LEDs.  This form of IO is quite crude.  I'm
still pretending I'm in a world without terminals or even teletypes.

The front panel of the Altair has switches and lights for selecting memory
addresses and inputting values.  Since the Altair is 8 bit, but with 16 bits of
memory addressing, the high byte switches and lights are not used for data.  The
Altair can read the high byte switches as input by reading from address 377Q.
Output, though, is not really output.  You can't just write to the lights.  The
trick to make the high byte lights turn on is to access memory at the higher
addresses.  So if we want the left most light to turn on, we can read or write
to address 100000Q and the light will illuminate for that moment.  Rapid,
repeated access makes the lights appear to remain on.  At least a couple of
games took advantage of this trick and allowed for some level of action[0] using
just the lights and switches of the front panel.  You can even play a version[1]
of Pong[1], but actual Pong was much more sophisticated.  This method limits you
to 8 bits of input and although you could use more memory lights for output,
typically you only use 8 bits for output to avoid conflicting with lower memory
access.

I'm going to ignore the Vector Interrupt part of the hardware except to explain
that it allows for 8 prioritized interrupts to be managed.  The CPU only
natively supports a single interrupt to be responded to as they happen.  The VI
board will allow you to manage multiple interrupts and fire them off with a
specified ID and only send another during an interrupt handler routine only when
a higher priority comes in.  I haven't done anything with it yet so I may cover
more details at a later time.

The RTC was part of the same expansion board.  It will produce an interrupt at
regular intervals.  This allows for timing within programs or keeping the time
of day.  The clock operated in two configurable modes.  It could fire interrupts
at line frequency, that is every time the AC power from the wall changes
direction which in the US is at 60Hz, or at a subset of the system clock
frequency.  The system runs at 2MHz but the fastest the RTC will do is 10,000Hz.
The frequency also has a configurable divide rate to slow the actual interrupt
frequency down.  You could divide by 1, 10, 100, or 1000.

The following configurations are possible:

================================================================================
|| Source            || Divide || Divide         || Time Interval             ||
||                   || Rate   || Frequency (Hz) ||                           ||
================================================================================
| Line Frequency     |       1 |              60 | 16.67 milliseconds          |
| (60Hz)             |         |                 |                             |
|                    |---------+-----------------+-----------------------------|
|                    |      10 |               6 | 166.7 milliseconds          |
|                    |---------+-----------------+-----------------------------|
|                    |     100 |              .6 | 1.67 seconds                |
|                    |---------|-----------------|-----------------------------|
|                    |    1000 |             .06 | 16.67 seconds               |
|--------------------+---------+-----------------+-----------------------------|
| 10,000Hz           |       1 |          10,000 | 100 microseconds            |
| system clock)      |---------+-----------------+-----------------------------|
|                    |      10 |           1,000 | 1 millisecond               |
|                    |---------+-----------------+-----------------------------|
|                    |     100 |             100 | 10 milliseconds             |
|                    |---------+-----------------+-----------------------------|
|                    |    1000 |              10 | 100 milliseconds            |
--------------------------------------------------------------------------------

Notice that there is no round second.  Math must be done.  Since I am using an
Altair clone, and not real hardware, the RTC is hard coded to a specific
configuration.  Initially, I thought that configuration was line frequency with
a divider of 10.  That coupled with some sloppy code, meant I never really got
the thing working right the first time.  It wasn't until I looked at it again
recently that I cleaned up my mistakes and it became obvious that the divider
was set to 1.  That means we will get 60 interrupts a second.

Like most things with the Altair (and many computer systems from the time)
information is abundant.  The 88-VI-RTC was sold as a kit so all the parts and
diagrams are in the manual as well as what pins to drive to configure and use
the board including example machine code.  Today everything would be
proprietary, encrypted, and with a license agreement that says you won't even
look at it in a way that might allow you to understand anything about it that
you haven't paid to know.

The code of the timer is as follows:


000     NOP             000 ; Stop loop
001     JMP             303
002             000Q    000
003             000Q    000

; RTC interrupt handler
010     MVI A           076 ; Stop RTC
011             331Q    331
012     OUT             323
013             376Q    376
014     DCR D           025 ; Decrement interrupt counter
015     JNZ             302 ; Jump out if not 60 interrupts
016             026Q    026
017             000Q    000
020     DCR B           005 ; Decrement timer
021     JZ              312 ; Time's up, end
022             000Q    000
023             000Q    000
024     MVI D           026 ; Reset interrupt counter
025             074Q    074
026     MVI A           076 ; Re-enable RTC
027             301Q    301
030     OUT             323
031             376Q    376
032     EI              373 ; Enable interrupts
033     RET             311 ; Return from interrupt handler

; Main program
100     IN              333 ; Read sense switches
101             377Q    377
102     MVI C           016 ; Zero C - low byte of counter
103             000Q    000 ; for display
104     MOV B,A         107 ; Save timer for display in the high byte of BC
105     MVI D           026 ; Initialize interrupt counter
106             074Q    074 ; to 60
107     LXI SP          061 ; Set stack address
110             000Q    200 ; to 000400Q
111             001Q    000 ; for RST to use
112     MVI A           076 ; Enable RTC
113             360Q    360
114     OUT             323
115             376Q    376
116     EI              373 ; Enable interrupts
117     LDAX B          012 ; Display timer on memory lights
120     JMP             303 ; Loop for interrupts
121             114Q    117
122             000Q    000


## Breaking it down ##
# Stop loop #
Address 000 - 003 is a simple loop which we jump to once the counter hits zero.
You could use a HLT instead but that causes all the lights on the Altair front
panel to light up and I wanted the counter to display zero.

# Interrupt handler #
Address 010 - 033 is the interrupt handler.  The RTC will create an interrupt by
sending a RST to the CPU and the interrupt number.  The interrupt number
dictates the address the CPU jumps to for the handler.  Interrupt 0 goes to
address 000, interrupt 1 goes to address 010, interrupt 2 to address 020, etc,
up to interrupt 7 at address 070.  Most hardware used interrupt 7 by virtue of
not sending anything at all (the data lines will go high if there is no data
sent).  The 88-VI-RTC allowed for the use of all 8 interrupts.  I have the RTC
tied to interrupt 1 so the handler needs to be at address 010.

The fixed handler addresses means you only have 8 bytes to fit your handler into
if you have to respond to multiple interrupts.  Typically you do a quick jump
from there to your actual handler somewhere else in memory.  Since I am only
using the RTC, I can put the whole handler in contiguous memory using as much
space as I need.

The handler needs to do a couple of things.  You need to clear the interrupt on
the RTC as it won't clear it itself like other hardware.  Interrupts are
disabled for the CPU automatically after the CPU responds to it.  After we're
done handling the interrupt, we need to reset the RTC and re-enable interrupts.

First thing we need to do is check if a second has gone by yet.  Decrement our
interrupt counter.  If it's zero, it's been a second (60 interrupts), otherwise
jump to re-enabling everything and return from the handler.  After 60
interrupts, decrement the timer first, then re-enable things and return.

# Setup #
Address 100 - 116 are the initial setup steps.  We read the sense switches to
get the number of seconds to count down.  Because the switches allow input of 8
bits, that only gives us a maximum of 4 minutes and 15 seconds for the counter.
We use the BC register pair to display the current timer value by referencing
the high memory.  So we set the low byte in C to zero and save the counter to
the high byte in B.  Set our interrupt counter to 60 in the D register.  Set a
stack address so the interrupts have a place to push the program counter to.  We
use address 000400Q as that is the highest address we can use without causing
the high memory lights to illuminate during stack access and we might as well
grab as much stack space as possible.  The stack gets used in a decrementing
manner and is decremented before being written to so it'll never light up the
light in the high byte.  Last, enable the RTC and enable interrupts.

# Display loop #
We then loop through addresses 117 - 122 displaying the current count on the
lights by reading the memory address stored in register pair BC which will have
the counter in the high byte.  The lights only light up in the moment LDAX is
executed but since we loop back to it between interrupts and the value doesn't
change for a full second, it shows up quite visibly on the front panel.

## Talking to the RTC ##
During our setup, we initialize the RTC by sending it a 360Q.  Each of the bits
have a specific meaning to the 88-VI-RTC hardware.


================================================================================
|| Bit     || Function                                                        ||
================================================================================
| Bit 7    | On to enable the VI board.                                        |
|----------+-------------------------------------------------------------------|
| Bit 6    | On to enable the RTC to generate interrupts.                      |
|----------+-------------------------------------------------------------------|
| Bit 5    | On to "clear the counter network of the clock frequency" meaning, |
|          | to initialize or reset the RTC divider counter.                   |
|----------+-------------------------------------------------------------------|
| Bit 4    | On to reset the RTC interrupt.  Usually reading data from a       |
|          | device that generated the interrupt will clear the interrupt, but |
|          | you must do this manually for the RTC.                            |
|----------+-------------------------------------------------------------------|
| Bit 3    | Should be 0 during initialization, but during operation, when on, |
|          | disables interrupts from the same or a lower level.               |
|----------+-------------------------------------------------------------------|
| Bits 2-0 | Set the interrupt level you're responding to.                     |
--------------------------------------------------------------------------------


So sending the board a 360Q tells it to enable the board, enable the RTC,
initialize (reset) the clock divide counter, and clear the RTC interrupt.  There
should be no interrupt right off the bat, but I set bit 4 anyway as I don't know
what state the hardware has initialized in.

Once an interrupt is thrown by the RTC, we need to manually clear it.  The CPU's
interrupt handling is automatically disabled which stops further interrupts
while we processes one.  Technically, stopping interrupts without stopping the
RTC from generating interrupts runs the risk of missing clock interrupts but
missing a few occasional milliseconds seems better than possibly getting lost in
the stack never able to decrement the counter and end the program.  Our handler
is fast enough to not be a problem.  So we send a 331Q to the hardware.  Bit 7
is on to keep the board enabled.  Bit 6 is on to keep the RTC generating
interrupts (we won't see them anyway).  Bit 5 is 0 to not clear the RTC divide
counter.  We're dividing by 1 so I don't think this makes a difference.  On
greater dividers, say 100, the hardware will count 100 ticks before firing the
interrupt and this bit allows you to reset that count to zero.  We're trying to
keep accurate time, so we wouldn't want to reset the counter in the middle.  Bit
4 is 1 to clear the RTC interrupt.  Bit 3 is on to disable lower priority
interrupts.  Bits 2-0 are set to 001 indicating that we're disabling interrupts
lower than level 1 which the RTC is configured to in the Altair clone by
default.

At the end of the handler, we send a 301Q to keep everything enabled, not clear
the divide counter, not clear an interrupt, and re-enable interrupts below level
1.  Ready for the next interrupt to fire.


[0] https://www.youtube.com/watch?v=ZKeiQ8e18QY
[1] http://altairclone.com/downloads/pong.pdf