Sunday, April 9, 2023

Centurion FFC Microcode Sequencer

In my last post I described the AMD 2901 Chip Slice which forms the core the Centurion floppy disk controller. The 2901 provides the two fundamental components of a CPU, registers and an Arithmetic Logic Unit (ALU). The question is, how do you turn a 2901 into something that can execute code? The key to this is microcode.

You can think of microcode as machine language instructions, but each bit of the microcode instruction controls a single signal within the CPU. For example the the 2901 has eight inputs which control the ALU function, each of these inputs would be a single bit in the microcode instruction. It is not uncommon for microprocessors to use microcode under the hood, where it is hidden from the programmer. The programmer works with machine language opcodes each of which executes one or more microcode instructions. The Centurion FFC on the other hand uses microcode directly. 

The Centurion FFC has seven microcode ROMs making for a 56 bit wide microcode instruction, and each is 1K. There is also an eighth ROM that store immediate data instead of control signals which can be fed into the 2901 and other parts of the CPU. Before we can look at what the microcode bits do, we need to see how microcode is addressed. For that, the board uses the Signetics 8X02A Control Store Sequencer. Here is the block diagram of that chip:


The chip contains a program counter which drives the nine address bits that go to the microcode ROMs. The next address is controlled by the AC0..AC2 inputs which comes from bits 0..2 of microcode ROM H8, so each microcode instruction also determines which instruction get's executed next. Here are the function of those controls lines, they are numerically out of order but I think they make more sense this way...

7 = Reset. This is the first value that needs to be applied to chip. It resets the program counter to 0. 

1 = Increment. This is the simplest and most common function, it just increments the program counter by one.

0 = Test and Skip. This is the simplest flow control function. If the Test input to the chip is high, the program counter in incremented by two, thus skipping an instruction. If the input is low, the program counter is incremented by one. This makes it easy to implement a branch without a need to specify an explicit destination. 

5 = Push for Looping. This function pushes the current address onto the chips internal stack, and then increments the program counter. This is used to start a loop.

2 = Branch to Loop. If the Test input is high, the top value is popped off the internal stack into the program counter, otherwise the address is incremented and the top value popped and discarded. This used to test for the end of a conditional loop started with the Push for Looping function. 

4 = Branch to Subroutine.  If the Test input is high the next address is pushed onto the chip's internal stack and then the program counter is loaded from the B0-B9 inputs to the chip, we will look at where those come from below. If the Test input is zero the counter increments to the next instruction. This function works as a conditional subroutine call, and since one of the conditions can be a static 1, it can also function a simple jump to subroutine. 

3 = Pop Stock. This pops the top value off the stack and puts it in the program counter. This allows the program to return from subroutines called from Branch to Subroutine. 

6 = Branch if Test Is True. If the Test input is high, the program counter is loaded from the B0-B9 inputs, otherwise the program counter is incremented. This works as a branch to a specific address.

The Branch to Subroutine and Branch if Test functions branch to the address on the B0-B9 inputs. This data can come from two places on the FFC, either from the constant ROM or from the output of the ALU. 



No comments:

Post a Comment