Close

Planning the MicroCode Sequencer

A project log for Homebrew 16-bit CPU

Inspired by The Soul of a New Machine Im designing and building a 16-bit CPU with virtual memory, CPU cache, and rich addressing modes

anthony-fauliseAnthony Faulise 05/05/2025 at 17:060 Comments

The purpose of the MicroCode Sequencer is to fetch MicroCode instructions in order and direct them to the ALU and other CPU components in order to manipulate data that accomplishes the intended machine instructions.  The MicroCode Sequencer needs to be able to jump and branch within the MicroCode, either to effectuate branch instructions or to loop (potentially in the case of multiply or divide instructions). The MicroSequencer needs to be able to pause its operation while it waits for the Bus Controller to fetch memory contents (and maybe write to memory, though I think that could potentially happen asynchronously, with the MicroSequencer carrying on before the Bus Controller confirms a write is complete). Finally, the MicroSequencer output will control the elements of the ALU and CPU without further intermediation, so the control outputs of the MicroSequencer will be enabling and disabling buffers, adders, and multiplexers directly. As a result, we expect the MicroSequencer to have a very wide data output word, potentially dozens and as many as 50 bits wide.

Narratively, I need the MicroSequencer to:

  1. Receive a starting address from the Instruction Controller
  2. Receive a request to start from the Instruction Controller
  3. Fetch the requested MicroCode Instruction
  4. Direct the MC Instruction outputs to the ALU or other elements of the CPU
  5. When necessary, evaluate a branch condition and proceed to a non-sequential next MicroCode instruction
  6. Otherwise, fetch the next sequential MicroCode Instruction, and repeat from 4
  7. Detect the last MicroCode instruction in a machine instruction and halt operation
  8. Signal the Instruction Controller that the current machine instruction has been completed

As a reminder, the Instruction Controller will call “subroutines” within the MicroSequencer to fetch and store Machine Instruction operands according to the addressing mode specified in the operand fields within an Instruction Word. These subroutines will not be accessible directly to the Machine Instruction programmer, but only indirectly by specifying a particular addressing mode in an instruction. Thus, when the MicroController is called to perform a Machine Instruction (like ADD or SHIFT LEFT), all necessary operands will already have been latched into the ALU operand latches. LIkewise, storage of the result of a Machine Instruction happens invisibly to the MicroCode of that Machine Instruction.

For the most part, I expect the code for a Machine Instruction will be relatively simple. For example, for ADC (add with carry), in the first clock cycle, the MicroController would simultaneously output control signals to:

In the second clock cycle, the MicroController would simultaneously output control signals to:

Branching and jumping seem to present some complexity. When we branch, we load a new value into the MicroCode Program Counter register, potentially based on the value of the CCR. We need to represent the destination address somewhere, and that has to be in a MicroCode instruction. The destination address field has to be the full width of the MicroCode ROM address word. The MicroCode ROM data word will already be quite wide. Do I really want to dedicate another 8-12 bits to hold branch and jump destination addresses?

One solution I see is to allow the bottom 8-12 bits of the MC ROM to hold the destination address when we are branching or jumping, and to function in their usual role as ALU/CPU control signals for instructions other than a branch or jump.

I propose that when we execute a MicroCode branch:

In my notes for “Instruction Set and Addressing Modes”, I had proposed these test conditions and instruction bit-field for the test condition:

Condition Bits

Condition

000

Always

001

Carry Set

010

Carry Clear

011

Zero

100

Non-Zero

101

Negative

110

Overflow

111

Half-Carry

As I thought ahead to implementing multi-programming and multi-tasking, I realied it would be helpful to have a bit to indicate if the CPU is in the middle of processing an interrupt and when it is in “Supervisor” mode. That means two more bits in the CCR and two more conditions to test for. To make these new tests fit in the 8 choices provided by my 3-bit condition field in the instruction, I need to trim.

My solution is to test only for one version of each status bit, “set.” If my program logic wants to test for the opposite condition, I’ll just have to reverse my if and else clauses. Also, I have to give up the “Always” condition. I’ll need to implement a different MicroCode for unconditional branches, jumps, and returns.

These are the revised branch condition codes:

Condition Bits

Condition

000

Parity Set

001

Carry Set

010

Overflow Set

011

Zero Set

100

Negative Set

101

Interrupt Set

110

Half-Carry Set

111

Supervisor Set

Discussions