Microcode pre-release

I decided to stop at this point and declare the initial version of the microcode complete (although the machine has not been simulated yet and the microcode is still untested). I have not yet completed faults and interrupts microcode, because I don’t know how I am going to do user/supervisor mode context switching and which approach I will choose for my memory subsystem organization for process memory isolation. Two options I am considering here are – implement some sort of paging (more sophisticated but allowing for real “virtual memory” with small chunks), or add to the design a physical memory offset set by the supervisor by a dedicated instruction and stored in some dedicated register (physical memory address would then always be physical offset plus logical address – simple approach but obviously memory wasting).

Before I make my decision and go any further, I want to simulate the whole thing with the following assumptions:

  • run only in supervisor mode
  • run with no faults/interrupts
  • run with no memory subsystem (logical address becomes a physical address, with some necessary bit extension)

My goal now is to write a hardware level simulator, perform basic computations and run a test suite for the current instruction set (of course, I need to write or port some sort of assembler for this). When (if?) I succeed, I will return to the memory subsystem design and context switching, and add it to the simulator (along with necessary microcode).

Okay, here is what we have now:

Instruction Set

I currently have 217 finished instructions, including loads, stores, arithmetic and logic operations, compares, and branches. From the addressing modes I initially planned to implement, I decided to skip direct and indirect addressing, with absolute memory address as one of the operands. Not because they required some extra effort (they are computable in current design) but because I think they will not be too useful. The same can be achieved by register direct, register indirect and register indirect with offset operations. The opcode space is limited and I only have 39 opcodes left. Most of instructions are 16-bit operations although I also added their 8-bit counterparts for some addressing modes. I added a downloads section to this site, where I will always store the most up-to-date version, which should be considered a single source of truth for the machine’s instruction set. The current version is here.

Microcode Assembler

The microcode assembler is a C++ application. Its job is to parse microcode listing in a text form and output an array of bits to an Intel HEX file. I tried to make the assembler bulletproof, and some of its features saved me from making some silly microcode mistakes which would be later difficult to hunt. The source code of the microcode assembler may be found in the downloads page, too. It is an Apple’s XCode project but the code is pure C++, so it should compile everywhere. Technically, the application is a simple lexical analyzer, and a parser with code generator. I am not using lex or yacc here. The source syntax is best explained by providing an example, so here it is for the instruction ADC A, X (add with carry register X to register A, store result in A):

// ADC A, Y
    0, *, MDR <- Y
    1, C, A <- A + MDR + 1; SETFLAGS_WORD; fetch
    1, !C, A <- A + MDR; SETFLAGS_WORD; fetch

Instructions are enclosed in op and endop keywords, with op holding an actual opcode ($00-$10f). Each line starts with a step (cycle) number followed by a comma, and the expression for flag values. Here, an asterisk means that the step applies to all flag combinations, C means that the step is valid when carry flag is set, !C when it is not set. Other flags (Z, N, V) may be used in the same manner, e.g. C!ZN is also a valid flags expression (although I don’t think I would ever need to use expressions of such complexity). Following the flags is a set of microinstructions, delimited by semicolons. Data transfers and ALU operations are expressed by <- operator. Apart from transfers, other microops are possible, like SETFLAGS or FETCH. There are also register increment and decrement operations, for registers capable of behaving so without the ALU. Here is another, lengthier example for a register indirect with 16-bit offset load operation that takes 6 clock cycles to compute:

// LD A, (SP:#i16)
    0, *, HI(MDR) <- MEM(PC); CODE; PC++
    1, *, LO(MDR) <- MEM(PC); CODE; PC++
    2, *, MAR <- MDR + SP
    3, *, HI(A) <- MEM(MAR); MAR++
    4, *, LO(A) <- MEM(MAR);
    5, *, fetch

In the download section there is a full source code of microcode as it stands today.

Microcode Word

The microcode word resulting from compilation is currently 4 bytes long and I hope to keep it this way. Four bytes gives me 32 microcode word bits of which I am using 29. Three bits are left for things related to faults/interrupts, and the memory subsystem. That’s not too much so in order to keep the microcode word 4 bytes long, I will probably need to optimize the field layout sometime soon. Here’s the current state:

Field Length Values Description
LBUS 2 MDR, A, X, Y Left bus drive enable register select
RBUS 2 MDR, MSW, ABUS, not_connected Right bus drive enable register select
ADRBUS 2 MAR, SP, DP, PC Address bus drive enable register select
BUSIFCMODE 2 MEM2ALULO, MEM2ALUHI, ALULO2MEM, ALUHI2MEM Bus interface direction and mode
BUSIFCEN 1 enable, disable Bus interface enable signal
LOAD 4 MDRLO, MDRHI, MDR, ALO, AHI, A, X, Y, MEM, MARLO, MARHI, MAR, SP, DP, PC, MSW Load enable register select
LOAD_IR 1 enable, disable Load IR register signal
ALUOP 4 as in 74LS181 function input ALU function
ALUCARRYIN 1 enable, disable ALU carry input enable signal
ALUSHR 1 enable, disable ALU right shifter enable signal
LOADFLAGS 2 WORD, HI_BYTE, LO_BYTE, disable Flags latch mode
INCPC 1 enable, disable PC++ enable signal
INCMAR 1 enable, disable MAR++ enable signal
INCSP 1 enable, disable SP++ enable signal
DECSP 1 enable, disable SP–– enable signal
MEMSEG 1 CODE, DATA Memory segment selection
SUPERVISOR 1 enable, disable Privileged (supervisor) instruction signal
not connected 3 not connected not connected

One thing that worries me already is that I have already used all load destinations in 4-bit load field. I am pretty sure now that in order to implement supervisor-user mode context switching I will need yet another scratch register to store the supervisor stack pointer somehow (user mode programs may have their stack pointers set wherever in 64-kbyte data region). That’s one additional bit. Also, I need a bit to enable and disable interrupts from the microcode. That’s two, leaving me with one bit for memory subsystem microcode. Not enough. Some crunching will definitely be necessary if I want to avoid adding another byte (and chip) to the microcode store.

Leave a Reply




Time limit is exhausted. Please reload the CAPTCHA.