Manual Labors: SPAM: SPAM Mark I Microcode

There are two facets to the microcode, the code required to get the assembler to do what we wanted, and the microcode itself. The assembler supports constant definition, macro expansion (including nested macros), and line labeling. Toward the beginning of the file, there are definitions for the controller input and output data, as well as the DO, BR0, and BR1 macros. The DO and BR macros assemble into one byte, for the high or low rom depending on the setting of the assembler constant HIGH_ROM.

The macros themselves are relatively unimportant, but do merit some discussion.

DO takes one argument, the thing to assert on the controller outputs. It then XORs (with a "!") the argument with the constant bitmask DO_CMD, which allows active low signals by setting the bit in the bitmask corresponding to that signal. The argument actually passed to DO is usually more than one signal, and this works by taking defined controller output bitmasks (such as ADR_INC or RAM_CS\) and ORing them together (with a ".") before passing them. This is very strange, but it works well and looks good.

BR0 and BR1 are branch on 0 and 1 respectively. They take two arguments separated by a ";". The first argument is the controller input number to branch on, always a defined constant (such as USR_REW or IFF_TRU). The second argument is the assembler label to branch to. The macro constructs the necessary byte by shifting and adding its arguments.

Finally, we wrote a macro called BEEP, which causes the machine to beep. This made it much easier to beep at will, and flex the powerful muscles of our beep generator, as it generated beeps in all its glorious splendor. The microcode itself consisted of six (6) main blocks:

Initialization in SPAM mark I simply set the message count to 0.
Event Loop
The event loop mainly dispatched control to handlers for various input events. In addition, on a USR_RNG, the main loop plays an outgoing message, checks USR_RNG again to see if the caller has hung up, and then increments MSG_CNT, sets CUR_MSG, and passes control to the code for recording a message.
Record takes data from the A to D converter, and stores it in the ram for the current message. It works by first settting the address controller to the beginning of the current message. Then we encounter for the first time our ram loop. The loop first waits for a rising edge on CLK_SLO, which we call 'synchronizing' with CLK_SLO.
This ensures each loop iteration will start at the beginning of a CLK_SLO cycle, and so we will sample at 8KHz. We then copy data from the A to D converter into the RAM, and increment the address counters. The loop continues as long as ADR_DON is low, which will occur after two (2) seconds have passed. Record has an additional entry point which sets CUR_MSG to 0 before recording, in order to record an outgoing message.
Play Outgoing Message
This is virtually identical to record, except that in the ram loop, data is copied from the ram to the D to A converter.
The guts of play are similar to play OGM, except that branches are included in the loop to check rewind and fast forward. The size of the loop becomes critical here, as the controller's clock is only eight (8) times faster than CLK_SLO, which means only eight instructions can be executed in each iteration. If FF or RW are pressed, USR_FFW or USR_REW will go high, and the loop will be interrupted to handle the input. On a fast forward, we check that there are still more messages to be played. If not, we exit the play loop. If so, we start playing the next message. On a rewind, we play the previous message, or restart the first message. This was (surprise, surprise) not what was expected, and was fixed in SPAM mark II. When done playing a message, execution branches to FASTFWD, where the current message is incremented, or we return to the event loop.
Erase simply sets the message count to zero (0).

Full microcode for SPAM mark I is in Appendix B.

Next: SPAM Mark II