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