I'm trying to create a CPU, using 8-bit instructions, and there will be 9 or 10 of them.

I have an add, subtract, multiply, load, store, branch if zero, branch if not zero, print (to display), input (from keyboard), and return (exit program) instructions.

Addresses are 4 bits long, and I have two general purpose registers, so I only need a single bit per register; so for addition, subtraction, and multiplication, I need 2 bits for the registers (one per register, two registers) - leaving me with 6 extra bits in the instruction format.

For loading/storing/branch if zero/branch if not zero, I need one bit for the single register and 4 bits for the address, leaving me with 3 extra bits in the instruction format.

For print and return, I only need one register, so 1 bit, leaving me with 7 bits for the rest of the instruction format; and for return, I have a full 8 bits I'm not sure what to do with.

So I need to figure out the instruction format and build my control unit circuit. I've been trying to do a lot of reading and I feel pretty stuck at how to even go about this - what size my op code should be, how to implement this in a logic circuit, etc.

Any advice would be appreciated.

EDIT: I've been given these specifications and I have to work around them, though I know they're not really ideal - it's just a small project I've been assigned (however I do appreciate the additional input because I do want to learn as much as possible, just know that I can't really change the specifications).

  • 1
    \$\begingroup\$ What is the implementation plan? With discrete elements (ICs)? or on an FPGA?? \$\endgroup\$ – nidhin Nov 22 '14 at 6:26
  • \$\begingroup\$ Was that set of instructions handed to you, or did you decide on them? \$\endgroup\$ – Ignacio Vazquez-Abrams Nov 22 '14 at 6:27
  • \$\begingroup\$ @nidhin This is just a simulated circuit in logisim. \$\endgroup\$ – cerremony Nov 22 '14 at 6:31
  • \$\begingroup\$ @IgnacioVazquez-Abrams this instruction set was handed to me \$\endgroup\$ – cerremony Nov 22 '14 at 6:34
  • 2
    \$\begingroup\$ Smells like homework, and the guidelines for posting homework have not been followed. State if it's homework, and tell us what you have tried, where you're stuck and what you think you should do but can't figure out. \$\endgroup\$ – akohlsmith Nov 22 '14 at 23:56

For a real computer, you definitively would want more than 4 bits of program address since 4 bits only allows 16 instructions. So I came up with a scheme using a two-byte instruction for jumps, calls, load and stores which would give you a 12 bit address or 4096 location.

However, if you leave off this extra byte, then my instruction format allows for 5 bits (not just 4) of program address, and up to 4 bits of RAM addressing.

So the following is an instruction set based on the specification of two registers. All instructions are one byte except for the four requiring full addresses (optional, as described earlier, leave off this 2nd byte for 4 bit addressing).

I left in the long formats, because if one includes them, I think this would make a reasonable 8-bit computer (even though it can only address 4K bytes).

Although I favor memory-mapped I/O over input/output instructions, I provided two of each to satisfy the spec.

    register-register instructions:

    0 0 x x x x d s

    where x x x x is the opcode,
          d is the destination register 0 or 1,
          and s is the source register 0 or 1

    opcodes field:

    0000  add   d = d + s
    0001  adc   d = d + s + c
    0010  sub   d = d - s
    0011  subb  d = d - s - c
    0100  and   d = d and s
    0101  or    d = d or s
    0110  xor   d = d xor s
    0111  not   d = not s
    1000  asr  s = 0 arithmetic shift right d
(s=0 means s field is 0, not that the register is 0)
    1000  asl  s = 1 arithmetic shift left d
    1001  ror  s = 0 rotate right d
    1001  rol  s = 1 rotate left d
    1010  inc  s = 0  increment d
    1010  dec  s = 1  decrement d
    1011  cmp  d - s (no store)
    1100  inp1  s = 0  input to reg d from input port 1
    1100  inp2  s = 1  input to reg d from input port 2
    1101  out1  s = 0  output from reg d to output port 1
    1101  out2  s = 1  output from reg d to output port 2
    1110  mul   d/s = s * d  (high byte of result into d, low byte into 1-d)
    1111  sec ds = 00  set carry
    1111  clc ds = 01  clear carry
    1111  ret ds = 10  return from subroutine
    1111  hlt ds = 11  halt

    0 1 0 0 n n n n

    brn - unconditional branch negative -n bytes (up to -16),
    used for branching back at end of a short loop after a skip

    0 1 0 1 b b i i

    skip instructions, where
        b b is type of branch
        i i = # of bytes to skip typically 1 or 2, latter for
        skipping over jump/call)

    b b field:

    00  scs skip i bytes if carry set
    01  scc skip i bytes if carry clear
    10  szs skip i bytes if zero bit set
    11  szc skip i bytes if zero bit clear

    0 1 1 r n n n n

    load immediate to register r (0 or 1) signed value nnnn
    +15 to -16

    1 0 x p a a a a
    a a a a a a a a  (2nd byte only for extended format)

    jump or call instruction (x = 0 is jump, 1 is call)
    p is reserved for a page bit (or could just be the high
    bit of address).  12 bits of address provide a direct call
    or jump to 4K of program memory (or 5 bits provide
    access to 32 bytes of memory).

    1 1 x r i a a a

    load store from/to RAM (x = 0 is load, 1 is store)
    11 bits of address provide direct access to 2K of RAM
    (or 3 bits provides access to 8 bytes of RAM)
    r is the destination or source register (0 or 1)
    i field specifies indexed addressing using the register
    not specified by the r field.  if indexing feature left
    off, then either 4K bytes or 16 bytes can be addressed.

There are three kinds of branches: jump and call instructions, which take a full address; an unconditional branch instruction that can branch backwards up to 16 bytes; and conditional skip instructions that can skip up to 4 bytes ahead. Using skips instead of branches allowed for a shorter address field. It could be redone as branches instead by getting rid of the load immediate instructions:

0 1 b b a a a a

conditional branch instructions, where
    b b is type of branch
    a a a a is signed relative branch +- 8 bytes

b b field:

00  scs branch i bytes if carry set
01  scc branch i bytes if carry clear
10  szs branch i bytes if zero bit set
11  szc branch i bytes if zero bit clear

The way the multiply works is as follows: an 8x8 multiply gives a 16 bit result. The multiply instruction always multiplies register 0 by register 1. The high byte of the result goes into register d, and the low byte goes into register 1-d. s is ignored.

I didn't implement the concept of multiplying the "input buffer" by the "data cache" since the OP didn't specify any details about the cache -- and I currently have the input buffer being read into either of the two registers. Loading the an input port into one of the registers, multiplying by the other producing a 16-bit product in both makes a lot more sense.

Except for the multiply, this could be implemented fairly easily; all of the arithmetic operations (add, subtract, compare) and logical operations (and, or, xor, not) can be performed by an ALU (Arithmetic/Logic Units), supported in Logisim. In real life, this might be implemented using two 4-bit 74LS181 ALUs cascaded together.



how to implement this in a logic circuit, etc.

In the normal flow of execution, an instruction gets fetched into the instruction register IR, then is decoded by the control unit into a big bundle of individual control wires, which then controls the datapath of the processor.

If I were you, I would design the control path "backwards" from that process:

First design the datapath.

Then make a list of all the control signals that are needed to specify how data flows through that datapath. The ALU needs some control signals to tell it whether to add, subtract, etc. The program counter section needs control signals to tell it whether the program counter gets reloaded with an incremented version of the previous value, or with some completely different branch value. etc.

Then for each assembly-language instruction mnemonic, figure out what sequence of pulses on each control signal line are required to implement that instruction.

If you are lucky, many of those control signals can be "directly decoded" from the instruction register. The remaining control signals are generated by the control unit, which is implemented as a Moore machine or a Mealy machine.

If you have a Princeton architecture -- your processor normally pulls instructions from the same single-ported memory used to read and write data -- then you are forced to have at least LOAD and STORE take multiple cycles to execute.

Perhaps the conceptually simplest and most general-purpose approach is to implement the control unit with a very wide control store ROM holding the microprogram, with a pipeline register that latches all the output bits of that ROM every clock cycle.


The "Microprocessor Design: Design steps" goes into much more detail.

p.s.: Please help improve the current rough draft of the "Microprocessor Design" book to make it easier for the next person to design a processor.

typical 8-bit CPUs

... using 8-bit instructions ...

With very rare exceptions, every instruction set I've ever seen proposed (much less even simulated or actually implemented) uses more than 8 bits for at least some instructions.

All the "8-bit CPUs" I've ever seen (6800, 6502, Zilog Z80, 8080, 68HC11, Microchip PIC, Atmel AVR, etc.; as well as some unique homebrew processors and a bunch of "paper-only" ideas that have not yet been implemented) have at least some instructions that are more than 8 bits long.

For practically all of those CPUs, the first byte of the instruction has the complete opcode, and from that byte the decoder in the CPU can figure out exactly how long the instruction is. Often NOP and RETURN are one-byte instructions. Often BRANCH is a multi-byte instruction -- one byte for the opcode, followed by however many bytes are required to fill the program counter PC.

I think it is unlikely that the person who gave you those specifications for an 8-bit machine really intended you to work under the additional heavy burden of also limiting the maximum length of every instruction, including the non-opcode bits, to 8 bits.

And so you can probably skip the next section.

What if every instruction were exactly 8 bits wide?

I think limiting the maximum instruction length to 8 bits (or even shorter!) is a fascinating idea. It makes some parts of the processor simpler, at the cost of making other things much more difficult to implement and test.

... instructions, and there will be 9 or 10 of them. ...

A processor with at least 9 instructions requires at least 4 bits to fully distinguish those instructions -- the opcode bits.


Plus at least 2 bits to need to distinguish between those 4 different operations. Plus at least 1 more bit to indicate whether it's one of these 4 instructions or some other instruction entirely.

That implies that loading/storing/branch if zero/branch if not zero instructions have this format:

3 bit opcode + 1 bit source/destination register select + 4 bit address = 8 bits.

That's a total of 8 bits, so it just barely fits.

If you really need 2 bits of register select for other instructions (I'm not sure that you do), you are forced to use a different format for those instructions, perhaps

3 bit opcode + 1 bit source/destination register select + 1 bit source register + 2 function bits + 1 unused bit = 8 bits. Typically the same machine-language opcode is used for several different ALU operations. The function bits select which particular operation -- add, subtract, or some other ALU operation.


I'm assuming you are using a Harvard architecture, with separate code and address spaces.

It's easy to map instructions to binary, you can see the MIPS instruction set to get an idea. And don't forget to add immediate loads to your ISA if you can!

Once you're done designing the ISA, you can think up how your datapath will be. The datapath describes how data gets around your CPU. The control logic will control this flow.

I highly recommend you take a look at Nand2Tetris. It has a chapter on building a simple 16-bit CPU with a graphical screen and a keyboard.


I wouldn't include instructions specifically to read the keyboard and/or write to the display. Instead, I'd probably memory map them so (for example) the keyboard looks like a couple bytes of memory (one for status, the other for data). Likewise, the display would look to the CPU like a byte of memory, and you'd just connect the LED controller to read from that memory location and display its content.

edit: the two bytes (status/data) for the keyboard are used so you can check things like whether a key is pressed right now, and what "modifier" keys (e.g., shift, control) were pressed along with the A key (just for example). You can represent it as a stream of events (ctrl was pressed, A was pressed, A was released, ctrl was released) but making meaningful use of that will require more program space than you seem to have available. The whole question may be moot though, if (for example) all you have is a set of number keys, and the user can only enter a single digit at a time.

As far as how you'd implement it: no, it wouldn't be in the instruction. It would be handled by the memory interface. In a typical case, you need a memory decoder that (for example) takes some bits of an address, and decides which memory chip to route a request to based on those bits. In this case, you'd take (for example) the most significant bit of your address, and when it's set you'd read from some logic instead of actual memory.

  • \$\begingroup\$ So what you're saying is, I could implement the read instruction as a store instruction, and the print instruction as a load instruction, but the addresses for those instructions are particular to 2 specific memory locations, which is indicated in the binary instruction, and those memory locations are connected to the keyboard and the display? Could you also explain what you mean about having two bytes for status and data? Would that be something I have to provide for in the control unit and the binary definitions of the instructions, or something I have to implement in memory? \$\endgroup\$ – cerremony Nov 22 '14 at 18:25

It sounds like you could cram everything into a single 8 bit byte, if addresses are 4 bit. The low 4 bits could be, in general, "address". The high 3 bits indicate the operation, and the remaining bit could select the register.

3 bits gives you 8 operations, but some of your instructions don't need a register or address (E.g. return needs nothing; print, input, branch probably only need a register). I'm pretty sure you can play about with your requirements until everything fits into 8 bits.

But 4 bit addresses sound pretty limiting! You have probably better implement your program first and make sure it can fit into what sounds like can't be longer than a 16 byte program!

  • 2
    \$\begingroup\$ And, if I were you, I'd probably ditch the multiply instruction and implement the multiplication in software. You'd need a more general instruction set, but would save having to implement multiplication in hardware. \$\endgroup\$ – David Sainty Nov 22 '14 at 7:20

Having addresses be only 4 bits long seems rather confining. While some machines may use 4-bit register addresses, such machines can generally access a memory system with a much larger address space.

I would suggest that as a personal project, you may wish to shoot for a roughly-16-bit address space. If you're building something to play with, I would suggest that a Von Neuman architecture (shared address and data memory) may be easier to work with than a Harvard architecture, even if it requires instructions to be fetched over an 8-bit bus. A simple way of handling variable-width instructions up to 21 or 28 bits would be to have three or four 8-bit instruction latches. One of the instruction latches should capture data from the bus, the second should capture data from the first, and the third should capture data from the second (and fourth, if present, from the third). Additionally, there should be a latch which, when set, would force the upper instruction latches to be cleared when the lower one is loaded (rather than being copied from the latches below). The upper bit of each instruction byte would indicate whether that byte is the last byte of an instruction. Instruction decode logic could thus very easily fetch an instruction with 7, 14, 21, or 28 bits of information for each instruction without having to look at more than one bit of the opcode.

I'd suggest for simplicity having a single accumulator and a few primary registers; as a typical pattern, have each instruction perform an ALU computation involving the accumulator and one secondary operand; allow the result to be sent back to the ALU or to the secondary operand. Secondary operand possibilities would include:

  • A "flags" register
  • An 8-bit immediate quantity encoded in the instruction
  • An address encoded in up to 16 bits of the instruction
  • An address formed by fetching a pair of consecutive bytes from memory at an address encoded in the instruction.

To allow for things like jump instructions, it may be helpful to have an "ALU operation" opcode which leaves the accumulator and flags unaffected, but copy a computed effective address to the program counter; using that opcode with one of the last two operand styles would yield a "jump". Additionally, since having an instruction try to store to an immediate value would be useless, opcodes whose bit definition would suggest that they would do so could be used to implement "conditional skip" instructions; such an instructions would set a latch which, when set, would cause the next instruction to have no effect other than to clear the "skip instruction" latch.

There will be a wide range of tradeoffs available between circuit complexity, speed, and usability. For example, it may be helpful to include some additional options for the secondary operand:

  • One of four 8-bit addressing registers (which operate as two pairs)
  • Memory at an address formed by taking a pair of registers or the program counter, adding a constant specified in the instruction, and optionally adding the accumulator as unsigned, signed, or unsigned minus 256.

Further, it may be helpful to add instructions to capture an effective address into one of the pairs of addressing registers. Such a feature would greatly improve the efficiency of many kinds of programs. On the other hand, adding such things would mean that one would be approaching the design complexity of a 16-bit processor, suggesting that perhaps one may as well build a 16-bit processor.


Your Answer

