There are two serial ports and only their physical interfaces are implemented in hardware. This means everything above layer one has to handled by software in realtime. There's no shift registers to hand off parallel data, only single bits in the Ei and Eo registers for the data, control, and clock signals.
Along with the video timing and wavetable synthesizer, the hardware abstraction layer also implements a virtual UART (vUART). This is also an optional feature and is controlled in a similar way to the audio using a serial mode.
Note that the vUART only handles one type of interface at a time. This is possible because both interfaces implement hardware flow control: The RS-232 uses RTS/CTS flow control and the PS/2 interface can be inhibited by grounded the clock via a DTL open-collector NAND gate (shown above).
The PS/2 device (in this case keyboard) is required to buffer data while the clock input is grounded. The fastest typist might get to 15 keystroke per seconds, so there's no need to scan the keyboard faster than that. The slowest PS/2 clock would be 10kHz and a make/break key stroke sequence would require 33 bits of data. This would only require a 5% duty cycle (33/667) for the keyboard to keep up.
The rest of the time can be used for the RS-232 interface. This runs at the same speed as the virtual process cycle, so around 9600 baud. The serial link would need to be interrupted after about 600 cycles to scan the keyboard, limiting the longest sequence of data to around 64 bytes. Sine the keyboard is scanned 15 times per second, then the maximum data rate is 960 bytes/second.
The clock synchronization is handled through sampling and state machines. The maximum clock speed for the PS/2 interface is 16.7kHz, placing the Nyquist frequency at 33.3kHz. This is close to the horizontal scan frequency of 38.4kHz and it makes sense to sample the serial ports during the horizontal sync period. This sampling takes place regardless of the serial mode and is just part of the fixed overhead making up the sync cycle.
A custom ALU function is used to sample the serial ports and drive a state machine. The ports appear on the upper 8 bits of the Ei register and the state machine uses a single byte stored in the zero page. There are two ports, so the state machine byte is broken in to two nibbles (since they are independent variables). Each state machine nibble is further broken down as two 2-bit components: State (4 possible values) and Data (up to 2 bits). The state and data values are updated on each line of the process cycle
The state machine of the PS/2 interface is driven by the PS/2 clock input. This can count up to 4 transitions of the clock value, or two complete clock cycles. This would be the most transitions possible within a singe process cycle and the result would be two bits of data received. The two data bits act as a shift register and the next PS/2 data bit will be sampled on the high to low PS/2 clock transition. The number of bits received can be determined by looking at the state before and after the process cycle. Either 0, 1 or 2 bits are received and this data is then shifted in to a larger register for processing during the keyboard scan cycle.
The state machine for the RS-232 port is changed on every line, for up to 4 lines. This state is used to determine when to sample the RX data during the process cycle. The two data bits store the previous value of the RX line and the final sampled value of RX. The problem to solve here is the potential drift in the data transition due to the clocks being slightly different. The state is reset to 0 when the RX value changes from the previous to the current value. The value of the RX input is sampled when the state machine reached 2. This way the sample is made approximately half way between the implied baud-rate clock edge.
The RS-232 transmit is the simplest part of the vUART. The data is transmitted at the rate of the virtual process cycle one bit at a time. The data is shifted out of a register in the zero page and the TX bit is set or reset depending on the MSB (a single stop bit is added before the next cycle). In addition, the data is only transmitted if the CTS line is low, indicating the DCE is ready to receive the data. The result is a data stream at 9593 baud. This is close enough to 9600 to not be a problem (only 730ppm slower).
Data can also be transmitted over the PS/2 port. There isn't a bit available in the Eo register to provide a simple PS/2 TX line, so a trick is used to create the extra bit needed. There's a spare bit in the scan register that is repurposed to sample the H-blank state. Loading the scan register starts the H-blank period, so the sampled H-blank value is always low. This line goes to an open-collector inverter connected across the PS/2 data line. The normal low value will hold the data line high so it can operate in receive mode.
If the scan register is re-loaded during the H-blank period, then the sampled value will be high. This will short the PS/2 data line and transmit a low value to the keyboard (the de-asserted state will represent a high value). In essence the PS/2 data is encoded in the duration of the video blanking period and then demodulated by this extra scan register bit. The tradeoff is the video timing itself, so transmitting data to the keyboard will only happen during initialization (before active video).
The RS-232 port uses RTS/CTS flow control. The system will pull the RTS signal low (lighting the front panel LED) to indicate Ready to Receive. This signal is received as the CTS signal on the DCE to indicate it is Clear to Send. The RTS is held high when the port is disabled and the DCE will wait to send data until the RTS is brought low again.
The PS/2 port is controlled by NANDing of the RTS and TX lines from the RS-232 interface. The PS/2 clock line is pulled low and the port is disabled when the RS-232 interface is enabled and the RTS is pulled low. Both the RTS and TX lines need to be high in order to release the PS/2 clock line and signal the PS/2 device (keyboard) is allowed to transmit. Both ports are disabled when RTS is high and the TX is low.
The possible states can be seen as follows:
|0||0||-||rx/tx||read serial/send 0|
|read serial/send 1|