I've designed and implemented a CPU architecture in one day. Of key architectural details: Processor is supposed to support updatable microcode, Architecture extensions and Is designed to be implemented sortof like a real CPU (at least in Logisim Evolution). As I was coding for speed, this 0.5 kiloline monster appeared to haunt me.
Is there a better way to implement the same functionality?
I have two programs built in. One counts the natural numbers, and the default one outputs the fibonacci sequence. To swap programs, replace pp(CPU(FIB))
with pp(CPU(COUNT))
, and vice-versa.
ISA:
ID | Opcode | Description |
---|---|---|
0 | ADD | A <-- A + B, overflow |
1 | JOTO | Jump if overflow, and toggle |
2 | LIT | B <-- Literal value following |
3 | LOAD | B <-- Mem[I] |
4 | STOR | Mem[I] <-- B |
5 | SWAP | Swap A and B registers |
6 | SWPI | Swap B and I registers |
7 | INC | A <-- A + 1, overflow |
8 | NOP | Clears overflow |
cpu.py
from enum import Enum, auto
from pprint import pp
from cpu_flags import (
ALU_ADD,
ALU_COMP,
ALU_INC,
ALU_LSH,
ALU_OUT,
ALU_REG_ENABLE,
ALU_REG_WRITE,
ALU_RSH,
ALU_XOR,
CLR_OV,
MEM_ENABLE,
MEM_WRITE,
MICRO_RESET,
PC_ENABLE,
PC_INC,
PC_WRITE,
REG_A_ENABLE,
REG_A_WRITE,
REG_B_ENABLE,
REG_B_WRITE,
REG_I_ENABLE,
REG_I_WRITE,
REG_IR_ENABLE,
REG_IR_WRITE,
SET_OV,
)
from cpu_inst import (
ADD,
AND,
CPU_INST,
INC,
JOTO,
LIT,
LOAD,
LSH,
NOP,
RSH,
STOR,
SWAP,
SWPI,
XOR,
)
from microcode import JOV_func, microcode
names = {
ADD: "ADD",
JOTO: "JOV",
LIT: "LIT",
LOAD: "LOAD",
STOR: "STOR",
SWAP: "SWAP",
SWPI: "SWPI",
INC: "INC",
NOP: "NOP",
XOR: "XOR",
AND: "AND",
LSH: "LSH",
RSH: "RSH",
}
class ISA_EXTS(Enum):
B = 1
S = 2
IO = 3
def generate_IA(extensions: set[ISA_EXTS]):
ia = {x for x in CPU_INST if x.value < 8}
if ISA_EXTS.B in extensions:
ia = ia.union((CPU_INST.XOR, CPU_INST.AND))
if ISA_EXTS.S in extensions:
ia = ia.union((CPU_INST.LSH, CPU_INST.RSH))
# if ISA_EXTS.IO in extensions:
# ia = ia.union((CPU_INST.IN, CPU_INST.OUT))
return sorted(ia)
MAX_VALUE = 1 << 300
isa_exts = {}
isa = generate_IA(isa_exts)
def CPU(MEM):
prog_counter = -1
reg_a = 0
reg_b = 0
reg_i = 0
reg_alu = 0
alu_out = 0
overflow = False
jumped = False
u = 0
instruction = NOP
new_instruction = NOP
old_bus = 0
bus = 0
flags = {}
inst_set = microcode[instruction]
while prog_counter < len(MEM):
# microcode[new_instruction]
old_bus = bus
bus = 0
# if prog_counter >= 18:
# print(u)
flags = inst_set[u]
# if u == 0:
# print(f"BUS:{bus}")
# print(
# f"pc:{prog_counter} {names[instruction]}, a:{reg_a}, b:{reg_b}, i:{reg_i}, o:{overflow}"
# )
# print(f"u:{u}, mem:{MEM[prog_counter - 2 : prog_counter + 3]}")
# print(flags)
if MICRO_RESET in flags:
u = 0
if not jumped:
instruction = new_instruction
else:
instruction = MEM[prog_counter]
inst_set = microcode[instruction]
if inst_set == JOV_func:
inst_set = JOV_func(overflow)
continue
if PC_INC in flags:
prog_counter += 1
if PC_ENABLE in flags:
bus = prog_counter
if REG_IR_ENABLE in flags:
bus = instruction
if MEM_ENABLE in flags:
# print(f"READMEM, mem:{MEM[old_bus - 2 : old_bus + 3]}")
bus = MEM[old_bus]
if REG_A_ENABLE in flags:
bus = reg_a
if REG_B_ENABLE in flags:
bus = reg_b
if REG_I_ENABLE in flags:
bus = reg_i
if ALU_REG_ENABLE in flags:
bus = reg_alu
if ALU_OUT in flags:
bus = alu_out
if PC_WRITE in flags:
# print("JUMP", bus)
jumped = True
prog_counter = bus
if REG_IR_WRITE in flags:
new_instruction = bus
if MEM_WRITE in flags:
MEM[old_bus] = bus
# print(f"WRITE MEM, mem:{MEM[old_bus - 2 : old_bus + 4]}")
if REG_A_WRITE in flags:
reg_a = bus
if REG_B_WRITE in flags:
reg_b = bus
if REG_I_WRITE in flags:
reg_i = bus
if ALU_REG_WRITE in flags:
reg_alu = bus
if ALU_ADD in flags:
b = reg_alu + bus
if b > MAX_VALUE:
overflow = True
b %= MAX_VALUE
alu_out = b
if ALU_XOR in flags:
alu_out = reg_alu ^ bus
if ALU_INC in flags:
alu_out = reg_alu + 1
if alu_out >= MAX_VALUE:
overflow = True
alu_out %= MAX_VALUE
if CLR_OV in flags:
overflow = False
if SET_OV in flags:
overflow = True
# ALU_COMP = CPU_FLAG.ALU_COMP
# ALU_AND = CPU_FLAG.ALU_AND
# ALU_LSH = CPU_FLAG.ALU_LSH
u += 1
return MEM
# Normal instruction
# FETCH, EXECUTE, INC
def create_prog(prog, length=1000):
return prog + (length - len(prog)) * [0]
# Fibonacci
FIB = create_prog(
[
LIT,
65,
SWPI, # 0, 0, 65
INC, # 1, 0
SWAP, # 0, 1
STOR,
SWAP, # 1, 0
LOAD, # 1, 1
ADD, # 2, 1, 64
SWAP, # 1, 2, 64
STOR, #
LIT,
28,
JOTO, # Terminate if overflow
NOP,
LOAD,
SWPI, # a + b, 64, b
SWAP, # 64, a + b, b
INC, # 65, a + b, b
SWAP,
SWPI, # a + b, b, 65
STOR, # Stored A+B at next I
LIT,
1000,
JOTO,
LIT,
7,
JOTO,
LIT,
0,
STOR,
LIT,
1000,
JOTO,
JOTO,
],
length=512,
)
COUNT = create_prog(
[
LIT,
24,
SWPI,
LIT,
0,
INC, # 1, 0, 16
SWAP, # 0, 1, 16
STOR, # Write to loc 16
SWPI, # 0, 16, 1
SWAP, # 16, 0, 1
INC, # 17, 0, 1
SWAP, # 0, 17, 1
SWPI, # 0, 1, 17
SWAP, # 1, 0, 17
LIT,
1000,
JOTO,
LIT,
3,
JOTO,
]
)
pp(CPU(FIB))
cpu_inst.py
from enum import Enum
class CPU_INST(Enum):
ADD = 0
JOV = 1
LIT = 2
LOAD = 3
STOR = 4
SWAP = 5
SWPI = 6
INC = 7
NOP = 8
XOR = 9
AND = 10
LSH = 11
RSH = 12
def __lt__(self, o):
return self.value < o.value
ADD = 0
JOTO = 1
LIT = 2
LOAD = 3
STOR = 4
SWAP = 5
SWPI = 6
INC = 7
NOP = 8
XOR = 9
AND = 10
LSH = 11
RSH = 12
# cpu_flags.py
from enum import Enum, auto
class CPU_FLAG(Enum):
MEM_ENABLE = auto()
MEM_WRITE = auto()
MICRO_RESET = auto()
PC_INC = auto()
PC_ENABLE = auto()
PC_WRITE = auto()
REG_IR_ENABLE = auto()
REG_IR_WRITE = auto()
REG_A_ENABLE = auto()
REG_A_WRITE = auto()
REG_B_ENABLE = auto()
REG_B_WRITE = auto()
REG_I_ENABLE = auto()
REG_I_WRITE = auto()
ALU_REG_ENABLE = auto()
ALU_REG_WRITE = auto()
ALU_COMP = auto()
ALU_ADD = auto()
ALU_AND = auto()
ALU_XOR = auto()
ALU_INC = auto()
ALU_LSH = auto()
ALU_RSH = auto()
ALU_OUT = auto()
CLR_OV = auto()
SET_OV = auto()
MEM_ENABLE = CPU_FLAG.MEM_ENABLE
MEM_WRITE = CPU_FLAG.MEM_WRITE
MICRO_RESET = CPU_FLAG.MICRO_RESET
PC_INC = CPU_FLAG.PC_INC
PC_ENABLE = CPU_FLAG.PC_ENABLE
PC_WRITE = CPU_FLAG.PC_WRITE
REG_IR_ENABLE = CPU_FLAG.REG_IR_ENABLE
REG_IR_WRITE = CPU_FLAG.REG_IR_WRITE
REG_A_ENABLE = CPU_FLAG.REG_A_ENABLE
REG_A_WRITE = CPU_FLAG.REG_A_WRITE
REG_B_ENABLE = CPU_FLAG.REG_B_ENABLE
REG_B_WRITE = CPU_FLAG.REG_B_WRITE
REG_I_ENABLE = CPU_FLAG.REG_I_ENABLE
REG_I_WRITE = CPU_FLAG.REG_I_WRITE
ALU_REG_ENABLE = CPU_FLAG.ALU_REG_ENABLE
ALU_REG_WRITE = CPU_FLAG.ALU_REG_WRITE
ALU_COMP = CPU_FLAG.ALU_COMP
ALU_ADD = CPU_FLAG.ALU_ADD
ALU_XOR = CPU_FLAG.ALU_XOR
ALU_AND = CPU_FLAG.ALU_AND
ALU_LSH = CPU_FLAG.ALU_LSH
ALU_RSH = CPU_FLAG.ALU_RSH
ALU_INC = CPU_FLAG.ALU_INC
ALU_OUT = CPU_FLAG.ALU_OUT
CLR_OV = CPU_FLAG.CLR_OV
SET_OV = CPU_FLAG.SET_OV
microcode.py
from cpu_flags import (
MEM_ENABLE,
MEM_WRITE,
MICRO_RESET,
PC_INC,
PC_ENABLE,
PC_WRITE,
REG_IR_ENABLE,
REG_IR_WRITE,
REG_A_ENABLE,
REG_A_WRITE,
REG_B_ENABLE,
REG_B_WRITE,
REG_I_ENABLE,
REG_I_WRITE,
ALU_REG_ENABLE,
ALU_REG_WRITE,
ALU_COMP,
ALU_ADD,
ALU_XOR,
ALU_INC,
ALU_LSH,
ALU_RSH,
ALU_OUT,
CLR_OV,
SET_OV,
)
from cpu_inst import (
ADD,
JOTO,
LIT,
LOAD,
STOR,
SWAP,
SWPI,
INC,
NOP,
XOR,
AND,
LSH,
RSH,
)
# Jump if overflow, toggle overflow
def JOV_func(overflow):
if overflow:
return [
{REG_B_ENABLE, PC_WRITE},
{CLR_OV},
{MICRO_RESET},
]
return [
# No Overflow {}
{PC_INC},
{PC_ENABLE},
{MEM_ENABLE, REG_IR_WRITE, SET_OV},
{MICRO_RESET},
]
# ADD operation
# FETCH, EXECUTE, STORE/INC
microcode = {
ADD: [
{REG_A_ENABLE, ALU_REG_WRITE, CLR_OV},
{REG_B_ENABLE, ALU_ADD},
{ALU_OUT, REG_A_WRITE},
{PC_INC},
{PC_ENABLE},
{MEM_ENABLE, REG_IR_WRITE},
{MICRO_RESET},
],
JOTO: JOV_func,
LIT: [
{PC_INC},
{PC_ENABLE},
{MEM_ENABLE, REG_B_WRITE},
{PC_INC},
{PC_ENABLE},
{MEM_ENABLE, REG_IR_WRITE},
{MICRO_RESET},
],
LOAD: [
{REG_I_ENABLE},
{MEM_ENABLE, REG_B_WRITE},
{PC_INC},
{PC_ENABLE},
{MEM_ENABLE, REG_IR_WRITE},
{MICRO_RESET},
],
STOR: [
{REG_I_ENABLE},
{MEM_WRITE, REG_B_ENABLE},
{PC_INC},
{PC_ENABLE},
{MEM_ENABLE, REG_IR_WRITE},
{MICRO_RESET},
],
SWAP: [
{ALU_REG_WRITE, REG_A_ENABLE},
{REG_A_WRITE, REG_B_ENABLE},
{REG_B_WRITE, ALU_REG_ENABLE},
{PC_INC},
{PC_ENABLE},
{MEM_ENABLE, REG_IR_WRITE},
{MICRO_RESET},
],
SWPI: [
{ALU_REG_WRITE, REG_B_ENABLE},
{REG_B_WRITE, REG_I_ENABLE},
{REG_I_WRITE, ALU_REG_ENABLE},
{PC_INC},
{PC_ENABLE},
{MEM_ENABLE, REG_IR_WRITE},
{MICRO_RESET},
],
INC: [
{REG_A_ENABLE, ALU_REG_WRITE, CLR_OV},
{ALU_INC},
{ALU_OUT, REG_A_WRITE},
{PC_INC},
{PC_ENABLE},
{MEM_ENABLE, REG_IR_WRITE},
{MICRO_RESET},
],
NOP: [
{PC_INC, CLR_OV},
{PC_ENABLE},
{MEM_ENABLE, REG_IR_WRITE},
{MICRO_RESET},
],
}
NOP
seems to be the wrong opcode name. Consider calling itCLRV
, for clear overflow. \$\endgroup\$