runt/core.py
David Lenfesty e850430a8c """Finished""" base RV32I instructions
Still a few base instructions to go, but those are waiting on the
privileged implementation. My game plan:

- implement privileged spec (M-only), adding way to handle traps
- finish implementing last of the base instructions
- add in exceptions that have been ignored in all instructions
- clean up main memory interface, mmaybe add some caching, at least some
  instruction cache.
2021-05-29 19:42:11 -06:00

299 lines
12 KiB
Python

from nmigen import *
from nmigen.hdl.rec import Layout, Record
from enum import Enum
from opcodes import Opcodes
from instruction_decoder import InstructionDecoder
from regfile import RegisterFile
# TODO replace this with a proper wishbone bus
class DumbMemoryBus(Record):
def __init__(self):
super().__init__(Layout([
("rw", 1),
("addr", unsigned(32)),
("data", unsigned(32)),
("i_valid", 1),
("o_ready", 1),
]))
class IntImmediate(Enum):
# Integer opperations on immediates, as defined by funct3 field
ADDI = 0b000
SLTI = 0b010
SLTIU = 0b011
XORI = 0b100
ORI = 0b110
ANDI = 0b111
SLLI = 0b001
SRxI = 0b101
class IntRegReg(Enum):
# Integer register-register operations, as defined by funct3 field
ADD = 0b000 # Also SUB
SLL = 0b001
SLT = 0b010
SLTU = 0b011
XOR = 0b100
SRx = 0b101 # SRL/SRA
OR = 0b110
AND = 0b111
class BranchCondition(Enum):
# Different BRANCH conditions, as defined by funct3 field
BEQ = 0b000
BNE = 0b001
BLT = 0b100
BGE = 0b101
BLTU = 0b110
BGEU = 0b111
class LSWidth(Enum):
B = 0b00
H = 0b01
W = 0b10
class RV32ICore(Elaboratable):
"""Basic RV32-I core."""
def __init__(self):
self.mem = DumbMemoryBus()
self.decoder = InstructionDecoder()
def elaborate(self, platform):
m = Module()
m.submodules.decoder = self.decoder
m.submodules.regfile = RegisterFile()
regfile = m.submodules.regfile
pc = Signal(unsigned(32))
instr = Signal(unsigned(32)) # Internal reg to hold instrunction data
decoder_ports = self.decoder.ports()
funct3 = decoder_ports[2]
imm = decoder_ports[3]
immu = decoder_ports[4]
src = decoder_ports[5]
dest = decoder_ports[6]
m.d.comb += self.decoder.instr.eq(instr)
m.d.sync += regfile.wen.eq(0)
m.d.sync += self.mem.i_valid.eq(0)
# Load/store variables
load_dest = Signal(unsigned(5))
load_unsigned = Signal()
ls_width = Signal(LSWidth)
with m.FSM():
with m.State("READ_PC"):
# Issue memory read to PC
m.d.sync += self.mem.rw.eq(0)
m.d.sync += self.mem.addr.eq(pc)
m.d.sync += self.mem.i_valid.eq(1)
m.next = "LOAD_PC"
with m.State("LOAD_PC"):
m.d.sync += self.mem.i_valid.eq(0)
with m.If(self.mem.o_ready):
m.d.sync += instr.eq(self.mem.data)
m.next = "DECODE"
with m.State("DECODE"):
# TODO likely integer ops should be deferred and split into a seperate ALU,
# Where instead of doing everything here, I load all the values into the ALU
# and pull from the results.
# TODO should I pull the src1/src2/dest assignments out to all the time?
with m.Switch(self.decoder.opcode):
with m.Case(Opcodes.OP_IMM):
m.next = "READ_PC"
m.d.comb += regfile.raddr1.eq(src)
m.d.comb += regfile.waddr.eq(dest)
m.d.sync += regfile.wen.eq(1)
with m.Switch(funct3):
with m.Case(IntImmediate.ADDI):
m.d.sync += regfile.wdata.eq(regfile.rdata1 + imm)
with m.Case(IntImmediate.SLTI):
m.d.sync += regfile.wdata.eq(regfile.rdata1 < imm)
with m.Case(IntImmediate.SLTIU):
# TODO evaluate if this casts correctly and does what I want
m.d.sync += regfile.wdata.eq(regfile.rdata1.as_unsigned() < immu)
with m.Case(IntImmediate.ANDI):
m.d.sync += regfile.wdata.eq(regfile.rdata1 & immu)
with m.Case(IntImmediate.ORI):
m.d.sync += regfile.wdata.eq(regfile.rdata1 | immu)
with m.Case(IntImmediate.XORI):
m.d.sync += regfile.wdata.eq(regfile.rdata1 ^ immu)
with m.Case(IntImmediate.SLLI):
m.d.sync += regfile.wdata.eq(regfile.rdata1.as_unsigned() << immu[0:4])
with m.Case(IntImmediate.SRxI):
with m.If(immu[10]):
# SRAI
m.d.sync += regfile.wdata.eq(regfile.rdata1.as_unsigned() >> immu[0:4])
with m.Else():
# SRLI
m.d.sync += regfile.wdata.eq(regfile.rdata1 >> immu[0:4])
with m.Case(Opcodes.LUI):
m.d.comb += regfile.waddr.eq(dest)
m.d.sync += regfile.wdata.eq(immu)
m.d.sync += regfile.wen.eq(1)
with m.Case(Opcodes.AUIPC):
m.d.comb += regfile.waddr.eq(dest)
m.d.sync += regfile.wdata.eq(pc + immu)
with m.Case(Opcodes.OP):
m.next = "READ_PC"
m.d.comb += regfile.raddr1.eq(self.decoder.src1)
m.d.comb += regfile.raddr2.eq(self.decoder.src2)
m.d.comb += regfile.waddr.eq(self.decoder.dest)
m.d.sync += regfile.wen.eq(1)
with m.Switch(self.decoder.funct7):
with m.Case(IntRegReg.ADD):
with m.If(self.decoder.funct7[5]):
# SUB
m.d.sync += regfile.wdata.eq(regfile.rdata1 - regfile.rdata2)
with m.Else():
# ADD
m.d.sync += regfile.wdata.eq(regfile.rdata1 + regfile.rdata2)
with m.Case(IntRegReg.SLL):
m.d.sync += regfile.wdata.eq(regfile.rdata1 << regfile.rdata2[0:4])
with m.Case(IntRegReg.SLT):
m.d.sync += regfile.wdata.eq(regfile.rdata1 < regfile.rdata2)
with m.Case(IntRegReg.SLTU):
rdata1 = regfile.rdata1.as_unsigned()
rdata2 = regfile.rdata2.as_unsigned()
m.d.sync += regfile.wdata.eq(rdata1 < rdata2)
with m.Case(IntRegReg.XOR):
m.d.sync += regfile.wdata.eq(regfile.rdata1 ^ regfile.rdata2)
with m.Case(IntRegReg.SRx):
with m.If(self.decoder.funct7[5]):
# SRA
m.d.sync += regfile.wdata.eq(regfile.rdata1 << regfile.rdata2[0:4])
with m.Else():
# SRL
m.d.sync += regfile.wdata.eq(regfile.rdata1.as_unsigned() << regfile.rdata2[0:4])
with m.Case(IntRegReg.OR):
m.d.sync += regfile.wdata.eq(regfile.rdata1 | regfile.rdata2)
with m.Case(IntRegReg.AND):
m.d.sync += regfile.wdata.eq(regfile.rdata1 & regfile.rdata2)
# TODO raise misalign exception
with m.Case(Opcodes.JAL):
m.d.comb += regfile.waddr.eq(self.decoder.dest)
m.d.sync += regfile.wdata.eq(pc + 4)
m.d.sync += regfile.wen.eq(1)
m.d.sync += pc.eq(pc + self.decoder.imm)
# TODO raise misalign exception
with m.Case(Opcodes.JALR):
m.d.comb += regfile.waddr.eq(self.decoder.dest)
m.d.comb += regfile.raddr1.eq(self.decoder.src1)
m.d.sync += regfile.wdata.eq(pc + 4)
m.d.sync += regfile.wen.eq(1)
m.d.sync += pc.eq(regfile.rdata1 + self.decoder.imm)
# TODO misalign exception
with m.Case(Opcodes.BRANCH):
m.d.comb += regfile.raddr1.eq(self.decoder.src1)
m.d.comb += regfile.raddr2.eq(self.decoder.src2)
branch_condition = Signal()
with m.Switch(self.decoder.funct3):
with m.Case(BranchCondition.BEQ):
m.d.comb += branch_condition.eq(regfile.rdata1 == regfile.rdata2)
with m.Case(BranchCondition.BNE):
m.d.comb += branch_condition.eq(regfile.rdata1 != regfile.rdata2)
with m.Case(BranchCondition.BLT):
m.d.comb += branch_condition.eq(regfile.rdata1 < regfile.rdata2)
with m.Case(BranchCondition.BGE):
m.d.comb += branch_condition.eq(regfile.rdata1 >= regfile.rdata2)
with m.Case(BranchCondition.BLTU):
m.d.comb += branch_condition.eq(regfile.rdata1.as_unsigned() < regfile.rdata2.as_unsigned())
with m.Case(BranchCondition.BGEU):
m.d.comb += branch_condition.eq(regfile.rdata1.as_unsigned() >= regfile.rdata2.as_unsigned())
with m.If(branch_condition):
m.d.sync += pc.eq(pc + self.decoder.imm)
with m.Case(Opcodes.LOAD):
m.d.comb += regfile.raddr1.eq(self.decoder.src1)
m.d.sync += load_dest.eq(self.decoder.dest)
with m.If(~self.mem.o_ready):
m.next = "LOAD"
m.d.sync += self.mem.rw.eq(0)
m.d.sync += self.mem.addr.eq(regfile.rdata1 + self.decoder.imm)
m.d.sync += self.mem.i_valid.eq(1)
m.d.sync += ls_width.eq(self.decoder.funct3[0:1])
m.d.sync += load_unsigned.eq(self.decoder.funct3[2])
with m.Case(Opcodes.STORE):
m.d.comb += regfile.raddr1.eq(self.decoder.src1)
m.d.comb += regfile.raddr2.eq(self.decoder.base)
with m.If(~self.mem.o_ready):
m.next = "STORE"
m.d.sync += self.mem.rw.eq(1)
m.d.sync += self.mem.addr.eq(regfile.rdata2 + self.decoder.imm)
m.d.sync += self.mem.data.eq(regfile.rdata1)
m.d.sync += self.mem.i_valid.eq(1)
m.d.sync += ls_width.eq(self.decoder.funct3[0:1])
with m.Case(Opcodes.MISC_MEM):
with m.If(self.decoder.funct3 == 0):
# TODO impl NOP
pass
with m.Else():
# TODO raise invalid instruction
pass
with m.Case(Opcodes.SYSTEM):
# TODO need to impl privileged stuff before I can do this
pass
# TODO raise invalid instruction
with m.Default():
pass
with m.State("LOAD"):
m.d.comb += regfile.waddr.eq(load_dest)
m.d.sync += regfile.wdata.eq(self.mem.data)
with m.If(self.mem.o_ready):
m.d.sync += regfile.wen.eq(1)
m.next = "READ_PC"
with m.State("STORE"):
# TODO this shouldn't be necessary, just temp until memory is handled properly
m.next = "READ_PC"
return m
if __name__ == "__main__":
from nmigen.cli import main
top = RV32ICore()
main(top)