Title: 8080 IO - echo
Date: December 08, 2018
Tags: altair programming
========================================

Time to take a leap forward in technology and human-computer interaction.  I'm
allowing myself to use a terminal to talk to the Altair.  A couple of
implementations of echo demonstrate serial IO.

The simplest program to start with for IO is an echo program.  It covers the
basics of interrupts and how to read and write to the terminal via the serial
port.  It's not much of thing on it's own.  On today's computers echoing is a
thing that just happens before you've actually even executed a program you're
trying to run.  But at the same time, seeing what you're about to command the
computer to do is a necessary part of interacting with it.  From this we can
build programming environments like BASIC or a shell environment or any other
kind of text interaction.

The code for echo using the 88-2SIO board comes almost entirely from the Altair
clone materials[0] as demonstrated in a video about interrupts[1].  I merely had
to add the echo part that writes the character back out to the serial port.

Code for echoing characters using the 2SIO board in the Altair clone:


000     LXI SP          061 ; Set stack pointer
001             000Q    000 ; to 000400Q
002             001Q    001
003     MVI A           076 ; Reset ACIA
004             003Q    003
005     OUT             323 ; 2SIO control
006             020Q    020 ; is at 020Q
007     MVI A           076 ; Set to 8n1
010             225Q    225
011     OUT             323
012             020Q    020
013     EI              373 ; Enable interrupts
014     NOP             000 ; Wait loop
015     JMP             303
016             014Q    014
017             000Q    000

; Interrupt handler
070     IN              333 ; Read character
071             021Q    021 ; from address 021Q
072     OUT             323 ; Write character back out
073             021Q    021 ; also to address 021Q
074     EI              373 ; Re-enable interrupts
075     RET             311 ; Return to wait loop


## Breaking it down ##

We start by setting the stack pointer to 000400Q which is the highest 8 bit
address.  No special reason to put it there versus any where else.  We then send
a 003Q to the 2SIO board which is value for the "master reset" to reset the
board's configuration and state.  The control address for the first serial port
is address 020Q.  When OUT and IN are used, the 1 byte address is not a memory
address in RAM but a hardware address.  We then configure the 2SIO board's first
serial port to 8 data bits, no parity, 1 stop bit, and 9600 baud.  After setting
up the serial port, we simply enable interrupts and enter a NOP loop to wait for
data.

The interrupt handler simply reads the data address of the 2SIO's first port
which we know is populated because of the interrupt.  Interrupts are
automatically disabled in the CPU once we jump to the handler, and the interrupt
generated by the board is acknowledged by reading the data address.  If you
remember the 88-VI-RTC board required the software to explicitly acknowledge the
interrupt since there was no data to read from the clock.  We simply write the
byte straight back to the data address and the 2SIO board will send it out the
port.  The IN and OUT addresses can be the same because the board knows when an
IN instruction is being executed and when an OUT instruction is being executed
and will handle the data appropriatly.

# Using the 2SIO #
Configuring
The serial ports on the 2SIO have to be configured for the number of bits,
parity, etc.  The configuration bits (from 7 to 0):

================================================================================
|| Bit || Value || Function                                                   ||
================================================================================
|     7 |      0 | Receive interrupt disabled.                                 |
|       |--------+-------------------------------------------------------------|
|       |      1 | Receive interrupt enabled.                                  |
|-------+--------+-------------------------------------------------------------|
|   6-5 |    0 0 | RTS = low, transmission interrupts disabled.                |
|       |--------+-------------------------------------------------------------|
|       |    0 1 | RTS = low, transmission interrupts enabled.                 |
|       |--------+-------------------------------------------------------------|
|       |    1 0 | RTS = high, transmission interrupts disabled.               |
|       |--------+-------------------------------------------------------------|
|       |    1 1 | RTS = high, transmits a break level on the transmit data    |
|       |        | output, transmission interrupts disabled.                   |
|-------+--------+-------------------------------------------------------------|
|   4-2 |  0 0 0 | 7 data bits, 2 stop bits, even parity.                      |
|       |--------+-------------------------------------------------------------|
|       |  0 0 1 | 7 data bits, 2 stop bits, odd parity.                       |
|       |--------+-------------------------------------------------------------|
|       |  0 1 0 | 7 data bits, 1 stop bit, even parity.                       |
|       |--------+-------------------------------------------------------------|
|       |  0 1 1 | 7 data bits, 1 stop bit, odd parity.                        |
|       |--------+-------------------------------------------------------------|
|       |  1 0 0 | 8 data bits, 2 stop bits, no parity.                        |
|       |--------+-------------------------------------------------------------|
|       |  1 0 1 | 8 data bits, 1 stop bit, no parity.                         |
|       |--------+-------------------------------------------------------------|
|       |  1 1 0 | 8 data bits, 1 stop bit, even parity.                       |
|       |--------+-------------------------------------------------------------|
|       |  1 1 1 | 8 data bits, 1 stop bit, odd parity.                        |
|-------+--------+-------------------------------------------------------------|
|   1-0 |    0 0 | Set the clock divide to 1.                                  |
|       |--------+-------------------------------------------------------------|
|       |    0 1 | Set the clock divide to 16.                                 |
|       |--------+-------------------------------------------------------------|
|       |    1 0 | Set the clock divide to 64.                                 |
|       |--------+-------------------------------------------------------------|
|       |    1 1 | Master reset.                                               |
--------------------------------------------------------------------------------

We used 10010101 to:  enable receive interrupts, set RTS = low with transmission
interrupts disabled, set 8 data bits, 1 stop bit with no parity, and set the
clock divide to 16.

RTS is Request to Send (later repurposed as Ready to Receive) and is part of a
handshake protocol some devices used.  This simple serial IO doesn't need it.
You can read more info on Wikipedia's RS-232[2] page.

The clock divide selects the baud rate.  The 2SIO board is hardwired to a
selected baud rate.  The Altair clone allows this to be configured in firmware
and is set to 9600 baud by default.  In software, one could choose a divide of
64 and get a slower baud rate (as slow as 27.5 baud) or a divide of 1 to get a
faster baud rate without rewiring the board.  The chip multiplies the baud rate
by 16 so a divide of 16 gives you the straight baud rate as wired.  With a wired
baud of 9600, we could also run at 2400 baud or 153,600 baud which seems really
fast to me for this era so I'm not sure it's actually valid.  The documentation
doesn't mention speeding up the baud rate, just slowing it down and 9600 is the
fastest hardwired rate.

# Status #
In addition to using interrupts to interact with the serial port, the 2SIO can
be polled for data.  The status address (the same as used for configuration) can
be read and the bits (from 0 to 7) tell the following:

================================================================================
|| Bit || Name                  || Description                                ||
================================================================================
|     0 | Receive Data Register  | 1 indicates data is ready in the data       |
|       | full                   | register.                                   |
|-------+------------------------+---------------------------------------------|
|     1 | Transmit Data Register | 1 indicates data has been transmitted and   |
|       | empty                  | is ready for more data to send.             |
|-------+------------------------+---------------------------------------------|
|     2 | Data Carrier Detect    | 1 when carrier is NOT detected.  When it    |
|       |                        | goes high, generates an interrupt if        |
|       |                        | Receive Interrupts are enabled.             |
|-------+------------------------+---------------------------------------------|
|     3 | Clear to Send          | 0 means a clear to send signal from a       |
|       |                        | modem.                                      |
|-------+------------------------+---------------------------------------------|
|     4 | Framing Error          | 1 indicates a synchronization error.        |
|-------+------------------------+---------------------------------------------|
|     5 | Receiver Overrun       | 1 means a character was not read from the   |
|       |                        | data register before the next character was |
|       |                        | recieved.                                   |
|-------+------------------------+---------------------------------------------|
|     6 | Parity Error           | 1 indicates that the parity bit does not    |
|       |                        | match the number of 1's in the received     |
|       |                        | character as specified by the configured    |
|       |                        | odd or even parity.                         |
|-------+------------------------+---------------------------------------------|
|     7 | Interrupt Request      | 1 when the interrupt request line is LOW.   |
--------------------------------------------------------------------------------

You can see that for more complex communication, like through a modem, polling
the status register gives a lot of information and control that simple
interrupts do not.

# ASCII codes #
With this fancy new technology, we can interact with the Altair.  We can enter
characters and have things happen based on what is entered.  Let's start by
swapping out the interrupt handler and instead of getting the entered character
back out, print out the ASCII code in octal.


; Interrupt handler
070     IN              333 ; Read character
071             021Q    021 ; from address 021Q
072     LXI H           041 ; Set heap address for storing data
073             200Q    200 ; to 000200Q
074             000Q    000
075     MOV B A         107 ; Save character in B
076     ANI             346 ; Mask all but low octal digit
077             007Q    007 ; 00 000 111
100     XRI             356 ; Add 60Q to convert to ASCII
101             060Q    060
102     MOV M A         167 ; Save digit to heap
103     INX H           168 ; Increment heap pointer
104     MOV A B         170 ; Restore original character value
105     RRC             017 ; Shift off low octal digit
106     RRC             017
107     RRC             017
110     MOV B A         107 ; Save shifted character
111     ANI             346 ; Mask all but low octal digit
112             007Q    007
113     XRI             356 ; Add 60Q to convert to ASCII
114             060Q    060
115     MOV M A         167 ; Save digit to heap
116     MOV A B         170 ; Restore shifted character
117     RRC             017 ; Shift off another octal digit
120     RRC             017
121     RRC             017
122     ANI             346 ; Mask all but remaining octal bits
123             003Q    003 ; which this time is only 2 bits
124     XRI             356 ; Add 60Q to convert to ASCII
125             060Q    060
126     OUT             323 ; Write high digit out immediately
127             021Q    021
130     MOV A M         176 ; Read middle digit from heap
131     OUT             323 ; Write middle digit out
132             021Q    021
133     DCX H           053 ; Decrement heap pointer
134     MOV A M         176 ; Read last digit from heap
135     OUT             323 ; Write last digit out
136             021Q
137     MVI A           076 ; Write a new line
140             012Q    012
141     OUT             323
142             021Q    021
143     MVI A           076 ; Write a carriage return
144             015Q    015
145     OUT             323
146             021Q    021
147     EI              373 ; Enable interrupts
150     RET             311 ; Return to our NOP loop until the next character


# Breaking this one down #
This code does a few interesting things.  We create a heap pointer to store data
to memory.  We have to do our own memory management.  There is no OS to call
malloc and no compiler to dynamically store to memory and give us the address
back.  I think a C compiler would store this data on the stack.  I chose not to
do that because stack operations on the 8080 work on 16 bit register pairs and I
needed to store 24 bits.  I had an extra byte.  Or so I initially though.  I
realized later I didn't need to store the last byte, I could leave it in the
accumulator and spit it right out the serial port.  If I go back and rewrite
this, I wouldn't use memory at all since I could fit those 2 bytes into the C
and D registers.

It may not be as efficient as it could have been but it's a chance to learn the
ways in which memory can be managed.  I have to be the compiler, assembler, and
operating system in addition to the programmer.

Next, read in the character from the 2SIO port's data address.  This
acknowledges the interrupt.  Pick a heap pointer.  Heap grows up, the stack
grows down.  When they meet, bad things happen, even in computers today.  It
works this way because when you call PUSH in the CPU, it automatically
decrements the address pointed to before writing the data to memory and POP
increments.  It's just been hardwired since the beginning.  So start the heap
low but leave room in case I have to modify the program and it gets longer.
Then we save the character as entered into the B register so we can use it
later.  Since we are converting the value to octal digits, in 8 bits, there will
be 3 digits.  Consisting of the low order 3 bits, the middle 3 bits, and the
high 2 bits that remain.  A bitwise AND with 007Q will zero out all but the low
3 bits which will remain unchanged.  A numeric value is 60Q less than it's ASCII
character so we just need to add 60Q to whatever the bits are to be able to
print it.  Instead of adding 60Q, I use XOR because I know the bits that 060Q
will land in are zero.  I thought bit operations might be quicker but the
documented timing indicates there is no difference.  Then we write that digit to
the heap and increment the heap pointer.  Do all the same steps for the next
digit.  When we get to the last digit, which is only 2 bits.  We mask it with
003Q before XORing with 060Q and instead of saving to memory, we can just write
it out to the serial port.  We then read the next digit off the heap and write
it, decrement the heap pointer, read and then write the last byte.  And for
readability, we follow the 3 octal digits with a new line and carriage return.
Re-enable interrupts and return to wait for the next character.

---------------------------------------------------------------------------

I'll stop here and continue next time with a string-aware version of echo and a
couple of string implementations.

[0] http://altairclone.com/downloads/interrupt_acknowledge.pdf
[1] https://www.youtube.com/watch?v=l5CHGm1eOio
[2] https://en.wikipedia.org/wiki/RS-232#RTS,_CTS,_and_RTR