Disassembling the Triad T2556: Serial I/O

Part Ten in a Series on the Triad T2556

Previously, we looked at the code that tested the video controller, read-only program memory, and some of the RAM.

This is an important milestone, since now the machine has confidence that it can perform basic operations like run programs and display text.

But as well as output, computers usually need input. The T2556 is no exception. From the schematic, we know that the machine uses a standard Z80 Serial I/O Controller. The device is complicated enough that it has its own manual.

Like many devices on the machine, the lower address lines are used to select a particular register on the device, and the higher bits are used to select the device.

A1 A0   Register
0 0 Port A Data
0 1 Port B Data
1 0 Port A Control
1 1 Port B Control

These registers provide a means to program the SIO, although notice that the approach is different to the way in which the 6845 video controller was programmed in the first post in this sub-series.

Let’s get into the details.

BLOCK 7A: Here’s another new instruction, otir. Just like the ldir instruction that we encountered last time, this is a block instruction. The acronym is short for output, increment, repeat. Given a output port in register c, a byte count in register b, and a memory location in hl, this instruction will copy all of the data in the block to the port.

BLOCK 7B: Looking back to the address/register table above, you can see that the code will first stream data to I/O address 3 (00000011 binary), and then a separate data block to I/O address 2 (00000010 binary).

So this is writing to the A and B control ports on the SIO.

BLOCK 7C/BLOCK 7D: but what do those data blocks actually tell the SIO to do? To understand this better, we need to refer back to the device’s manual.

You can see that the designers did their best to group like functions, but they were constrained by how many registers they could address and it gets a bit messy in places.

Of course, there’s a lot more information and context in the device’s manual. Basically, though, the first write will address register 0. The bottom three bits of the byte sent (D2-D0) will indicate which register will be sent the next byte.

Looking at the layout for write register 0, it’s broken out into three blocks: bits 7 and 6 have to do with reseting status, bits 5–3 have various other reset and control functions, and bits 2–0 set which register will receive the next control byte.

The complete configuration for port B and how it’s broken down is covered in the diagram opposite. This kind of information will be super useful when we want to later repurpose the machine.

From the schematic, we know that transmit on this port routes to the 40-column serial printer, and transmit comes from the keyboard. So all things being equal, we now know that receive and transmit each run at 8 bits/1 stop bit/no parity, but that’s kinda outside the scope of this article. What we might care more about is that each character received will cause an interrupt to be generated (“generate interrupt on all characters received”), but we don’t care whether there is a possible parity error with the byte as it arrives (“status doesn’t affect vector”). The device will also drive the DTR (“data transmit ready”) and RTS (“request to send”) serial handshake lines.

We haven’t come across anything to do with interrupts yet, though, that’ll happen later.

If you like, you can decode how serial port A is programmed using the same process.

BLOCK 8A: If the SIO is working, we should be able to read back what’s stored in register 2 of Port A, and it’ll match what we programmed a moment ago. If not, then execution flows to sio_check_fail in block 8D.

As you can see from the manual extract, register 2 contains something called an interrupt vector and is only present on port A. That will come in handy later.

BLOCK 8B: Some minor changes are made to the configuration of serial port B, most notably that the clock mode (which sets the baud rate of the interface) is changed from x16 to x1*, and even parity is enabled for the port. Clock mode configures how much the clock on the /RxCB pin is divided in order to determine the expected serial speed. I don’t know why it’s configured as x16 at first, and then drops to x1 right after.

BLOCK 8C: here’s another tone loop, indicating that the SIO configuration was successful.

BLOCK 8D: This is a diagnostic message which displays the “Terminal is ill” message, as well as an indication that it was the SIO that caused problems.

Block 8E: A continuous tone loop indicating that the hardware needs repair.

That’s it! The SIO chip is now configured and ready to run. Next time, we’ll look at the remaining general initialization chores that are needed to finish getting the machine started.

A quick diversion for fun. Port B (whose receive line attaches to the keyboard) gets its clock from this circuit.

The 16MHz system clock derived from a quartz crystal is divided by 16 to generate a 1MHz signal as 1MHZCLK.

This clock is then further divided by 6 stages of flip-flops packaged as part of a 74LS393 counter circuit. 2⁶ = 64, so this clock is divided by 64.

1MHz/64 = 15,625 baud. If I have the math right, then a keyboard connected to this port would need to run at 15,625 baud. Weird, right?

That’s most of the hardware reverse engineered. I’ve been working on actually coding up a new system ROM and building some custom expansion hardware to learn and prove out the discovery. I have a new job, though, and this will take a lot of my time. So reporting back and finishing up the remaining half-dozen or so blog posts in the series will have to wait. Thank you for reading!

Software and Technology Nerd, DevOps Ninja, Maker of Things, Aerospace Enthusiast. https://orc.works/