Approaches to storing and addressing microcode for homebrew CPU

I've been teaching myself about CPU architecture for a while now and have successfully designed a couple myself. They were always based around microcode to drive the CPU's control lines.

The microcode is stored on one ROM chip and addressed by composing an address out of (1 = LSB):

1. Bank
2. CPU flag states
3. Instruction
4. T-state

Let's take my latest CPU as an example of how the address is constructed:

1. Bank (3 bits) - There are 48 control lines in my CPU, so I need 6 bytes to store all control line states. The bank part of the address allows me to address 6 bytes in the microcode ROM.
2. CPU flag states (3 bits) - I narrowed down my flags to zero, carry and compare. Need 3 bits to address all combinations
3. Instruction (8 bits) - The instruction set requires over 127 op-code, so I choose to go with 8 bits.
4. T-state (5 bits) - Some instructions take over 16 t-states to complete (complex ones like conditional CALL / RETURN) so with 5 bits I have enough room for up to 32 t-states

The final example microcode ROM address would look something like:

[flags][instruction][t-state][bank] [000][00000000][00000][000]

Now for my question: In the above example, I seem to have reached the limit of what I can do with this approach. My microcode ROM address is 19 bits long and I have found one chip that supports it (29C040) but it seems that I don't have a lot of options if I want a large address.

I'm thinking about my next CPU which will need more flags (negative, overflow, parity), and who knows, maybe even a few more control lines or an extra bit for T-states.

What would be a better approach to storing and addressing my microcode in that case?

The only thing I can think of right now is to:

• Add more ROM chips (would only free up 1 extra bit? per chip doesn't seem a good approach)
• Limit the instruction set (also, would only give me 1 - maybe 2 - extra bits)
• Limit the number of control lines, but that would allow for less control over the CPU...

I wonder how this is solved in professional, microcode-based CPUs and what I'm missing here.

• Better approach is functional requirements first, and only then design. Otherwise, for generic applications, take already existing architecture or existing CPU/MCU chip. Commented Aug 21, 2019 at 9:01
• decades ago I used 256word by 8bit MonolithicMemories PROMS (not erasable) to implement various state machines for data collection and feeding that data into Silent 700 cassette recorders. I used 1 bit for THIS_IS_AN_ABSOLUTE_JUMP flag; with only 7 bits left for the address, I simply jumped to any even address. Had enough space with the 256 words; good luck. Are you planning a VLIW version? Commented Aug 22, 2019 at 6:10

Your addressing scheme uses completely disjoint areas of microcode memory for every possible instruction. Most CPUs have a lot of microcode that can be shared among instructions. For example, instruction fetch, operand read and result writeback are typically identical across large groups of instructions, with the only difference being the specific ALU operation performed in the middle.

It's also very odd to store the bytes of the microcode word serially — this means that your ROM must cycle 6 times for every "T state". It is much more common to make the microcode memory wide enough so that it cycles at the same rate as the rest of the logic.

Finally, it sounds like you're experimenting with CPU design. Maybe you should consider using wide SRAM for your microcode memory, and loading it up from some external source (an Arduino or equivalent) each time you power up your system. This would make it a lot easier to make changes.

• My controller is running at 8 times the clockspeed of the processor to “counter” the fact that microcode is stored serially as you describe it. But I do realize it’s a naive implementation. I’m learning every day :) I’ll give jt some more thought on how to re-use parts of the microcode. You wouldn’t have any example implementations you could refer to? Commented Aug 21, 2019 at 14:13
• The most current example would be Ben Eater's 8-bit breadboard processor. In particular, look at the section called "CPU control logic". I have an ancient evaluation kit for the Intel 3000 series family of bipolar bit-slice chips. They seemed to think that 512 words of microcode ROM was enough for most applications of the day (mid-1970s). The manual includes a complete design for a 16-bit CPU using these chips. Commented Aug 21, 2019 at 14:29
• Afaik Ben Eater uses the same approach, although his CPU doesn’t have as many control lines and only a few instructions. He is using 2 roms chips to be able to load all control lones in one tick (meaning he has 16 of them or less). I’ve built his CPU breadboard CPU as well - it was the beginning of this journey for me. I will look into the Intel 3000 information you shared. Thanks Commented Aug 21, 2019 at 14:51
• I’m going to mark this as the answer. It wasn’t the straight up solution, but it gave me a big push into the right direction. I realize now that I should apply more encoding to the microcode and do more decoding in my controller. This allows me to fit all control lines within 16-bit (and I can easily find eproms with 16 bit words) and also to decrease the amount of address bits needed (managed to take out the “bank” part and also moved the “flags” state out of the microcode). It’s looking a lot better now Commented Aug 22, 2019 at 10:13

For my homebrew CPU, the CSCvon8, I used a 4Kx16 ROM to hold my microcode. The 8-bit instruction is latched into an 8-bit register to form 8 of the 12 address bits to the microcode ROM.

The remaining 4 ROM address bits come from a 4-bit counter. This allows each instruction to index into 16 microinstructions. And the ROM has 16 bits of output, so I've got 16 control lines I can set/clear per microinstruction.

One of the control lines resets the microcounter to zero, so instructions don't have to have exactly 16 microinstructions. They can be composed of from 2 up to 16 microinstructions.

You could move your flags bits out from the ROM address space. I send three control lines out to a 74HCT151 8:1 mux. This selects one of seven flag bits to send as a control line to the program counter. To generate all the jumps (==, !=, <, <=, >, >=, always, never) I cheat by having my own ALU. You could use some combinational logic to join the N and Z flags appropriately.

What you've got is horizontal microcode which is what I'm using as well. You might want to consider vertical microcode, or another format where the microcode has its own internal branch/jump, i.e. to jump the microcounter based on the flags values.

Have a look at this example: https://web.archive.org/web/20160305185415/http://www.mythsim.org/ and my take on the same approach: https://minnie.tuhs.org/Programs/UcodeCPU/index.html (scroll down to "The Microcode Logic").

• Funny things is, I’ve already been reading up on your Github repo for the past 2 weeks and have taken a lot of lessons from it :D At this point I’ve worked out an approach that works for me and expect to be sharing it in a week or so (my repo is still private until then). Thanks for sharing your work!! Commented Sep 20, 2019 at 10:33