Programmable Systems Interface TMS 9901

From Ninerpedia
Jump to navigation Jump to search

The Programmable System Interface (PSI) is a circuit that is used on the TI-99/4A main board, on some expansion cards, and on the Geneve.

The designation of the PSI is TMS9901, and this is the name that most users will be more familiar with.

TODO: Add text

Features

Package

Interrupt mode

In interrupt mode, bits 1 to 15 are used to set the interrupt mask (write), or to query the interrupt inputs (read). That is, the meaning of reading and writing the bits is indeed different.

This schema shows how the interrupt inputs are used.

Counter mode

Besides the I/O capabilities, the PSI offers a "real-time" clock. This should not be understood as a precise timer with seconds, hundreds of seconds or other but as a countdown timer that is decremented every 64 CPU clock cycles.

This schematic is taken from the Osborne book, redrawn by me in LibreOffice (and with added Load Buffer).

Clock9901.png

Using the timer

There are basically two ways of using the countdown timer:

  • Interval timer: Set the counter to a value; wait until the counter reaches zero. The PSI can be set up to raise an interrupt at that instant.
  • Event timer: Set the counter to a value; do something and then read the counter value.

The counter has 14 bits; that is, its maximum value is 0x3FFF (16383). The counter is decremented every 64 clock cycles, and for the TI-99/4A and the Geneve with a clock cycle time of 333ns, this means that the counter resolution is 21.333 µs. The whole 16384 steps take 0.3495 seconds (349.5 ms).

When the counter reaches 0, it is reloaded from the load buffer, and counting continues.

It is essential to understand that the counter always counts when the load buffer contains a non-zero value. That is, it even counts when we are not in clock mode.

Setting the timer

In order to set the load buffer and so to define the maximum counter value, we must switch to clock mode. This is achieved by setting bit 0 of the PSI to 1. In the TI/Geneve systems, the PSI is mapped to CRU base address 0, so we can do this

CLR  R12
SBO  0

to enter clock mode. Using SBZ 0 returns to interrupt mode.

The effect of switching to clock mode is that the load buffer becomes accessible by the CRU addresses 1 to 14. Supposed that we want to load the buffer with the value 0x00FF, we may use these lines:

 CLR  R12
 SBO  0
 LI   R1,>00FF
 INCT R12
 LDCR R1,14

TODO: Check number orientation

As we see, we must change the CRU base. The LDCR operation always starts at the address defined in R12; if this is not 0, we have to set it here. Note, however, that the CRU address is always half of the R12 value, since the rightmost bit, A15, is not part of the address. If we want to start at bit 1, we must increase R12 by 2. This is, by the way, done wrong in several places in the literature where R12 is only set to 1.

There is no need for two separate CRU operations. We can combine the SBO and the LDCR to one operation if we define the rightmost bit of R1 as the bit 0 and the following bits as the timer value.

 CLR  R12
 LI   R1,>01FF    (0000 0001 1111 1111)
 LDCR R1,15

Do not write all 16 bits. The last bit will change bit 15 of the PSI, which is the /RST2 bit. When we are in clock mode, writing a 0 to the /RST2 bit resets all I/O ports. This means that all pins are set to inputs, and devices that rely on them as outputs may be turned off unintentionally.

Reading the timer

We are interested for the current value of the timer at some occasions. However, since the counter is constantly changing (every 64 clock cycles), reading from the active counter may deliver a wrong result. Think, for instance, about a value 0x2000, which is decremented after we read the first bits, we may end up with 0x2FFF (2 from 0x2000, FFF from 0x1FFF).

For this reason, the timer value is copied to a read buffer. This happens at every 64th clock cycle, when the counter is also decremented, but only when we are not in clock mode.

To read the buffer, we must set the PSI to clock mode first, which freezes the counter value in the read buffer. Then we can read the counter using STCR.

 CLR  R12
 SBO  0
 STCR R1,15
 SRL  R1,1
 SBZ  0

As we see, register 1 will contain bit 0 in its rightmost bit, so we shift R1 by one position to move it out.

Do not forget to return to interrupt mode, or the buffer will not receive further updates.

The weird S0 input

There is one peculiarity with the PSI. When the input line S0 is set to 1, the clock mode is left temporarily - until S0 is 0 again, and bit 0 is 1. If bit 0 is 0, this has no effect, as the clock mode is never entered.

What is the reason for this?

TI obviously considered use cases where the PSI remains in clock mode for longer times, while the I/O ports must still be operated. The I/O ports are available on bits 16-31, which means that the S0 input is 1 for these addresses. So when we do a SBO 16, this is not related to the timer value, and thus should control the first I/O port. To allow this, the chip returns to interrupt/port mode for this operation, and when S0 is reset, returns to clock mode.

In the TI console, the S0-S4 inputs are directly connected with the address bus lines A10-A14. This has a strange side effect: Whenever address bus line A10 changes to 1, the clock mode is left temporarily, and this also happens for ordinary memory accesses.

Suppose that we are in clock mode (SBO 0). In this mode, the read buffer should not be updated, so when we read it, we should get the same value every time, although the real counter value is different. But this is difficult to achieve: If our program uses a memory address where A10 is 1 for some instruction, loading this instruction into the CPU will set A10 to 1, and thus leave clock mode, which updates the counter.

Therefore, you may have the impression that the read register continues to be updated even though you set bit 0 to 1. But this is only happening because your program extends over a location where A10 is 1. So if you want to prove that in clock mode the read register remains fixed, your program must avoid all memory locations where A10 is 1.

Safety precautions