class Core(ElaboratableAbstract): def __init__(self, clock, look_ahead=1, addr_length=32, xlen=32, include_enable=False, include_debug_opcode=1): assert addr_length % 8 == 0, "address length must be octet aligned" assert xlen % 8 == 0, "register width must be octet aligned" assert look_ahead >= 1, "Core should see at least one full word ahead" self.look_ahead = look_ahead super().__init__() self.clock = clock # register width self.xlen = xlen # addr_length holds width of address bus self.addr_length = addr_length # add mem2core bus for reads self.mem2core = mem2core = MemoryBus(addr_length, xlen, "mem2core") self.add_existing_output_signal(mem2core.addr) self.add_existing_output_signal(mem2core.en) self.add_existing_output_signal(mem2core.seq) self.add_existing_input_signal(mem2core.ready) self.add_existing_input_signal(mem2core.value) # add core2mem bus for writes self.core2mem = core2mem = MemoryBus(addr_length, xlen, "core2mem") self.add_existing_output_signal(core2mem.addr) self.add_existing_output_signal(core2mem.en) self.add_existing_output_signal(core2mem.seq) self.add_existing_output_signal(core2mem.value) self.add_existing_input_signal(mem2core.ready) # instruction implementation contains actual implementation of instructions self.instructions: List[Instruction] = [] # is enabled is an optional input pin that can pauses RISCV self.is_enabled = Signal(name="en") if include_enable else None # debug opcode is an optional output signal which shows what instruction was executed on last cycle self.debug_opcode = self.add_output_signal( DebugOpcode, name="dbg_op") if include_debug_opcode else None self.debug_value = self.add_output_signal( xlen, name="dbg_val") if include_debug_opcode else None self.iclk = None self.current_module: Module = None self.itype = IType("itype") self.utype = UType("utype") self.jtype = JType("jtype") self.btype = BType("btype") self.in_reset = Signal(reset=1) self.last_instruction = Signal(32) self.last_instruction_valid = Signal( ) #if true, continue execution from last_instruction, otherwise from input[0] self.current_instruction_valid = Signal() self.have_valid_instruction = Signal() self.current_instruction = Signal(32) self.cycle = Signal(4) self.next_pc = Signal(xlen) self.advance_pc = Signal() self.alu = ALU(self.xlen, "alu") self.left_shifter = Shifter(xlen, Shifter.LEFT, "SL") self.right_shifter = Shifter(xlen, Shifter.RIGHT, "SR") self.register_file = RegisterFileModule(xlen) self.pc = Signal( xlen, name="pc" ) #TODO: remove from register file? use additional signals in regfile? def add_instruction(self, implementation): self.instructions.append(implementation) implementation.core = self return implementation def elaborate(self, p: Platform) -> Module: m = Module() self.current_module = m m.submodules.alu = self.alu m.submodules.shl = self.left_shifter m.submodules.shr = self.right_shifter m.submodules.regs = self.register_file self.iclk = m.d.i m.d.comb += self.last_instruction_valid.eq(self.cycle != 0) with m.If((self.cycle == 0) & (self.mem2core.ready)): m.d.comb += self.current_instruction.eq(self.mem2core.value) m.d.comb += self.current_instruction_valid.eq(1) with m.Else(): m.d.comb += self.current_instruction.eq(self.last_instruction) m.d.comb += self.current_instruction_valid.eq(0) m.d.comb += self.have_valid_instruction.eq( self.current_instruction_valid | self.last_instruction_valid) self.itype.elaborate(m.d.comb, self.current_instruction) self.utype.elaborate(m.d.comb, self.current_instruction) self.btype.elaborate(m.d.comb, self.current_instruction) self.jtype.elaborate(m.d.comb, self.current_instruction) m.d.comb += self.alu.en.eq(0) m.d.comb += self.advance_pc.eq(0) m.d.comb += self.next_pc.eq(0) if self.is_enabled is None: self.elaborate_impl(p) else: with m.If(self.is_enabled): self.elaborate_impl(p) self.advance_pc_if_needed() self.iclk += self.last_instruction.eq(self.current_instruction) return m def query_rs1(self, idx=None): """ Query register file throught RS1 port. If no index provided, rs1 from the current instruction is used """ if idx is None: idx = self.itype.rs1 comb = self.current_module.d.comb comb += self.register_file.rs1_in.eq(idx) return self.register_file.rs1_out def query_rs2(self, idx=None): """ Query register file throught RS2 port. If no index provided, rs2 from the current instruction is used """ if idx is None: idx = self.btype.rs2 comb = self.current_module.d.comb comb += self.register_file.rs2_in.eq(idx) return self.register_file.rs2_out def elaborate_impl(self, p: Platform) -> Module: m = self.current_module #comb = m.d.comb iclk = self.iclk self.emit_debug_opcode(DebugOpcode.NOT_SPECIFIED, 0) with m.If(self.in_reset): iclk += self.in_reset.eq(0) iclk += self.pc.eq(0x200) self.mem2core.init_read(iclk, 0x200, 1) self.emit_debug_opcode(DebugOpcode.IN_RESET) with m.Elif(self.have_valid_instruction): # Run instruction if data is ready first = True for instr in self.instructions: if_inst = m.If if first else m.Elif with if_inst(instr.check()): instr.implement() first = False with m.Else(): self.emit_debug_opcode(DebugOpcode.INVALID) self.move_pc_to_next_instr() #TODO: add reg instruction_executed and check it instead? with m.Else(): self.mem2core.init_read(self.iclk, self.pc, 1) self.emit_debug_opcode(DebugOpcode.AWAIT_READ) return m def call_alu(self, func: OpAlu, lhs: Statement, rhs: Statement): """ Call ALU and return its output wire """ comb = self.current_module.d.comb comb += self.alu.lhs.eq(lhs) comb += self.alu.rhs.eq(rhs) comb += self.alu.op.eq(func) comb += self.alu.en.eq(1) return self.alu.output def call_left_shift(self, rs: Value, shamt: Statement): """ Call SHIFT-LEFT module and return its output wire """ comb = self.current_module.d.comb comb += self.left_shifter.input.eq(rs) comb += self.left_shifter.shamt.eq(shamt) return self.left_shifter.output def call_right_shift(self, rs: Value, shamt: Statement, msb: Statement): """ Call SHIFT-RIGHT module and return its output wire """ comb = self.current_module.d.comb comb += self.right_shifter.input.eq(rs) comb += self.right_shifter.msb.eq(msb) comb += self.right_shifter.shamt.eq(shamt) return self.right_shifter.output def emit_debug_opcode(self, op: DebugOpcode, x: Optional[Value] = None): if self.debug_opcode is not None: domain = self.current_module.d.comb domain += self.debug_opcode.eq(op) if x is not None: domain += self.debug_value.eq(x) def assign_pc(self, new_pc_value): self.current_module.d.comb += self.next_pc.eq(new_pc_value) self.current_module.d.comb += self.advance_pc.eq(1) def move_pc_to_next_instr(self): """ Move PC to the start of the next instruction """ self.assign_pc(self.pc + 4) def assign_gpr(self, idx: Value, value: Value): # R0 will be reassigned to 0 at the end of elaborate() comb = self.current_module.d.comb comb += self.register_file.rd.eq(idx) comb += self.register_file.rd_value.eq(value) def advance_pc_if_needed(self): m = self.current_module with m.If(self.advance_pc): self.iclk += self.pc.eq(self.next_pc) self.schedule_read(self.next_pc, 1) self.iclk += self.cycle.eq(0) def schedule_read(self, addr, seq): self.mem2core.init_read(self.iclk, addr, seq) def make_fakemem(self, m: Module, mem: Dict[int, int]): comb: List[Statement] = m.d.comb with m.If(self.mem2core.en): with m.Switch(self.mem2core.addr): for address, value in mem.items(): with m.Case(address): word_value = value | ( mem.get(address + 1, 0xff) << 8) | ( mem.get(address + 2, 0xff) << 16) | ( mem.get(address + 3, 0xff) << 24) comb += self.mem2core.value.eq(word_value) with m.Default(): comb += self.mem2core.value.eq(0xFFFFFFFF) comb += self.mem2core.ready.eq(1) with m.Else(): comb += self.mem2core.ready.eq(1) def simulate(self, top: Module, clk: ClockInfo, mem: Dict[int, int], n=30, filename_prefix="waves/test"): rst = clk.rst self.make_fakemem(top, mem) dump_inputs(self, top) def timings(): yield rst.eq(1) yield yield rst.eq(0) for _ in range(n): yield sim = Simulator(top) sim.add_clock(muS, domain="i") sim.add_sync_process(timings, domain="i") with sim.write_vcd(f"{filename_prefix}.vcd", f"{filename_prefix}.gtkw", traces=self.ports()): sim.run()
class VerificationRegisterFile: def capture(self, m: Core, core: Core, past: int): comb = m.d.comb if past > 0: prefix = f"past{past}" else: prefix = "now" self.r = RegisterFile(core.xlen, prefix=prefix) for i in range(self.r.main_gpr_count()): comb += self.r[i].eq(Past(core.register_file.r[i], past)) comb += self.r.pc.eq(Past(core.pc, past)) # TODO: move to additional structure self.itype = IType(prefix=f"{prefix}_i") self.itype.elaborate(comb, Past(core.current_instruction, past)) self.jtype = JType(prefix=f"{prefix}_j") self.jtype.elaborate(comb, Past(core.current_instruction, past)) self.utype = UType(prefix=f"{prefix}_u") self.utype.elaborate(comb, Past(core.current_instruction, past)) self.btype = BType(prefix=f"{prefix}_b") self.btype.elaborate(comb, Past(core.current_instruction, past)) # TODO: membus self.input_ready = Signal.like(core.mem2core.ready, name=f"{prefix}_input_ready") self.input_data = Array([ Signal(core.xlen, name=f"{prefix}_input_{i}") for i in range(core.look_ahead) ]) self.cycle = Signal.like(core.cycle, name=f"{prefix}_cycle") comb += self.cycle.eq(Past(core.cycle, past)) # TODO: move to structure self.mem2core_addr = Signal.like(core.mem2core.addr, name=f"{prefix}_mem2core_addr") self.mem2core_en = Signal.like(core.mem2core.en, name=f"{prefix}_mem2core_en") self.mem2core_seq = Signal.like(core.mem2core.seq, name=f"{prefix}_mem2core_seq") comb += self.mem2core_addr.eq(Past(core.mem2core.addr, past)) comb += self.mem2core_en.eq(Past(core.mem2core.en, past)) comb += self.mem2core_seq.eq(Past(core.mem2core.seq, past)) comb += self.input_ready.eq(Past(core.mem2core.ready, past)) comb += self.input_data[0].eq(Past(core.mem2core.value, past)) def at_instruction_start(self): return (self.cycle == 0) & (self.input_ready[0]) def assert_loading_from(self, m: Core, addr, src_loc_at=1): comb = m.d.comb comb += Assert(self.mem2core_en, src_loc_at=src_loc_at) comb += Assert(self.mem2core_addr == addr, src_loc_at=src_loc_at) def assert_same_gpr(self, m: Core, other: RegisterFile, src_loc_at=1): comb = m.d.comb for i in range(self.r.main_gpr_count()): comb += Assert(self.r[i] == other[i], src_loc_at=src_loc_at) def assert_same_gpr_but_one(self, m: Module, other: RegisterFile, skip: Value, src_loc_at=1): comb = m.d.comb for i in range(self.r.main_gpr_count()): with m.If(skip != i): comb += Assert(self.r[i] == other[i], src_loc_at=src_loc_at) def assert_gpr_value(self, m: Module, idx: Value, expected_value: Value, src_loc_at=1): """ Assert GPR value (ignored for idx = 0 and zeri is checked instead) """ comb = m.d.comb with m.If(idx == 0): comb += Assert(self.r[0] == 0, src_loc_at=src_loc_at) with m.Else(): comb += Assert(self.r[idx] == expected_value, src_loc_at=src_loc_at) def assert_pc_advanced(self, m: Module, previous: RegisterFile, src_loc_at=1): comb = m.d.comb comb += Assert(self.r.pc == (previous.pc + 4)[:self.r.pc.width], src_loc_at=src_loc_at) def assert_same_pc(self, m: Module, previous: RegisterFile, src_loc_at=1): comb = m.d.comb comb += Assert(self.r.pc == previous.pc, src_loc_at=src_loc_at)