feat: Implement simple base machine with add and addi instructions

Signed-off-by: Christoph Heiss <contact@christoph-heiss.at>
This commit is contained in:
Christoph Heiss 2022-03-26 23:10:43 +01:00
parent 8cbd199e1e
commit 3fea46f4d6
Signed by: c8h4
GPG key ID: 9C82009BEEDEA0FF
2 changed files with 264 additions and 8 deletions

251
teuthida/__init__.py Normal file
View file

@ -0,0 +1,251 @@
from amaranth import *
from enum import IntEnum
DEFAULT_XLEN = 64
class AluOp(IntEnum):
ADD = 0
class Alu(Elaboratable):
def __init__(self, xlen=DEFAULT_XLEN):
# Inputs
self.en = Signal()
self.op = Signal(range(len(AluOp)))
self.in1 = Signal(xlen)
self.in2 = Signal(xlen)
# Outputs
self.out = Signal(xlen)
def elaborate(self, _):
m = Module()
with m.If(self.en == 1):
with m.Switch(self.op):
with m.Case(AluOp.ADD):
m.d.comb += self.out.eq(self.in1 + self.in2)
return m
class Registeregf(Elaboratable):
def __init__(self, xlen=DEFAULT_XLEN):
# State
self.regs = Array([Signal(xlen)] * 30) # x1 - x31
self.pc = Signal(xlen) # x32
# Inputs
self.sel1 = Signal(5)
self.sel2 = Signal(5)
self.wren = Signal()
self.wrsel = Signal(5)
self.wrval = Signal(xlen)
# Outputs
self.out1 = Signal(xlen)
self.out2 = Signal(xlen)
def elaborate(self, _):
m = Module()
with m.Switch(self.sel1):
with m.Case(0):
# TODO: Optimize away everywhere else
m.d.comb += self.out1.eq(0)
with m.Default():
m.d.comb += self.out1.eq(self.regs[self.sel1 - 1])
with m.Switch(self.sel2):
with m.Case(0):
m.d.comb += self.out2.eq(0)
with m.Default():
m.d.comb += self.out2.eq(self.regs[self.sel2 - 1])
with m.If(self.wren == 1):
# TODO: Eliminate need for checking for x0
with m.If((self.wrsel > 0) & (self.wrsel < 31)):
m.d.comb += self.regs[self.wrsel - 1].eq(self.wrval)
return m
class BootRom(Elaboratable):
def __init__(self, xlen=DEFAULT_XLEN):
# Inputs
self.addr = Signal(xlen)
# Outputs
self.out = Signal(xlen)
# State
data = [
0x0000_0533, # add a0, x0, x0
0x0420_0593, # addi a1, x0, 0x42 (aka li a1, 0x42)
0x00b5_0533, # add a0, a0, a1
]
self.mem = Memory(width=xlen, depth=len(data), init=data)
def elaborate(self, _):
m = Module()
with m.If(self.addr[2:] < self.mem.depth):
m.d.comb += self.out.eq(self.mem[self.addr[2:]])
return m
class Opcode(IntEnum):
REG = 0b0110011 # Register-only inst
IMM = 0b0010011 # Immediate inst
class Funct3(IntEnum):
IMM_ADDI = 0b000
class Funct7(IntEnum):
REG_ADD = 0b000_0000
class InstructionDecoder(Elaboratable):
def __init__(self, alu, regf, xlen=DEFAULT_XLEN):
# Links
self.alu = alu
self.regf = regf
# Inputs
self.inst = Signal(xlen)
# Outputs
self.illegal = Signal()
self.alu_en = Signal()
self.rd = Signal(5)
self.regwrite = Signal()
def elaborate(self, _):
m = Module()
#
# | 32 25 | 24 20 | 19 15 | 14 12 | 11 7 | 6 0 |
# +--------------+---------+---------+--------+--------+--------------+
# R | funct7 | rs2 | rs1 | funct3 | rd | opcode |
# +--------------+---------+---------+--------+--------+--------------+
# I | immediate [11:0] | rs1 | funct3 | rd | opcode |
# +------------------------+---------+--------+--------+--------------+
#
#
op = Signal(7)
rs1 = Signal(5)
rs2 = Signal(5)
funct3 = Signal(3)
funct7 = Signal(7)
imm = Signal(12)
m.d.comb += [
op.eq(self.inst[:7]),
rs1.eq(self.inst[15:20]),
rs2.eq(self.inst[20:25]),
funct3.eq(self.inst[12:15]),
funct7.eq(self.inst[25:32]),
imm.eq(self.inst[20:32]),
self.regf.sel1.eq(rs1),
self.alu.in1.eq(self.regf.out1),
self.rd.eq(self.inst[7:12]),
]
with m.Switch(op):
with m.Case(Opcode.REG):
m.d.comb += self.regwrite.eq(1);
with m.Switch(funct7):
with m.Case(Funct7.REG_ADD): # add rd, rs1, rs2
m.d.comb += [
self.regf.sel2.eq(rs2),
self.alu.in2.eq(self.regf.out2),
self.alu.op.eq(AluOp.ADD),
self.alu_en.eq(1),
]
with m.Default():
m.d.comb += self.illegal.eq(1)
with m.Case(Opcode.IMM):
m.d.comb += self.regwrite.eq(1);
with m.Switch(funct3):
with m.Case(Funct3.IMM_ADDI):
m.d.comb += [
self.alu.in2.eq(imm),
self.alu.op.eq(AluOp.ADD),
self.alu_en.eq(1),
]
with m.Default():
m.d.comb += self.illegal.eq(1)
with m.Default():
m.d.comb += self.illegal.eq(1)
return m
class PipelineStage(IntEnum):
FETCH = 0
DECODE = 1
EXECUTE = 2
MEMACCESS = 3
REGWRITE = 4
class Cpu(Elaboratable):
def __init__(self, xlen=DEFAULT_XLEN):
self.cycles = Signal(xlen)
self.stage = Signal(range(len(PipelineStage)))
self.halt = Signal()
def elaborate(self, platform):
m = Module()
alu = m.submodules.alu = Alu()
bootrom = m.submodules.bootrom = BootRom()
regf = m.submodules.regf = Registeregf()
dec = m.submodules.dec = InstructionDecoder(alu, regf)
with m.If(self.halt == 0):
with m.Switch(self.stage):
with m.Case(PipelineStage.FETCH):
m.d.sync += [
# Reset possibly-modifiying state
alu.en.eq(0),
regf.wren.eq(0),
regf.pc.eq(regf.pc + 4),
bootrom.addr.eq(regf.pc),
dec.inst.eq(bootrom.out),
]
with m.Case(PipelineStage.DECODE):
# Fully halt in case there an illegal instruction is encountered
m.d.sync += self.halt.eq(dec.illegal)
with m.Case(PipelineStage.EXECUTE):
m.d.sync += [
alu.en.eq(dec.alu_en),
]
with m.Case(PipelineStage.MEMACCESS):
m.d.sync += [
# alu.en.eq(0),
]
with m.Case(PipelineStage.REGWRITE):
with m.If(dec.regwrite):
m.d.sync += [
regf.wrsel.eq(dec.rd),
regf.wrval.eq(alu.out),
regf.wren.eq(1),
]
with m.If(self.stage == PipelineStage.REGWRITE):
m.d.sync += [
self.stage.eq(PipelineStage.FETCH),
self.cycles.eq(self.cycles + 1),
]
with m.Else():
m.d.sync += self.stage.eq(self.stage + 1)
return m

View file

@ -1,13 +1,18 @@
from amaranth import *
from nmigen.back.pysim import Simulator, Delay, Settle
def process():
yield
from . import Cpu, PipelineStage
def start():
m = Module()
sim = Simulator(m)
sim.add_clock(1e-6) # 1 MHz
sim.add_process(process)
cpu = Cpu()
sim.run()
def process():
# Run for 10 cycles
for _ in range(10 * len(PipelineStage)):
yield
sim = Simulator(cpu)
sim.add_clock(1e-6) # 1 MHz
sim.add_sync_process(process)
with sim.write_vcd("teuthida.vcd", "teuthida.gtkw"):
sim.run()