├── README.md ├── .gitignore ├── shift_card.sby ├── transparent_latch.sby ├── alu_card.sby ├── async_memory.sby ├── ics.sby ├── reg_card.sby ├── util.py ├── Makefile ├── IC_GAL.py ├── irq_load_rom.py ├── formal_cpu.sby ├── transparent_latch.py ├── exc_card.py ├── IC_7416244.py ├── alu_card.py ├── async_memory.py ├── trap_rom.py ├── shift_card.py ├── consts.py ├── irq_card.py ├── IC_7416374.py ├── reg_card.py ├── pla_parser.py ├── ics.py ├── sequencer_rom.py └── sequencer_card.py /README.md: -------------------------------------------------------------------------------- 1 | riscv-reboot 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # RTLIL files 7 | *.il 8 | 9 | # Verification runs 10 | *-bmc 11 | *_bmc 12 | *-prove 13 | *_prove 14 | *-cover 15 | *_cover 16 | *.gtkw 17 | 18 | # VSCode nonsense 19 | .vscode 20 | -------------------------------------------------------------------------------- /shift_card.sby: -------------------------------------------------------------------------------- 1 | [tasks] 2 | cover 3 | bmc 4 | 5 | [options] 6 | bmc: mode bmc 7 | cover: mode cover 8 | depth 1 9 | multiclock off 10 | 11 | [engines] 12 | smtbmc z3 13 | 14 | [script] 15 | read_verilog < sim will run YourClass.sim and output to whatever vcd 36 | file you wrote to. 37 | python gen will run YourClass.formal and output in RTLIL format 38 | to toplevel.il. You can then formally verify using 39 | sby -f . 40 | """ 41 | 42 | if len(sys.argv) < 2 or (sys.argv[1] != "sim" and sys.argv[1] != "gen" and sys.argv[1] != "rtl"): 43 | print(f"Usage: python {sys.argv[0]} sim|gen|rtl") 44 | sys.exit(1) 45 | 46 | if sys.argv[1] == "sim": 47 | cls.sim() 48 | return 49 | 50 | design = None 51 | ports = [] 52 | 53 | if sys.argv[1] == "rtl": 54 | design, ports = cls.toRTL() 55 | else: 56 | design, ports = cls.formal() 57 | 58 | fragment = Fragment.get(design, None) 59 | output = rtlil.convert(fragment, ports=ports) 60 | with open(filename, "w") as f: 61 | f.write(output) 62 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Simple Makefile which just runs through all the ALL targets 2 | # and does BMC and prove on them. 3 | 4 | ALL1 := op op_imm lui auipc 5 | ALL2 := jal jalr branch csr ecall 6 | ALL3 := lb lbu lh lhu lw sb sh sw fatal1 fatal2 fatal3 fatal4 irq 7 | BMC1 := $(patsubst %,%-bmc,$(ALL1)) 8 | BMC2 := $(patsubst %,%-bmc,$(ALL2)) 9 | BMC3 := $(patsubst %,%-bmc,$(ALL3)) 10 | PROVE1 := $(patsubst %,%-prove,$(ALL1)) 11 | PROVE2 := $(patsubst %,%-prove,$(ALL2)) 12 | PROVE3 := $(patsubst %,%-prove,$(ALL3)) 13 | 14 | # Here we specify the longest-running targets first, so they get priority 15 | # when parallelizing with make -j. In theory, if there's one of those long-running 16 | # targets which takes the longest time, it will have started approximately first. 17 | ALLBMC := $(BMC3) $(BMC2) $(BMC1) 18 | ALLPROVE := $(PROVE3) $(PROVE2) $(PROVE1) 19 | ALL := $(ALLBMC) $(ALLPROVE) 20 | 21 | SRCS := formal_cpu.py sequencer_card.py reg_card.py shift_card.py alu_card.py 22 | SRCS += transparent_latch.py async_memory.py util.py consts.py 23 | 24 | all: | $(ALLPROVE) 25 | @for i in $(ALL1) $(ALL2) $(ALL3); do \ 26 | DIR="formal_cpu_$$i-prove"; \ 27 | if [ -e "$$DIR" -a -f "$$DIR/status" ]; then \ 28 | printf "%-15s: %s\n" "$$i-prove" "`cat $$DIR/status`"; \ 29 | else \ 30 | printf "%-15s: %s\n" "$$i-prove" "UNCOMPLETED"; \ 31 | fi; \ 32 | done 33 | 34 | clean: cleanbmc cleanprove 35 | 36 | cover: $(SRCS) 37 | python3 formal_cpu.py gen 38 | sby -f formal_cpu.sby cover 39 | 40 | cleanbmc: 41 | @for i in $(ALLBMC); do \ 42 | rm -f formal_cpu_$(patsubst %-bmc,%,$$i).il; \ 43 | rm -rf formal_cpu_$$i; \ 44 | done 45 | 46 | cleanprove: 47 | @for i in $(ALLPROVE); do \ 48 | rm -f formal_cpu_$(patsubst %-prove,%,$$i).il; \ 49 | rm -rf formal_cpu_$$i; \ 50 | done 51 | 52 | %-prove: formal_cpu_%.il 53 | sby -f formal_cpu.sby $@ 54 | 55 | formal_cpu_%.il: VERIFY = $(patsubst formal_cpu_%.il,%,$@) 56 | formal_cpu_%.il: $(SRCS) 57 | python3 formal_cpu.py gen $(VERIFY) 58 | 59 | %-bmc: %-bmc/.done 60 | printf "\n" 61 | 62 | %-bmc/.done: VERIFY = $(patsubst %-bmc/.done,%,$@) 63 | %-bmc/.done: formal_cpu_%.il 64 | sby -f formal_cpu.sby $(VERIFY)-bmc 65 | touch formal_cpu_$(VERIFY)-bmc/.done 66 | -------------------------------------------------------------------------------- /IC_GAL.py: -------------------------------------------------------------------------------- 1 | # Disable pylint's "your name is too short" warning. 2 | # pylint: disable=C0103 3 | # Disable protected access warnings 4 | # pylint: disable=W0212 5 | from typing import List, Tuple 6 | 7 | from nmigen import Signal, Module, Elaboratable, signed, ClockSignal, ClockDomain, Array 8 | from nmigen.build import Platform 9 | from nmigen.asserts import Assert, Assume, Cover, Stable, Past 10 | 11 | from util import main 12 | 13 | 14 | class IC_GAL_imm_format_decoder(Elaboratable): 15 | """Contains logic for a 7416244 16-bit buffer. 16 | """ 17 | 18 | def __init__(self): 19 | # Inputs 20 | self.opcode = Signal(7) 21 | 22 | # Outputs 23 | self.i_n_oe = Signal() 24 | self.s_n_oe = Signal() 25 | self.u_n_oe = Signal() 26 | self.b_n_oe = Signal() 27 | self.j_n_oe = Signal() 28 | self.sys_n_oe = Signal() 29 | 30 | def elaborate(self, _: Platform) -> Module: 31 | """Implements the logic of the buffer.""" 32 | m = Module() 33 | 34 | # OE_I = 0000011 | 0001111 | 0010011 35 | # OE_S = 0100011 36 | # OE_U = 0010111 | 0110111 37 | # OE_B = 1100011 38 | # OE_J = 1100111 | 1101111 39 | # OE_SYS = 1110011 40 | 41 | m.d.comb += [ 42 | self.i_n_oe.eq(1), 43 | self.s_n_oe.eq(1), 44 | self.u_n_oe.eq(1), 45 | self.b_n_oe.eq(1), 46 | self.j_n_oe.eq(1), 47 | self.sys_n_oe.eq(1), 48 | ] 49 | with m.Switch(self.opcode): 50 | with m.Case(0b0000011, 0b0001111, 0b0010011): # I 51 | m.d.comb += self.i_n_oe.eq(0) 52 | with m.Case(0b0100011): # S 53 | m.d.comb += self.s_n_oe.eq(0) 54 | with m.Case(0b0010111, 0b0110111): # U 55 | m.d.comb += self.u_n_oe.eq(0) 56 | with m.Case(0b1100011): 57 | m.d.comb += self.b_n_oe.eq(0) 58 | with m.Case(0b1100111, 0b1101111): 59 | m.d.comb += self.j_n_oe.eq(0) 60 | with m.Default(): 61 | m.d.comb += self.sys_n_oe.eq(0) 62 | return m 63 | 64 | 65 | if __name__ == "__main__": 66 | main(IC_GAL_imm_format_decoder) 67 | -------------------------------------------------------------------------------- /irq_load_rom.py: -------------------------------------------------------------------------------- 1 | # Disable pylint's "your name is too short" warning. 2 | # pylint: disable=C0103 3 | # Disable protected access warnings 4 | # pylint: disable=W0212 5 | from nmigen import Signal, Module, Elaboratable 6 | from nmigen.build import Platform 7 | 8 | from util import all_true 9 | 10 | 11 | class IrqLoadInstrROM(Elaboratable): 12 | """ROM for the interrupt/load_instr sequencer card state machine.""" 13 | 14 | def __init__(self): 15 | # Inputs (4 bits) 16 | 17 | self.is_interrupted = Signal() 18 | self._instr_phase = Signal(2) 19 | self.enable_sequencer_rom = Signal() 20 | 21 | # Outputs (4 bits) 22 | 23 | self.load_trap = Signal() 24 | self.next_trap = Signal() 25 | self._load_instr = Signal(reset=1) 26 | self.mem_rd = Signal() 27 | 28 | def elaborate(self, _: Platform) -> Module: 29 | """Implements the logic of the interrupt/load_instr sequencer ROM.""" 30 | m = Module() 31 | 32 | # Defaults 33 | m.d.comb += [ 34 | self.load_trap.eq(0), 35 | self.next_trap.eq(0), 36 | self._load_instr.eq(0), 37 | self.mem_rd.eq(0), 38 | ] 39 | 40 | with m.If(self.enable_sequencer_rom): 41 | 42 | # 3 independent cases: 43 | # 44 | # 1. We were interrupted and the instruction is complete. 45 | # 2. We're at the beginning of an instruction, and not interrupted. 46 | # 3. Instruction handling 47 | # 48 | # Maybe we can handle the first two with just some OR gates 49 | # on the output signals. 50 | 51 | # True when instr_complete and ~trap and (mei or mti pending). 52 | with m.If(self.is_interrupted): 53 | # m.d.comb += self._pc_to_z.eq(1) # Does this do anything? 54 | # m.d.comb += self._next_instr_phase.eq(0) 55 | m.d.comb += self.load_trap.eq(1) 56 | m.d.comb += self.next_trap.eq(1) 57 | 58 | # Load the instruction on instruction phase 0 unless we've been interrupted. 59 | with m.If(all_true(self._instr_phase == 0, ~self.is_interrupted)): 60 | m.d.comb += self._load_instr.eq(1) 61 | m.d.comb += self.mem_rd.eq(1) 62 | 63 | return m 64 | -------------------------------------------------------------------------------- /formal_cpu.sby: -------------------------------------------------------------------------------- 1 | [tasks] 2 | cover 3 | --pycode-begin-- 4 | for t in "bmc prove".split(): 5 | for o in "op op_imm lui auipc jal jalr branch csr lb lbu lh lhu lw sb sh sw fatal1 fatal2 fatal3 fatal4 fatal5 fatal6 irq ecall".split(): 6 | output(f"{o}-{t}") 7 | --pycode-end-- 8 | 9 | [options] 10 | --pycode-begin-- 11 | for t in "bmc prove".split(): 12 | for o in "op op_imm lui auipc jal jalr branch csr lb lbu lh lhu lw sb sh sw fatal1 fatal2 fatal3 fatal4 fatal5 fatal6 irq ecall".split(): 13 | output(f"{o}-{t}: mode {t}") 14 | --pycode-end-- 15 | 16 | multiclock on 17 | cover: mode cover 18 | cover: depth 50 19 | 20 | --pycode-begin-- 21 | if task != "cover": 22 | ts = task.split("-") 23 | o = ts[0] 24 | t = ts[1] 25 | if o in "op op_imm lui auipc fatal4".split(): 26 | output(f"{o}-bmc: depth 7") 27 | output(f"{o}-prove: depth 6") 28 | elif o in "jal jalr branch csr ecall".split(): 29 | output(f"{o}-bmc: depth 13") 30 | output(f"{o}-prove: depth 12") 31 | elif o in "lb lbu lh lhu lw sb sh sw irq".split(): 32 | output(f"{o}-bmc: depth 19") 33 | output(f"{o}-prove: depth 18") 34 | elif o in "fatal1 fatal2 fatal3".split(): 35 | output(f"{o}-bmc: depth 19") 36 | output(f"{o}-prove: depth 18") 37 | --pycode-end-- 38 | 39 | [engines] 40 | cover: smtbmc boolector 41 | 42 | --pycode-begin-- 43 | if task != "cover": 44 | ts = task.split("-") 45 | o = ts[0] 46 | t = ts[1] 47 | if t == "bmc": 48 | output(f"{task}: smtbmc z3") 49 | else: 50 | output(f"{task}: smtbmc z3") 51 | --pycode-end-- 52 | 53 | [script] 54 | read_verilog < 0 37 | self.data_in = Signal(size) 38 | self.data_out = Signal(size) 39 | self.le = Signal() 40 | self.n_oe = Signal() 41 | 42 | self.size = size 43 | 44 | def elaborate(self, _: Platform) -> Module: 45 | """Implements the logic of a transparent latch.""" 46 | m = Module() 47 | 48 | internal_reg = Signal(self.size, reset=0, reset_less=True) 49 | 50 | # Local clock domain so we can clock data into the 51 | # internal memory on the negative edge of le. 52 | le_clk = ClockDomain("le_clk", clk_edge="neg", local=True) 53 | m.domains.le_clk = le_clk 54 | le_clk.clk = self.le 55 | 56 | m.d.le_clk += internal_reg.eq(self.data_in) 57 | m.d.comb += self.data_out.eq(Mux(self.n_oe, 0, internal_reg)) 58 | with m.If(self.le & ~self.n_oe): 59 | m.d.comb += self.data_out.eq(self.data_in) 60 | 61 | return m 62 | 63 | @classmethod 64 | def sim(cls): 65 | """A quick simulation of the transparent latch.""" 66 | m = Module() 67 | m.submodules.latch = latch = TransparentLatch(32) 68 | 69 | sim = Simulator(m) 70 | 71 | def process(): 72 | yield latch.n_oe.eq(1) 73 | yield latch.le.eq(1) 74 | yield Delay(1e-6) 75 | yield latch.data_in.eq(0xAAAA1111) 76 | yield Delay(1e-6) 77 | yield latch.data_in.eq(0x1111AAAA) 78 | yield Delay(1e-6) 79 | yield latch.le.eq(0) 80 | yield Delay(1e-6) 81 | yield latch.data_in.eq(0xAAAA1111) 82 | yield Delay(1e-6) 83 | yield latch.le.eq(1) 84 | yield Delay(1e-6) 85 | 86 | sim.add_process(process) 87 | with sim.write_vcd("latch.vcd"): 88 | sim.run_until(10e-6) 89 | 90 | @classmethod 91 | def formal(cls) -> Tuple[Module, List[Signal]]: 92 | """Formal verification for the transparent latch. 93 | 94 | Note that you MUST have multiclock on in the sby file, because there is 95 | more than one clock in the system -- the default formal clock and the 96 | local clock inside the transparent latch. 97 | """ 98 | m = Module() 99 | m.submodules.latch = latch = TransparentLatch(32) 100 | 101 | m.d.sync += Cover((latch.data_out == 0xAAAAAAAA) & (latch.le == 0) 102 | & (Past(latch.data_out, 2) == 0xBBBBBBBB) 103 | & (Past(latch.le, 2) == 0)) 104 | 105 | with m.If(latch.n_oe == 1): 106 | m.d.comb += Assert(latch.data_out == 0) 107 | 108 | with m.If((latch.n_oe == 0) & (latch.le == 1)): 109 | m.d.comb += Assert(latch.data_out == latch.data_in) 110 | 111 | with m.If((latch.n_oe == 0) & Fell(latch.le)): 112 | m.d.sync += Assert(latch.data_out == Past(latch.data_in)) 113 | 114 | return m, [latch.data_in, latch.le, latch.n_oe] 115 | 116 | 117 | if __name__ == "__main__": 118 | main(TransparentLatch) 119 | -------------------------------------------------------------------------------- /exc_card.py: -------------------------------------------------------------------------------- 1 | # Disable pylint's "your name is too short" warning. 2 | # pylint: disable=C0103 3 | from typing import List 4 | 5 | from nmigen import Signal, Module, Elaboratable 6 | from nmigen.build import Platform 7 | 8 | from IC_7416374 import IC_reg32_with_mux 9 | from consts import CSRAddr 10 | 11 | 12 | class ExcCard(Elaboratable): 13 | """Logic for the exception card. 14 | 15 | Attributes: 16 | data_x_in: The X bus, read from. 17 | data_x_out: The X bus, written to. 18 | data_y_in: The Y bus, always read from. 19 | data_z_in: The Z bus, always read from. 20 | csr_num: The CSR number for access. 21 | csr_to_x: Read the CSR (output to X). 22 | z_to_csr: Write the CSR (input from Z). 23 | save_trap_csrs: Signal that the buses should be saved to the CSRs. 24 | 25 | The card holds the MCAUSE, MEPC, and MTVAL registers. Aside from responding 26 | to the normal CSR read/write instructions, it can also store X -> MCAUSE, 27 | Y -> MEPC, and Z -> MTVAL when requested. 28 | """ 29 | 30 | data_x_in: Signal 31 | data_x_out: Signal 32 | data_y_in: Signal 33 | data_z_in: Signal 34 | csr_num: Signal 35 | csr_to_x: Signal 36 | z_to_csr: Signal 37 | save_trap_csrs: Signal 38 | 39 | def __init__(self, ext_init: bool = False): 40 | """Constructs an exception card.""" 41 | self._ext_init = ext_init 42 | 43 | # Buses 44 | self.data_x_in = Signal(32) 45 | self.data_x_out = Signal(32) 46 | self.data_y_in = Signal(32) 47 | self.data_z_in = Signal(32) 48 | 49 | # Controls 50 | self.csr_num = Signal(CSRAddr) 51 | self.csr_to_x = Signal() 52 | self.z_to_csr = Signal() 53 | self.save_trap_csrs = Signal() 54 | 55 | # Internals 56 | self._mcause = Signal(32) 57 | self._mepc = Signal(32) 58 | self._mtval = Signal(32) 59 | 60 | def elaborate(self, _: Platform) -> Module: 61 | """Implements the logic of the exception card.""" 62 | m = Module() 63 | 64 | self.multiplex_to_reg(m, clk="ph2w", reg=self._mcause, 65 | sels=[ 66 | self.z_to_csr & ( 67 | self.csr_num == CSRAddr.MCAUSE), 68 | self.save_trap_csrs 69 | ], 70 | sigs=[self.data_z_in, self.data_x_in]) 71 | self.multiplex_to_reg(m, clk="ph2w", reg=self._mepc, 72 | sels=[ 73 | self.z_to_csr & ( 74 | self.csr_num == CSRAddr.MEPC), 75 | self.save_trap_csrs 76 | ], 77 | sigs=[self.data_z_in, self.data_y_in]) 78 | self.multiplex_to_reg(m, clk="ph2w", reg=self._mtval, 79 | sels=[ 80 | (self.z_to_csr & (self.csr_num == CSRAddr.MTVAL)) | 81 | self.save_trap_csrs, 82 | ], 83 | sigs=[self.data_z_in]) 84 | 85 | m.d.comb += self.data_x_out.eq(0) 86 | with m.If(self.csr_to_x & ~self.save_trap_csrs): 87 | with m.Switch(self.csr_num): 88 | with m.Case(CSRAddr.MCAUSE): 89 | m.d.comb += self.data_x_out.eq(self._mcause) 90 | with m.Case(CSRAddr.MEPC): 91 | m.d.comb += self.data_x_out.eq(self._mepc) 92 | with m.Case(CSRAddr.MTVAL): 93 | m.d.comb += self.data_x_out.eq(self._mtval) 94 | 95 | return m 96 | 97 | def multiplex_to_reg(self, m: Module, clk: str, reg: Signal, sels: List[Signal], sigs: List[Signal]): 98 | """Sets up a multiplexer with a register. 99 | 100 | clk is the clock domain on which the register is clocked. 101 | 102 | reg is the register signal. 103 | 104 | sels is an array of Signals which select that input for the multiplexer (active high). If 105 | no select is active, then the register retains its value. 106 | 107 | sigs is an array of Signals which are the inputs to the multiplexer. 108 | """ 109 | assert len(sels) == len(sigs) 110 | 111 | muxreg = IC_reg32_with_mux( 112 | clk=clk, N=len(sels), ext_init=self._ext_init, faster=True) 113 | m.submodules += muxreg 114 | m.d.comb += reg.eq(muxreg.q) 115 | for i in range(len(sels)): 116 | m.d.comb += muxreg.n_sel[i].eq(~sels[i]) 117 | m.d.comb += muxreg.d[i].eq(sigs[i]) 118 | -------------------------------------------------------------------------------- /IC_7416244.py: -------------------------------------------------------------------------------- 1 | """Module containing stuff made out of 7416244 16-bit buffers.""" 2 | # Disable pylint's "your name is too short" warning. 3 | # pylint: disable=C0103 4 | # Disable protected access warnings 5 | # pylint: disable=W0212 6 | from typing import List, Tuple 7 | 8 | from nmigen import Signal, Module, Elaboratable, Array 9 | from nmigen.build import Platform 10 | from nmigen.asserts import Assert 11 | 12 | from util import main 13 | 14 | 15 | class IC_7416244_sub(Elaboratable): 16 | """Contains logic for a 7416244 4-bit buffer. 17 | """ 18 | 19 | def __init__(self): 20 | self.a = Signal(4) 21 | self.n_oe = Signal() 22 | self.y = Signal(4) 23 | 24 | def elaborate(self, _: Platform) -> Module: 25 | """Implements the logic of the buffer.""" 26 | m = Module() 27 | 28 | m.d.comb += self.y.eq(0) 29 | with m.If(self.n_oe == 0): 30 | m.d.comb += self.y.eq(self.a) 31 | 32 | return m 33 | 34 | 35 | class IC_7416244(Elaboratable): 36 | """Contains logic for a 7416244 16-bit buffer. 37 | """ 38 | 39 | def __init__(self): 40 | # Inputs 41 | self.a0 = Signal(4) 42 | self.a1 = Signal(4) 43 | self.a2 = Signal(4) 44 | self.a3 = Signal(4) 45 | 46 | # Output enables 47 | self.n_oe0 = Signal() 48 | self.n_oe1 = Signal() 49 | self.n_oe2 = Signal() 50 | self.n_oe3 = Signal() 51 | 52 | # Outputs 53 | self.y0 = Signal(4) 54 | self.y1 = Signal(4) 55 | self.y2 = Signal(4) 56 | self.y3 = Signal(4) 57 | 58 | def elaborate(self, _: Platform) -> Module: 59 | """Implements the logic of the buffer.""" 60 | m = Module() 61 | 62 | s0 = IC_7416244_sub() 63 | s1 = IC_7416244_sub() 64 | s2 = IC_7416244_sub() 65 | s3 = IC_7416244_sub() 66 | 67 | m.submodules += [s0, s1, s2, s3] 68 | 69 | m.d.comb += s0.a.eq(self.a0) 70 | m.d.comb += s1.a.eq(self.a1) 71 | m.d.comb += s2.a.eq(self.a2) 72 | m.d.comb += s3.a.eq(self.a3) 73 | 74 | m.d.comb += s0.n_oe.eq(self.n_oe0) 75 | m.d.comb += s1.n_oe.eq(self.n_oe1) 76 | m.d.comb += s2.n_oe.eq(self.n_oe2) 77 | m.d.comb += s3.n_oe.eq(self.n_oe3) 78 | 79 | m.d.comb += self.y0.eq(s0.y) 80 | m.d.comb += self.y1.eq(s1.y) 81 | m.d.comb += self.y2.eq(s2.y) 82 | m.d.comb += self.y3.eq(s3.y) 83 | 84 | return m 85 | 86 | @classmethod 87 | def formal(cls) -> Tuple[Module, List[Signal]]: 88 | m = Module() 89 | s = IC_7416244() 90 | 91 | m.submodules += s 92 | 93 | with m.If(s.n_oe0): 94 | m.d.comb += Assert(s.y0 == 0) 95 | with m.Else(): 96 | m.d.comb += Assert(s.y0 == s.a0) 97 | with m.If(s.n_oe1): 98 | m.d.comb += Assert(s.y1 == 0) 99 | with m.Else(): 100 | m.d.comb += Assert(s.y1 == s.a1) 101 | with m.If(s.n_oe2): 102 | m.d.comb += Assert(s.y2 == 0) 103 | with m.Else(): 104 | m.d.comb += Assert(s.y2 == s.a2) 105 | with m.If(s.n_oe3): 106 | m.d.comb += Assert(s.y3 == 0) 107 | with m.Else(): 108 | m.d.comb += Assert(s.y3 == s.a3) 109 | 110 | return m, [s.a0, s.a1, s.a2, s.a3, s.n_oe0, s.n_oe1, s.n_oe2, s.n_oe3] 111 | 112 | 113 | class IC_buff32(Elaboratable): 114 | """A pair of 7416244s, with OEs tied together.""" 115 | 116 | def __init__(self): 117 | self.a = Signal(32) 118 | self.n_oe = Signal() 119 | self.y = Signal(32) 120 | 121 | def elaborate(self, _: Platform) -> Module: 122 | """Implements the logic of a 32-bit buffer. 123 | 124 | Made out of a pair of 7416244s. 125 | """ 126 | m = Module() 127 | 128 | buffs = [IC_7416244(), IC_7416244()] 129 | m.submodules += buffs 130 | 131 | for i in range(2): 132 | m.d.comb += buffs[i].n_oe0.eq(self.n_oe) 133 | m.d.comb += buffs[i].n_oe1.eq(self.n_oe) 134 | m.d.comb += buffs[i].n_oe2.eq(self.n_oe) 135 | m.d.comb += buffs[i].n_oe3.eq(self.n_oe) 136 | 137 | for i in range(2): 138 | m.d.comb += buffs[i].a0.eq(self.a[16*i:16*i+4]) 139 | m.d.comb += buffs[i].a1.eq(self.a[16*i+4:16*i+8]) 140 | m.d.comb += buffs[i].a2.eq(self.a[16*i+8:16*i+12]) 141 | m.d.comb += buffs[i].a3.eq(self.a[16*i+12:16*i+16]) 142 | 143 | for i in range(2): 144 | m.d.comb += self.y[16*i:16*i+4].eq(buffs[i].y0) 145 | m.d.comb += self.y[16*i+4:16*i+8].eq(buffs[i].y1) 146 | m.d.comb += self.y[16*i+8:16*i+12].eq(buffs[i].y2) 147 | m.d.comb += self.y[16*i+12:16*i+16].eq(buffs[i].y3) 148 | 149 | return m 150 | 151 | 152 | class IC_mux32(Elaboratable): 153 | """An N-input 32-bit multiplexer made of 7416244s. 154 | 155 | Select lines are separate. Activating more than one is a really 156 | bad idea. 157 | """ 158 | 159 | def __init__(self, N: int, faster: bool = False): 160 | self.N = N 161 | self.a = Array([Signal(32, name=f"mux_in{i}") for i in range(N)]) 162 | self.n_sel = Signal(N) 163 | self.y = Signal(32) 164 | 165 | self._faster = faster 166 | 167 | def elaborate(self, _: Platform) -> Module: 168 | """Implements the logic of an N-input 32-bit multiplexer.""" 169 | m = Module() 170 | 171 | if self._faster: 172 | m.d.comb += self.y.eq(0) 173 | for i in range(self.N): 174 | with m.If(~self.n_sel[i]): 175 | m.d.comb += self.y.eq(self.a[i]) 176 | return m 177 | 178 | buffs = [IC_buff32() for _ in range(self.N)] 179 | m.submodules += buffs 180 | 181 | for i in range(self.N): 182 | m.d.comb += buffs[i].a.eq(self.a[i]) 183 | m.d.comb += buffs[i].n_oe.eq(self.n_sel[i]) 184 | 185 | combine = 0 186 | for i in range(0, self.N): 187 | combine |= buffs[i].y 188 | m.d.comb += self.y.eq(combine) 189 | 190 | return m 191 | 192 | 193 | if __name__ == "__main__": 194 | main(IC_7416244) 195 | -------------------------------------------------------------------------------- /alu_card.py: -------------------------------------------------------------------------------- 1 | # Disable pylint's "your name is too short" warning. 2 | # pylint: disable=C0103 3 | from typing import List, Tuple 4 | 5 | from nmigen import Signal, Module, Elaboratable 6 | from nmigen.build import Platform 7 | from nmigen.asserts import Assert 8 | 9 | from consts import AluOp 10 | from transparent_latch import TransparentLatch 11 | from util import main 12 | 13 | 14 | class AluCard(Elaboratable): 15 | """Logic for the ALU card. 16 | 17 | Attributes: 18 | data_x: The X bus, always read from. 19 | data_y: The Y bus, always read from. 20 | data_z: The Z bus, written to when ALU is active. 21 | alu_op: The ALU op to perform. 22 | alu_eq: Set if the result of a SUB is zero. 23 | alu_lt: Set if the result of a signed SUB is less than zero. 24 | alu_ltu: Set if the result of an unsigned SUB is less than zero. 25 | """ 26 | 27 | data_x: Signal 28 | data_y: Signal 29 | data_z: Signal 30 | alu_op: Signal 31 | alu_eq: Signal 32 | alu_lt: Signal 33 | alu_ltu: Signal 34 | 35 | def __init__(self): 36 | # Buses 37 | self.data_x = Signal(32) 38 | self.data_y = Signal(32) 39 | self.data_z = Signal(32) 40 | 41 | # Controls 42 | self.alu_op = Signal(AluOp) 43 | 44 | # Outputs for branch compares using SUB: 45 | self.alu_eq = Signal() 46 | self.alu_lt = Signal() 47 | self.alu_ltu = Signal() 48 | 49 | def elaborate(self, _: Platform) -> Module: 50 | """Implements the logic of the ALU card.""" 51 | m = Module() 52 | 53 | output_buffer = TransparentLatch(size=32) 54 | 55 | m.submodules += output_buffer 56 | m.d.comb += output_buffer.le.eq(1) 57 | m.d.comb += output_buffer.data_in.eq(0) 58 | m.d.comb += self.data_z.eq(output_buffer.data_out) 59 | 60 | m.d.comb += self.alu_eq.eq(0) 61 | m.d.comb += self.alu_lt.eq(0) 62 | m.d.comb += self.alu_ltu.eq(0) 63 | 64 | x = self.data_x 65 | y = self.data_y 66 | 67 | m.d.comb += self.alu_eq.eq(output_buffer.data_in == 0) 68 | m.d.comb += self.alu_ltu.eq(x < y) 69 | m.d.comb += self.alu_lt.eq(x.as_signed() < y.as_signed()) 70 | 71 | with m.Switch(self.alu_op): 72 | with m.Case(AluOp.ADD): 73 | m.d.comb += output_buffer.data_in.eq(x + y) 74 | m.d.comb += output_buffer.n_oe.eq(0) 75 | 76 | with m.Case(AluOp.SUB): 77 | m.d.comb += output_buffer.data_in.eq(x - y) 78 | m.d.comb += output_buffer.n_oe.eq(0) 79 | 80 | with m.Case(AluOp.SLTU): 81 | m.d.comb += output_buffer.data_in.eq(self.alu_ltu) 82 | m.d.comb += output_buffer.n_oe.eq(0) 83 | 84 | with m.Case(AluOp.SLT): 85 | m.d.comb += output_buffer.data_in.eq(self.alu_lt) 86 | m.d.comb += output_buffer.n_oe.eq(0) 87 | 88 | with m.Case(AluOp.AND): 89 | m.d.comb += output_buffer.data_in.eq(x & y) 90 | m.d.comb += output_buffer.n_oe.eq(0) 91 | 92 | with m.Case(AluOp.AND_NOT): 93 | m.d.comb += output_buffer.data_in.eq(x & ~y) 94 | m.d.comb += output_buffer.n_oe.eq(0) 95 | 96 | with m.Case(AluOp.OR): 97 | m.d.comb += output_buffer.data_in.eq(x | y) 98 | m.d.comb += output_buffer.n_oe.eq(0) 99 | 100 | with m.Case(AluOp.XOR): 101 | m.d.comb += output_buffer.data_in.eq(x ^ y) 102 | m.d.comb += output_buffer.n_oe.eq(0) 103 | 104 | with m.Case(AluOp.X): 105 | m.d.comb += output_buffer.data_in.eq(x) 106 | m.d.comb += output_buffer.n_oe.eq(0) 107 | 108 | with m.Case(AluOp.Y): 109 | m.d.comb += output_buffer.data_in.eq(y) 110 | m.d.comb += output_buffer.n_oe.eq(0) 111 | 112 | with m.Default(): 113 | m.d.comb += output_buffer.n_oe.eq(1) 114 | 115 | return m 116 | 117 | @classmethod 118 | def formal(cls) -> Tuple[Module, List[Signal]]: 119 | """Formal verification for the ALU.""" 120 | m = Module() 121 | m.submodules.alu = alu = AluCard() 122 | 123 | with m.Switch(alu.alu_op): 124 | with m.Case(AluOp.ADD): 125 | m.d.comb += Assert(alu.data_z == 126 | (alu.data_x + alu.data_y)[:32]) 127 | 128 | with m.Case(AluOp.SUB): 129 | m.d.comb += [ 130 | Assert(alu.data_z == 131 | (alu.data_x - alu.data_y)[:32]), 132 | Assert(alu.alu_eq == (alu.data_x == alu.data_y)), 133 | Assert(alu.alu_ltu == (alu.data_x < alu.data_y)), 134 | Assert(alu.alu_lt == (alu.data_x.as_signed() 135 | < alu.data_y.as_signed())), 136 | ] 137 | 138 | with m.Case(AluOp.AND): 139 | m.d.comb += Assert(alu.data_z == (alu.data_x & alu.data_y)) 140 | 141 | with m.Case(AluOp.AND_NOT): 142 | m.d.comb += Assert(alu.data_z == 143 | (alu.data_x & ~alu.data_y)) 144 | 145 | with m.Case(AluOp.OR): 146 | m.d.comb += Assert(alu.data_z == (alu.data_x | alu.data_y)) 147 | 148 | with m.Case(AluOp.XOR): 149 | m.d.comb += Assert(alu.data_z == (alu.data_x ^ alu.data_y)) 150 | 151 | with m.Case(AluOp.SLTU): 152 | with m.If(alu.data_x < alu.data_y): 153 | m.d.comb += Assert(alu.data_z == 1) 154 | with m.Else(): 155 | m.d.comb += Assert(alu.data_z == 0) 156 | 157 | with m.Case(AluOp.SLT): 158 | with m.If(alu.data_x.as_signed() < alu.data_y.as_signed()): 159 | m.d.comb += Assert(alu.data_z == 1) 160 | with m.Else(): 161 | m.d.comb += Assert(alu.data_z == 0) 162 | 163 | with m.Case(AluOp.X): 164 | m.d.comb += Assert(alu.data_z == alu.data_x) 165 | 166 | with m.Case(AluOp.Y): 167 | m.d.comb += Assert(alu.data_z == alu.data_y) 168 | 169 | with m.Default(): 170 | m.d.comb += Assert(alu.data_z == 0) 171 | 172 | return m, [alu.alu_op, alu.data_x, alu.data_y, alu.data_z] 173 | 174 | 175 | if __name__ == "__main__": 176 | main(AluCard) 177 | -------------------------------------------------------------------------------- /async_memory.py: -------------------------------------------------------------------------------- 1 | # Disable pylint's "your name is too short" warning. 2 | # pylint: disable=C0103 3 | from typing import List, Tuple 4 | 5 | from nmigen import Array, Signal, Module, Elaboratable, ClockDomain 6 | from nmigen.build import Platform 7 | from nmigen.sim import Simulator, Delay 8 | from nmigen.asserts import Assert, Assume, Cover, Past, Stable, Rose, Fell, AnyConst, Initial 9 | 10 | from util import main 11 | 12 | 13 | class AsyncMemory(Elaboratable): 14 | """Logic for a typical asynchronous memory. 15 | 16 | Attributes: 17 | addr: The address to read or write. 18 | data_in: The input data (when writing). 19 | data_out: The output data (when reading). 20 | n_oe: Output enable, active low. When n_oe is 1, the output is 0. 21 | n_wr: Write, active low. 22 | """ 23 | 24 | addr: Signal 25 | data_in: Signal 26 | data_out: Signal 27 | n_oe: Signal 28 | n_wr: Signal 29 | 30 | def __init__(self, width: int, addr_lines: int, ext_init: bool = False): 31 | """Constructs an asynchronous memory. 32 | 33 | Args: 34 | width: The number of bits in each memory cell ("word"). 35 | addr_lines: The number of address lines. We only support memories 36 | up to 16 address lines (so 64k words). 37 | """ 38 | assert width > 0 39 | assert addr_lines > 0 40 | assert addr_lines <= 16 41 | 42 | attrs = [] if not ext_init else [("uninitialized", "")] 43 | 44 | self.addr = Signal(addr_lines) 45 | self.n_oe = Signal() 46 | self.n_wr = Signal() 47 | 48 | # There's no such thing as a tristate or bidirectional port 49 | # in nMigen, so we do this instead. 50 | self.data_in = Signal(width) 51 | self.data_out = Signal(width) 52 | 53 | # The actual memory 54 | self._mem = Array( 55 | [Signal(width, reset_less=True, attrs=attrs) for _ in range(2**addr_lines)]) 56 | 57 | def elaborate(self, _: Platform) -> Module: 58 | """Implements the logic of an asynchronous memory. 59 | 60 | Essentially implements the wr-controlled write cycle, where 61 | the oe signal doesn't matter. We can't really implement the 62 | delays involved, so be safe with your signals :) 63 | """ 64 | m = Module() 65 | 66 | # Local clock domain so we can clock data into the memory 67 | # on the positive edge of n_wr. 68 | wr_clk = ClockDomain("wr_clk", local=True) 69 | m.domains.wr_clk = wr_clk 70 | wr_clk.clk = self.n_wr 71 | 72 | m.d.comb += self.data_out.eq(0) 73 | with m.If(~self.n_oe & self.n_wr): 74 | m.d.comb += self.data_out.eq(self._mem[self.addr]) 75 | m.d.wr_clk += self._mem[self.addr].eq(self.data_in) 76 | 77 | return m 78 | 79 | @classmethod 80 | def sim(cls): 81 | """A quick simulation of the async memory.""" 82 | m = Module() 83 | m.submodules.mem = mem = AsyncMemory(width=32, addr_lines=5) 84 | 85 | sim = Simulator(m) 86 | 87 | def process(): 88 | yield mem.n_oe.eq(0) 89 | yield mem.n_wr.eq(1) 90 | yield mem.data_in.eq(0xFFFFFFFF) 91 | yield Delay(1e-6) 92 | yield mem.addr.eq(1) 93 | yield mem.n_oe.eq(0) 94 | yield Delay(1e-6) 95 | yield mem.n_oe.eq(1) 96 | yield Delay(1e-6) 97 | yield mem.data_in.eq(0xAAAA1111) 98 | yield Delay(1e-6) 99 | yield mem.n_wr.eq(0) 100 | yield Delay(0.2e-6) 101 | yield mem.n_wr.eq(1) 102 | yield Delay(0.2e-6) 103 | yield mem.data_in.eq(0xFFFFFFFF) 104 | yield mem.n_oe.eq(0) 105 | yield Delay(1e-6) 106 | yield mem.addr.eq(0) 107 | yield Delay(1e-6) 108 | 109 | sim.add_process(process) 110 | with sim.write_vcd("async_memory.vcd"): 111 | sim.run_until(10e-6) 112 | 113 | @classmethod 114 | def formal(cls) -> Tuple[Module, List[Signal]]: 115 | """Formal verification for the async memory. 116 | 117 | Note that you MUST have multiclock on in the sby file, because there is 118 | more than one clock in the system -- the default formal clock and the 119 | local clock inside the memory. 120 | """ 121 | m = Module() 122 | m.submodules.mem = mem = AsyncMemory(width=32, addr_lines=5) 123 | 124 | # Assume "good practices": 125 | # * n_oe and n_wr are never simultaneously 0, and any changes 126 | # are separated by at least a cycle to allow buffers to turn off. 127 | # * memory address remains stable throughout a write cycle, and 128 | # is also stable just before a write cycle. 129 | 130 | m.d.comb += Assume(mem.n_oe | mem.n_wr) 131 | 132 | # Paren placement is very important! While Python logical operators 133 | # and, or have lower precedence than ==, the bitwise operators 134 | # &, |, ^ have *higher* precedence. It can be confusing when it looks 135 | # like you're writing a boolean expression, but you're actually writing 136 | # a bitwise expression. 137 | with m.If(Fell(mem.n_oe)): 138 | m.d.comb += Assume((mem.n_wr == 1) & (Past(mem.n_wr) == 1)) 139 | with m.If(Fell(mem.n_wr)): 140 | m.d.comb += Assume((mem.n_oe == 1) & (Past(mem.n_oe) == 1)) 141 | with m.If(Rose(mem.n_wr) | (mem.n_wr == 0)): 142 | m.d.comb += Assume(Stable(mem.addr)) 143 | 144 | m.d.comb += Cover((mem.data_out == 0xAAAAAAAA) 145 | & (Past(mem.data_out) == 0xBBBBBBBB)) 146 | 147 | # Make sure that when the output is disabled, the output is zero, and 148 | # when enabled, it's whatever we're pointing at in memory. 149 | with m.If(mem.n_oe == 1): 150 | m.d.comb += Assert(mem.data_out == 0) 151 | with m.Else(): 152 | m.d.comb += Assert(mem.data_out == mem._mem[mem.addr]) 153 | 154 | # If we just wrote data, make sure that cell that we pointed at 155 | # for writing now contains the data we wrote. 156 | with m.If(Rose(mem.n_wr)): 157 | m.d.comb += Assert(mem._mem[Past(mem.addr)] == Past(mem.data_in)) 158 | 159 | # Pick an address, any address. 160 | check_addr = AnyConst(5) 161 | 162 | # We assert that unless that address is written, its data will not 163 | # change. To know when we've written the data, we have to create 164 | # a clock domain to let us save the data when written. 165 | 166 | saved_data_clk = ClockDomain("saved_data_clk") 167 | m.domains.saved_data_clk = saved_data_clk 168 | saved_data_clk.clk = mem.n_wr 169 | 170 | saved_data = Signal(32) 171 | with m.If(mem.addr == check_addr): 172 | m.d.saved_data_clk += saved_data.eq(mem.data_in) 173 | 174 | with m.If(Initial()): 175 | m.d.comb += Assume(saved_data == mem._mem[check_addr]) 176 | m.d.comb += Assume(mem.n_wr == 1) 177 | with m.Else(): 178 | m.d.comb += Assert(saved_data == mem._mem[check_addr]) 179 | 180 | return m, [mem.addr, mem.data_in, mem.n_wr, mem.n_oe] 181 | 182 | 183 | if __name__ == "__main__": 184 | main(AsyncMemory) 185 | -------------------------------------------------------------------------------- /trap_rom.py: -------------------------------------------------------------------------------- 1 | # Disable pylint's "your name is too short" warning. 2 | # pylint: disable=C0103 3 | # Disable protected access warnings 4 | # pylint: disable=W0212 5 | from nmigen import Signal, Module, Elaboratable 6 | from nmigen.build import Platform 7 | 8 | from consts import AluOp 9 | from consts import TrapCauseSelect, SeqMuxSelect, ConstSelect 10 | from util import all_true 11 | 12 | 13 | class TrapROM(Elaboratable): 14 | """ROM for the trap sequencer card state machine.""" 15 | 16 | def __init__(self): 17 | # Inputs (12 bits) 18 | 19 | self.is_interrupted = Signal() 20 | self.exception = Signal() 21 | self.fatal = Signal() 22 | self.instr_misalign = Signal() 23 | self.bad_instr = Signal() 24 | self.trap = Signal() 25 | self.mei_pend = Signal() 26 | self.mti_pend = Signal() 27 | self.vec_mode = Signal(2) 28 | self._instr_phase = Signal(2) 29 | 30 | # Outputs (31 bits) 31 | 32 | self.set_instr_complete = Signal() 33 | 34 | # Raised when the exception card should store trap data. 35 | self.save_trap_csrs = Signal() 36 | 37 | # CSR lines 38 | self.csr_to_x = Signal() 39 | 40 | self._next_instr_phase = Signal(2) 41 | self._const = Signal(ConstSelect) 42 | 43 | # -> X 44 | self.x_mux_select = Signal(SeqMuxSelect) 45 | 46 | # -> Y 47 | self.y_mux_select = Signal(SeqMuxSelect) 48 | 49 | # -> Z 50 | self.z_mux_select = Signal(SeqMuxSelect) 51 | self.alu_op_to_z = Signal(AluOp) # 4 bits 52 | 53 | # -> PC 54 | self.pc_mux_select = Signal(SeqMuxSelect) 55 | 56 | # -> csr_num 57 | self._mcause_to_csr_num = Signal() 58 | 59 | # -> memaddr 60 | self.memaddr_mux_select = Signal(SeqMuxSelect) 61 | 62 | # -> various CSRs 63 | self.clear_pend_mti = Signal() 64 | self.clear_pend_mei = Signal() 65 | 66 | self.enter_trap = Signal() 67 | self.exit_trap = Signal() 68 | 69 | # Signals for next registers 70 | self.load_trap = Signal() 71 | self.next_trap = Signal() 72 | self.load_exception = Signal() 73 | self.next_exception = Signal() 74 | self.next_fatal = Signal() 75 | 76 | def elaborate(self, _: Platform) -> Module: 77 | """Implements the logic of the trap sequencer ROM.""" 78 | m = Module() 79 | 80 | # Defaults 81 | m.d.comb += [ 82 | self.set_instr_complete.eq(0), 83 | self.save_trap_csrs.eq(0), 84 | self.csr_to_x.eq(0), 85 | self._next_instr_phase.eq(0), 86 | self.alu_op_to_z.eq(AluOp.NONE), 87 | self._mcause_to_csr_num.eq(0), 88 | self.enter_trap.eq(0), 89 | self.exit_trap.eq(0), 90 | self.clear_pend_mti.eq(0), 91 | self.clear_pend_mei.eq(0), 92 | self.x_mux_select.eq(SeqMuxSelect.X), 93 | self.y_mux_select.eq(SeqMuxSelect.Y), 94 | self.z_mux_select.eq(SeqMuxSelect.Z), 95 | self.pc_mux_select.eq(SeqMuxSelect.PC), 96 | self.memaddr_mux_select.eq(SeqMuxSelect.MEMADDR), 97 | self._const.eq(0), 98 | ] 99 | 100 | m.d.comb += [ 101 | self.load_trap.eq(0), 102 | self.next_trap.eq(0), 103 | self.load_exception.eq(0), 104 | self.next_exception.eq(0), 105 | self.next_fatal.eq(0), 106 | ] 107 | 108 | # 4 cases here: 109 | # 110 | # 1. It's a trap! 111 | # 2. It's not a trap, but PC is misaligned. 112 | # 3. It's not a trap, PC is aligned, but it's a bad instruction. 113 | # 4. None of the above (allow control by sequencer ROM). 114 | 115 | with m.If(self.trap): 116 | self.handle_trap(m) 117 | 118 | # True when pc[0:2] != 0 and ~trap. 119 | with m.Elif(self.instr_misalign): 120 | self.set_exception( 121 | m, ConstSelect.EXC_INSTR_ADDR_MISALIGN, mtval=SeqMuxSelect.PC) 122 | 123 | with m.Elif(self.bad_instr): 124 | self.set_exception( 125 | m, ConstSelect.EXC_ILLEGAL_INSTR, mtval=SeqMuxSelect.INSTR) 126 | 127 | return m 128 | 129 | def set_exception(self, m: Module, exc: ConstSelect, mtval: SeqMuxSelect, fatal: bool = True): 130 | m.d.comb += self.load_exception.eq(1) 131 | m.d.comb += self.next_exception.eq(1) 132 | m.d.comb += self.next_fatal.eq(1 if fatal else 0) 133 | 134 | m.d.comb += self._const.eq(exc) 135 | m.d.comb += self.x_mux_select.eq(SeqMuxSelect.CONST) 136 | m.d.comb += self.z_mux_select.eq(mtval) 137 | 138 | if fatal: 139 | m.d.comb += self.y_mux_select.eq(SeqMuxSelect.PC) 140 | else: 141 | m.d.comb += self.y_mux_select.eq(SeqMuxSelect.PC_PLUS_4) 142 | 143 | # X -> MCAUSE, Y -> MEPC, Z -> MTVAL 144 | m.d.comb += self.save_trap_csrs.eq(1) 145 | m.d.comb += self.load_trap.eq(1) 146 | m.d.comb += self.next_trap.eq(1) 147 | m.d.comb += self._next_instr_phase.eq(0) 148 | 149 | def handle_trap(self, m: Module): 150 | """Adds trap handling logic. 151 | 152 | For fatals, we store the cause and then halt. 153 | """ 154 | is_int = ~self.exception 155 | 156 | with m.If(self._instr_phase == 0): 157 | with m.If(self.fatal): 158 | m.d.comb += self._next_instr_phase.eq(0) # hang. 159 | with m.Else(): 160 | m.d.comb += self._next_instr_phase.eq(1) 161 | 162 | # If set_exception was called, we've already saved the trap CSRs. 163 | with m.If(is_int): 164 | with m.If(self.mei_pend): 165 | m.d.comb += self._const.eq(ConstSelect.INT_MACH_EXTERNAL) 166 | m.d.comb += self.x_mux_select.eq(SeqMuxSelect.CONST) 167 | m.d.comb += self.clear_pend_mei.eq(1) 168 | with m.Elif(self.mti_pend): 169 | m.d.comb += self._const.eq(ConstSelect.INT_MACH_TIMER) 170 | m.d.comb += self.x_mux_select.eq(SeqMuxSelect.CONST) 171 | m.d.comb += self.clear_pend_mti.eq(1) 172 | 173 | m.d.comb += self.y_mux_select.eq(SeqMuxSelect.PC) 174 | 175 | # MTVAL should be zero for non-exceptions, but right now it's just random. 176 | # X -> MCAUSE, Y -> MEPC, Z -> MTVAL 177 | m.d.comb += self.save_trap_csrs.eq(1) 178 | 179 | with m.Else(): 180 | # In vectored mode, we calculate the target address with: 181 | # ((mtvec >> 2) + cause) << 2. This is the same as 182 | # (mtvec & 0xFFFFFFFC) + 4 * cause, but doesn't require 183 | # the cause to be shifted before adding. 184 | # mtvec >> 2 185 | m.d.comb += self.y_mux_select.eq(SeqMuxSelect.MTVEC_LSR2) 186 | with m.If(all_true(is_int, self.vec_mode == 1)): 187 | m.d.comb += [ 188 | self._mcause_to_csr_num.eq(1), 189 | self.csr_to_x.eq(1), 190 | ] 191 | 192 | m.d.comb += self.load_trap.eq(1) 193 | m.d.comb += self.next_trap.eq(0) 194 | m.d.comb += [ 195 | self.alu_op_to_z.eq(AluOp.ADD), 196 | self.memaddr_mux_select.eq( 197 | SeqMuxSelect.Z_LSL2), # z << 2 -> memaddr, pc 198 | self.pc_mux_select.eq(SeqMuxSelect.Z_LSL2), 199 | self.enter_trap.eq(1), 200 | self.set_instr_complete.eq(1), 201 | ] 202 | -------------------------------------------------------------------------------- /shift_card.py: -------------------------------------------------------------------------------- 1 | # Disable pylint's "your name is too short" warning. 2 | # pylint: disable=C0103 3 | from typing import List, Tuple 4 | 5 | from nmigen import Signal, Module, Elaboratable, Mux, Repl 6 | from nmigen.build import Platform 7 | from nmigen.asserts import Assert, Cover 8 | 9 | from consts import AluOp 10 | from transparent_latch import TransparentLatch 11 | from util import main 12 | 13 | 14 | class _ConditionalShiftRight(Elaboratable): 15 | """A right-shifter that shifts by N or 0, logical or arithmetic. 16 | 17 | For a 16-bit version of this, we might use the SN74CBT16233. 18 | 19 | Attributes: 20 | data_in: The input data. 21 | data_out: The output data. 22 | en: Whether shifting is enabled or disabled. 23 | arithmetic: Whether shifting is arithmetic or not (i.e. logical). 24 | """ 25 | 26 | en: Signal 27 | arithmetic: Signal 28 | data_in: Signal 29 | data_out: Signal 30 | 31 | def __init__(self, width: int, N: int): 32 | """Constructs a conditional right-shifter. 33 | 34 | Args: 35 | width: The number of bits in the shifter. 36 | N: The number of bits to shift by. 37 | """ 38 | self._N = N 39 | 40 | self.en = Signal() 41 | self.arithmetic = Signal() 42 | self.data_in = Signal(width) 43 | self.data_out = Signal(width) 44 | 45 | def elaborate(self, _: Platform) -> Module: 46 | """Implements the logic for the conditional right-shifter.""" 47 | m = Module() 48 | N = self._N 49 | 50 | with m.If(self.en): 51 | # The high N bits get either 0 or the most significant 52 | # bit in data_in, depending on arithmetic. 53 | msb = self.data_in[-1] 54 | w = len(self.data_out) 55 | 56 | # m.d.comb += self.data_out.bit_select(w-N, N).eq( 57 | # Mux(self.arithmetic, Repl(msb, N), 0)) 58 | 59 | with m.If(self.arithmetic): 60 | m.d.comb += self.data_out[w-N:].eq(Repl(msb, N)) 61 | with m.Else(): 62 | m.d.comb += self.data_out[w-N:].eq(0) 63 | 64 | # The rest are moved over. 65 | # m.d.comb += self.data_out.bit_select(0, w-N).eq( 66 | # self.data_in.bit_select(N, w-N)) 67 | 68 | m.d.comb += self.data_out[:w-N].eq(self.data_in[N:]) 69 | 70 | with m.Else(): 71 | m.d.comb += self.data_out.eq(self.data_in) 72 | 73 | return m 74 | 75 | 76 | class _ConditionalReverser(Elaboratable): 77 | """A conditional bit reverser. 78 | 79 | Attributes: 80 | data_in: The input data. 81 | data_out: The output data. 82 | en: Whether reversing is enabled or disabled. 83 | """ 84 | 85 | def __init__(self, width: int): 86 | """Constructs a conditional reverser. 87 | 88 | Args: 89 | width: The number of bits in the reverser. 90 | """ 91 | self.en = Signal() 92 | self.data_in = Signal(width) 93 | self.data_out = Signal(width) 94 | 95 | def elaborate(self, _: Platform) -> Module: 96 | """Implements the logic for the reverser.""" 97 | m = Module() 98 | 99 | m.d.comb += self.data_out.eq(Mux(self.en, 100 | self.data_in[::-1], 101 | self.data_in)) 102 | 103 | return m 104 | 105 | 106 | class ShiftCard(Elaboratable): 107 | """Logic for the shifter card. 108 | 109 | This implements a logarithmic shifter. If shifting left, the input 110 | and output are inverted so that only shift right needs to be implemented. 111 | """ 112 | 113 | data_x: Signal 114 | data_y: Signal 115 | data_z: Signal 116 | alu_op: Signal 117 | 118 | def __init__(self): 119 | # Buses 120 | self.data_x = Signal(32) 121 | self.data_y = Signal(32) 122 | self.data_z = Signal(32) 123 | 124 | # Controls 125 | self.alu_op = Signal(AluOp) 126 | 127 | def elaborate(self, _: Platform) -> Module: 128 | """Implements the logic of the shifter card.""" 129 | m = Module() 130 | 131 | input_reverse = _ConditionalReverser(width=32) 132 | shift1 = _ConditionalShiftRight(width=32, N=1) 133 | shift2 = _ConditionalShiftRight(width=32, N=2) 134 | shift4 = _ConditionalShiftRight(width=32, N=4) 135 | shift8 = _ConditionalShiftRight(width=32, N=8) 136 | shift16 = _ConditionalShiftRight(width=32, N=16) 137 | output_reverse = _ConditionalReverser(width=32) 138 | output_buffer = TransparentLatch(size=32) 139 | 140 | m.submodules += [input_reverse, shift1, shift2, 141 | shift4, shift8, shift16, output_reverse, output_buffer] 142 | 143 | # Hook up inputs and outputs 144 | 145 | m.d.comb += [ 146 | input_reverse.data_in.eq(self.data_x), 147 | shift1.data_in.eq(input_reverse.data_out), 148 | shift2.data_in.eq(shift1.data_out), 149 | shift4.data_in.eq(shift2.data_out), 150 | shift8.data_in.eq(shift4.data_out), 151 | shift16.data_in.eq(shift8.data_out), 152 | output_reverse.data_in.eq(shift16.data_out), 153 | output_buffer.data_in.eq(output_reverse.data_out), 154 | self.data_z.eq(output_buffer.data_out), 155 | ] 156 | 157 | # Some flags 158 | shift_arith = Signal() 159 | shift_left = Signal() 160 | 161 | m.d.comb += shift_arith.eq(self.alu_op == AluOp.SRA) 162 | m.d.comb += shift_left.eq(self.alu_op == AluOp.SLL) 163 | 164 | m.d.comb += [ 165 | input_reverse.en.eq(shift_left), 166 | output_reverse.en.eq(shift_left), 167 | shift1.arithmetic.eq(shift_arith), 168 | shift2.arithmetic.eq(shift_arith), 169 | shift4.arithmetic.eq(shift_arith), 170 | shift8.arithmetic.eq(shift_arith), 171 | shift16.arithmetic.eq(shift_arith), 172 | ] 173 | 174 | # Shift amount 175 | shamt = self.data_y[:5] 176 | 177 | m.d.comb += [ 178 | shift1.en.eq(shamt[0]), 179 | shift2.en.eq(shamt[1]), 180 | shift4.en.eq(shamt[2]), 181 | shift8.en.eq(shamt[3]), 182 | shift16.en.eq(shamt[4]), 183 | ] 184 | 185 | m.d.comb += output_buffer.le.eq(1) 186 | m.d.comb += output_buffer.n_oe.eq(1) 187 | 188 | with m.Switch(self.alu_op): 189 | with m.Case(AluOp.SLL, AluOp.SRL, AluOp.SRA): 190 | m.d.comb += output_buffer.n_oe.eq(0) 191 | with m.Default(): 192 | m.d.comb += output_buffer.n_oe.eq(1) 193 | 194 | return m 195 | 196 | @classmethod 197 | def formal(cls) -> Tuple[Module, List[Signal]]: 198 | """Formal verification for the shifter.""" 199 | m = Module() 200 | m.submodules.shifter = shifter = ShiftCard() 201 | 202 | shamt = Signal(5) 203 | m.d.comb += shamt.eq(shifter.data_y[:5]) 204 | 205 | with m.If(shamt > 0): 206 | m.d.comb += Cover(shifter.data_z == 0xFFFFAAA0) 207 | 208 | with m.Switch(shifter.alu_op): 209 | with m.Case(AluOp.SLL): 210 | m.d.comb += Assert(shifter.data_z == 211 | (shifter.data_x << shamt)[:32]) 212 | 213 | with m.Case(AluOp.SRL): 214 | m.d.comb += Assert(shifter.data_z == 215 | (shifter.data_x >> shamt)) 216 | 217 | with m.Case(AluOp.SRA): 218 | m.d.comb += Assert(shifter.data_z == 219 | (shifter.data_x.as_signed() >> shamt)) 220 | 221 | with m.Default(): 222 | m.d.comb += Assert(shifter.data_z == 0) 223 | 224 | return m, [shifter.alu_op, shifter.data_x, shifter.data_y, shifter.data_z] 225 | 226 | 227 | if __name__ == "__main__": 228 | main(ShiftCard) 229 | -------------------------------------------------------------------------------- /consts.py: -------------------------------------------------------------------------------- 1 | # Disable pylint's "your name is too short" warning. 2 | # pylint: disable=C0103 3 | 4 | from enum import IntEnum, unique 5 | 6 | 7 | @unique 8 | class Instr(IntEnum): 9 | """Instructions where all 32 bits are fixed.""" 10 | MRET = 0x30200073 11 | ECALL = 0x00000073 12 | EBREAK = 0x00100073 13 | 14 | 15 | @unique 16 | class Opcode(IntEnum): 17 | """Opcodes.""" 18 | LOAD = 0b000_0011 # 0x03 19 | OP_IMM = 0b001_0011 # 0x13 20 | STORE = 0b010_0011 # 0x23 21 | OP = 0b011_0011 # 0x33 22 | BRANCH = 0b110_0011 # 0x63 23 | SYSTEM = 0b111_0011 # 0x73 24 | LUI = 0b011_0111 # 0x37 25 | JALR = 0b110_0111 # 0x67 26 | MISC_MEM = 0b000_1111 # 0x0F 27 | AUIPC = 0b001_0111 # 0x17 28 | JAL = 0b110_1111 # 0x6F 29 | 30 | # Opcodes in other extensions, or reserved, or custom, 31 | # or for other instruction lengths. 32 | LOAD_FP = 0b000_0111 # 0x07 33 | CUSTOM0 = 0b000_1011 # 0x0B 34 | OP_IMM32 = 0b001_1011 # 0x1B 35 | INSTR_48A = 0b001_1111 # 0x1F 36 | STORE_FP = 0b010_0111 # 0x27 37 | CUSTOM1 = 0b0101011 38 | AMO = 0b0101111 39 | OP32 = 0b0111011 40 | MADD = 0b1000011 41 | MSUB = 0b1000111 42 | NMSUB = 0b1001011 43 | NMADD = 0b1001111 44 | OP_FP = 0b1010011 45 | RESERVED0 = 0b1010111 46 | CUSTOM2 = 0b1011011 47 | INSTR_64 = 0b0111111 48 | INSTR_48B = 0b1011111 49 | RESERVED1 = 0b1101011 50 | RESERVED2 = 0b1110111 51 | CUSTOM3 = 0b1111011 52 | INSTR_80 = 0b1111111 53 | 54 | 55 | @unique 56 | class OpcodeSelect(IntEnum): 57 | """Opcode selection for state machine lookup table.""" 58 | NONE = 0 59 | LOAD = 1 60 | STORE = 2 61 | OP = 3 62 | BRANCH = 4 63 | CSRS = 5 64 | LUI = 6 65 | JALR = 7 66 | AUIPC = 8 67 | JAL = 9 68 | OP_IMM = 10 69 | MRET = 11 70 | ECALL = 12 71 | EBREAK = 13 72 | 73 | 74 | @unique 75 | class OpcodeFormat(IntEnum): 76 | """Opcode formats.""" 77 | R = 0 # OP 78 | I = 1 # LOAD, MISC_MEM, OP_IMM 79 | U = 2 # AUIPC, LUI 80 | S = 3 # STORE 81 | B = 4 # BRANCH 82 | J = 5 # JAL, JALR 83 | SYS = 6 # SYSTEM 84 | 85 | 86 | @unique 87 | class BranchCond(IntEnum): 88 | """Branch conditions.""" 89 | EQ = 0b000 90 | NE = 0b001 91 | LT = 0b100 92 | GE = 0b101 93 | LTU = 0b110 94 | GEU = 0b111 95 | 96 | 97 | @unique 98 | class MemAccessWidth(IntEnum): 99 | """Memory access widths.""" 100 | B = 0b000 101 | H = 0b001 102 | W = 0b010 103 | BU = 0b100 104 | HU = 0b101 105 | 106 | 107 | @unique 108 | class AluOp(IntEnum): 109 | "ALU card operations." 110 | NONE = 0b0000 111 | ADD = 0b0001 112 | SUB = 0b0010 113 | SLL = 0b0011 114 | SLT = 0b0100 115 | SLTU = 0b0101 116 | XOR = 0b0110 117 | SRL = 0b0111 118 | SRA = 0b1000 119 | OR = 0b1001 120 | AND = 0b1010 121 | X = 0b1011 122 | Y = 0b1100 123 | AND_NOT = 0b1101 124 | 125 | 126 | @unique 127 | class AluFunc(IntEnum): 128 | """ALU functions.""" 129 | ADD = 0b0000 130 | SUB = 0b1000 131 | SLL = 0b0001 132 | SLT = 0b0010 133 | SLTU = 0b1011 134 | XOR = 0b0100 135 | SRL = 0b0101 136 | SRA = 0b1101 137 | OR = 0b0110 138 | AND = 0b0111 139 | 140 | 141 | @unique 142 | class SystemFunc(IntEnum): 143 | """System opcode functions.""" 144 | PRIV = 0b000 145 | CSRRW = 0b001 146 | CSRRS = 0b010 147 | CSRRC = 0b011 148 | CSRRWI = 0b101 149 | CSRRSI = 0b110 150 | CSRRCI = 0b111 151 | 152 | 153 | @unique 154 | class PrivFunc(IntEnum): 155 | """Privileged functions, funct12 value.""" 156 | # Functions for which rd and rs1 must be 0: 157 | ECALL = 0b000000000000 158 | EBREAK = 0b000000000001 159 | URET = 0b000000000010 160 | SRET = 0b000100000010 161 | MRET = 0b001100000010 162 | WFI = 0b000100000101 163 | 164 | 165 | @unique 166 | class TrapCause(IntEnum): 167 | """Trap causes.""" 168 | INT_USER_SOFTWARE = 0x80000000 169 | INT_SUPV_SOFTWARE = 0x80000001 170 | INT_MACH_SOFTWARE = 0x80000003 171 | INT_USER_TIMER = 0x80000004 172 | INT_SUPV_TIMER = 0x80000005 173 | INT_MACH_TIMER = 0x80000007 174 | INT_USER_EXTERNAL = 0x80000008 175 | INT_SUPV_EXTERNAL = 0x80000009 176 | INT_MACH_EXTERNAL = 0x8000000B 177 | 178 | EXC_INSTR_ADDR_MISALIGN = 0x00000000 179 | EXC_INSTR_ACCESS_FAULT = 0x00000001 180 | EXC_ILLEGAL_INSTR = 0x00000002 181 | EXC_BREAKPOINT = 0x00000003 182 | EXC_LOAD_ADDR_MISALIGN = 0x00000004 183 | EXC_LOAD_ACCESS_FAULT = 0x00000005 184 | EXC_STORE_AMO_ADDR_MISALIGN = 0x00000006 185 | EXC_STORE_AMO_ACCESS_FAULT = 0x00000007 186 | EXC_ECALL_FROM_USER_MODE = 0x00000008 187 | EXC_ECALL_FROM_SUPV_MODE = 0x00000009 188 | EXC_ECALL_FROM_MACH_MODE = 0x0000000B 189 | EXC_INSTR_PAGE_FAULT = 0x0000000C 190 | EXC_LOAD_PAGE_FAULT = 0x0000000D 191 | EXC_STORE_AMO_PAGE_FAULT = 0x0000000F 192 | 193 | 194 | @unique 195 | class TrapCauseSelect(IntEnum): 196 | """Selectors for trap causes.""" 197 | NONE = 0 198 | EXC_INSTR_ADDR_MISALIGN = 1 199 | EXC_ILLEGAL_INSTR = 2 200 | EXC_BREAKPOINT = 3 201 | EXC_LOAD_ADDR_MISALIGN = 4 202 | EXC_STORE_AMO_ADDR_MISALIGN = 5 203 | EXC_ECALL_FROM_MACH_MODE = 6 204 | INT_MACH_EXTERNAL = 7 205 | INT_MACH_TIMER = 8 206 | 207 | 208 | @unique 209 | class ConstSelect(IntEnum): 210 | """Selectors for consts.""" 211 | EXC_INSTR_ADDR_MISALIGN = 0 # 0x00000000 212 | EXC_ILLEGAL_INSTR = 1 # 0x00000002 213 | EXC_BREAKPOINT = 2 # 0x00000003 214 | EXC_LOAD_ADDR_MISALIGN = 3 # 0x00000004 215 | EXC_STORE_AMO_ADDR_MISALIGN = 4 # 0x00000006 216 | EXC_ECALL_FROM_MACH_MODE = 5 # 0x0000000B 217 | INT_MACH_EXTERNAL = 6 # 0x8000000B 218 | INT_MACH_TIMER = 7 # 0x80000007 219 | SHAMT_0 = 8 # 0x00000000 220 | SHAMT_4 = 9 # 0x00000004 221 | SHAMT_8 = 10 # 0x00000008 222 | SHAMT_16 = 11 # 0x00000010 223 | SHAMT_24 = 12 # 0x00000018 224 | 225 | 226 | @unique 227 | class CSRAddr(IntEnum): 228 | """CSR addresses.""" 229 | MSTATUS = 0x300 230 | MIE = 0x304 231 | MTVEC = 0x305 232 | MEPC = 0x341 233 | MCAUSE = 0x342 234 | MTVAL = 0x343 235 | MIP = 0x344 236 | LAST = 0xFFF 237 | 238 | 239 | @unique 240 | class MStatus(IntEnum): 241 | """Bits for mstatus.""" 242 | MIE = 3 # Machine interrupts global enable (00000008) 243 | MPIE = 7 # Machine interrupts global enable (previous value) (00000080) 244 | 245 | 246 | @unique 247 | class MInterrupt(IntEnum): 248 | """Bits for mie and mip.""" 249 | MSI = 3 # Machine software interrupt enabled/pending (00000008) 250 | MTI = 7 # Machine timer interrupt enabled/pending (00000080) 251 | MEI = 11 # Machine external interrupt enabled/pending (00000800) 252 | 253 | 254 | @unique 255 | class InstrReg(IntEnum): 256 | """Which register number to put on *_reg.""" 257 | ZERO = 0 258 | RS1 = 1 259 | RS2 = 2 260 | RD = 3 261 | 262 | 263 | @unique 264 | class NextPC(IntEnum): 265 | PC_PLUS_4 = 0 266 | MEMADDR = 1 267 | Z = 2 268 | X = 3 269 | MEMADDR_NO_LSB = 4 270 | 271 | 272 | @unique 273 | class SeqMuxSelect(IntEnum): 274 | """Which input is selected in a reg/buff multiplexer card. 275 | 276 | For outputting to bus X, Y, or Z, if the selection is the 277 | same as the output bus -- that is, we send the bus to the bus itself -- 278 | then the bus is actually disabled so that some other card can 279 | control the bus. 280 | 281 | When we want to output the trap cause or a non-MTVEC CSR to X, that's a different 282 | signal, which isn't handled by the mux card, so we set the selection 283 | to X in that case. 284 | """ 285 | MEMDATA_WR = 0 286 | MEMDATA_RD = 1 287 | MEMADDR = 2 288 | MEMADDR_LSB_MASKED = 3 289 | PC = 4 290 | PC_PLUS_4 = 5 291 | MTVEC = 6 292 | MTVEC_LSR2 = 7 293 | TMP = 8 294 | IMM = 9 295 | INSTR = 10 296 | X = 11 297 | Y = 12 298 | Z = 13 299 | Z_LSL2 = 14 300 | CONST = 15 301 | -------------------------------------------------------------------------------- /irq_card.py: -------------------------------------------------------------------------------- 1 | # Disable pylint's "your name is too short" warning. 2 | # pylint: disable=C0103 3 | from typing import List 4 | 5 | from nmigen import Signal, Module, Elaboratable 6 | from nmigen.build import Platform 7 | 8 | from IC_7416244 import IC_mux32 9 | from IC_7416374 import IC_reg32_with_mux 10 | from consts import CSRAddr, MStatus, MInterrupt 11 | 12 | 13 | class IrqCard(Elaboratable): 14 | """Logic for the interrupt card. 15 | 16 | The card holds the MSTATUS, MIE and MIP registers. Aside from responding 17 | to the normal CSR read/write instructions, it can also do other stuff. 18 | """ 19 | 20 | def __init__(self, ext_init: bool = False): 21 | """Constructs an interrupt card.""" 22 | self._ext_init = ext_init 23 | 24 | # Buses 25 | self.data_x_out = Signal(32) 26 | self.data_z_in = Signal(32) 27 | 28 | # Controls 29 | self.csr_num = Signal(CSRAddr) 30 | self.csr_to_x = Signal() 31 | self.z_to_csr = Signal() 32 | self.trap = Signal() 33 | self.time_irq = Signal() 34 | self.ext_irq = Signal() 35 | self.enter_trap = Signal() 36 | self.exit_trap = Signal() 37 | self.clear_pend_mti = Signal() 38 | self.clear_pend_mei = Signal() 39 | self.mei_pend = Signal() 40 | self.mti_pend = Signal() 41 | 42 | # Internals 43 | self._mstatus = Signal(32) 44 | self._mie = Signal(32) 45 | self._mip = Signal(32) 46 | 47 | self._pend_mti = Signal() 48 | self._pend_mei = Signal() 49 | self._clear_pend_mti = Signal() 50 | self._clear_pend_mei = Signal() 51 | 52 | def elaborate(self, _: Platform) -> Module: 53 | """Implements the logic of the interrupt card.""" 54 | m = Module() 55 | 56 | m.d.comb += [ 57 | self._pend_mti.eq(0), 58 | self._pend_mei.eq(0), 59 | self._clear_pend_mti.eq(0), 60 | self._clear_pend_mei.eq(0), 61 | ] 62 | 63 | with m.If(~self.trap): 64 | with m.If(self._mstatus[MStatus.MIE]): 65 | 66 | with m.If(self._mie[MInterrupt.MTI]): 67 | with m.If(~self._mip[MInterrupt.MTI]): 68 | m.d.comb += self._pend_mti.eq(self.time_irq) 69 | with m.Else(): 70 | m.d.comb += self._clear_pend_mti.eq(1) 71 | 72 | with m.If(self._mie[MInterrupt.MEI]): 73 | with m.If(~self._mip[MInterrupt.MEI]): 74 | m.d.comb += self._pend_mei.eq(self.ext_irq) 75 | with m.Else(): 76 | m.d.comb += self._clear_pend_mei.eq(1) 77 | 78 | with m.Else(): 79 | m.d.comb += self._clear_pend_mti.eq(1) 80 | m.d.comb += self._clear_pend_mei.eq(1) 81 | 82 | m.d.comb += self.mei_pend.eq(self._mip[MInterrupt.MEI]) 83 | m.d.comb += self.mti_pend.eq(self._mip[MInterrupt.MTI]) 84 | 85 | enter_trap_mstatus = self._mstatus 86 | enter_trap_mstatus &= ~(1 << MStatus.MIE) # clear MIE 87 | enter_trap_mstatus &= ~(1 << MStatus.MPIE) # clear MPIE 88 | enter_trap_mstatus |= ( 89 | self._mstatus[MStatus.MIE] << MStatus.MPIE) # set MPIE 90 | 91 | exit_trap_mstatus = self._mstatus 92 | exit_trap_mstatus |= (1 << MStatus.MPIE) # set MPIE 93 | exit_trap_mstatus &= ~(1 << MStatus.MIE) # clear MIE 94 | exit_trap_mstatus |= ( 95 | self._mstatus[MStatus.MPIE] << MStatus.MIE) # set MIE 96 | 97 | self.multiplex_to_reg(m, clk="ph2w", reg=self._mstatus, 98 | sels=[ 99 | self.z_to_csr & ( 100 | self.csr_num == CSRAddr.MSTATUS), 101 | self.enter_trap, 102 | self.exit_trap, 103 | ], 104 | sigs=[ 105 | self.data_z_in, 106 | enter_trap_mstatus, 107 | exit_trap_mstatus, 108 | ], 109 | ext_init=self._ext_init) 110 | 111 | self.multiplex_to_reg(m, clk="ph2w", reg=self._mie, 112 | sels=[ 113 | self.z_to_csr & ( 114 | self.csr_num == CSRAddr.MIE), 115 | ], 116 | sigs=[self.data_z_in], 117 | ext_init=self._ext_init) 118 | 119 | mtip = Signal() 120 | meip = Signal() 121 | 122 | m.d.comb += mtip.eq(self._mip[MInterrupt.MTI]) 123 | m.d.comb += meip.eq(self._mip[MInterrupt.MEI]) 124 | with m.If(self._pend_mti): 125 | m.d.comb += mtip.eq(1) 126 | with m.Elif(self._clear_pend_mti | self.clear_pend_mti): 127 | m.d.comb += mtip.eq(0) 128 | with m.If(self._pend_mei): 129 | m.d.comb += meip.eq(1) 130 | with m.Elif(self._clear_pend_mei | self.clear_pend_mei): 131 | m.d.comb += meip.eq(0) 132 | 133 | # Pending machine interrupts are not writable. 134 | mip_load = Signal(32) 135 | m.d.comb += mip_load.eq(self.data_z_in) 136 | m.d.comb += mip_load[MInterrupt.MTI].eq(mtip) 137 | m.d.comb += mip_load[MInterrupt.MEI].eq(meip) 138 | m.d.comb += mip_load[MInterrupt.MSI].eq(self._mip[MInterrupt.MSI]) 139 | 140 | load_mip = self.z_to_csr & (self.csr_num == CSRAddr.MIP) 141 | 142 | mip_pend = Signal(32) 143 | m.d.comb += mip_pend.eq(self._mip) 144 | m.d.comb += mip_pend[MInterrupt.MTI].eq(mtip) 145 | m.d.comb += mip_pend[MInterrupt.MEI].eq(meip) 146 | 147 | # Either we load MIP, or set/clear/retain bits in MIP. We never have 148 | # to leave MIP alone. 149 | 150 | self.multiplex_to_reg(m, clk="ph2w", reg=self._mip, 151 | sels=[load_mip, ~load_mip], 152 | sigs=[mip_load, mip_pend]) 153 | 154 | self.multiplex_to_bus(m, bus=self.data_x_out, 155 | sels=[ 156 | self.csr_to_x & ( 157 | self.csr_num == CSRAddr.MSTATUS), 158 | self.csr_to_x & ( 159 | self.csr_num == CSRAddr.MIE), 160 | self.csr_to_x & ( 161 | self.csr_num == CSRAddr.MIP), 162 | ], 163 | sigs=[self._mstatus, 164 | self._mie, 165 | self._mip, 166 | ]) 167 | 168 | return m 169 | 170 | def multiplex_to_reg(self, m: Module, clk: str, reg: Signal, sels: List[Signal], sigs: List[Signal], 171 | ext_init: bool = False): 172 | """Sets up a multiplexer with a register. 173 | 174 | clk is the clock domain on which the register is clocked. 175 | 176 | reg is the register signal. 177 | 178 | sels is an array of Signals which select that input for the multiplexer (active high). If 179 | no select is active, then the register retains its value. 180 | 181 | sigs is an array of Signals which are the inputs to the multiplexer. 182 | """ 183 | assert len(sels) == len(sigs) 184 | 185 | muxreg = IC_reg32_with_mux( 186 | clk=clk, N=len(sels), ext_init=ext_init, faster=True) 187 | m.submodules += muxreg 188 | m.d.comb += reg.eq(muxreg.q) 189 | for i in range(len(sels)): 190 | m.d.comb += muxreg.n_sel[i].eq(~sels[i]) 191 | m.d.comb += muxreg.d[i].eq(sigs[i]) 192 | 193 | def multiplex_to_bus(self, m: Module, bus: Signal, sels: List[Signal], sigs: List[Signal]): 194 | """Sets up a multiplexer to a bus.""" 195 | assert len(sels) == len(sigs) 196 | 197 | mux = IC_mux32(N=len(sels), faster=True) 198 | m.submodules += mux 199 | m.d.comb += bus.eq(mux.y) 200 | for i in range(len(sels)): 201 | m.d.comb += mux.n_sel[i].eq(~sels[i]) 202 | m.d.comb += mux.a[i].eq(sigs[i]) 203 | -------------------------------------------------------------------------------- /IC_7416374.py: -------------------------------------------------------------------------------- 1 | """Module containing stuff made out of 7416374 16-bit registers.""" 2 | # Disable pylint's "your name is too short" warning. 3 | # pylint: disable=C0103 4 | # Disable protected access warnings 5 | # pylint: disable=W0212 6 | from typing import List, Tuple 7 | 8 | from nmigen import Signal, Module, Elaboratable, Array, ClockSignal, ResetSignal, ClockDomain 9 | from nmigen.build import Platform 10 | from nmigen.asserts import Assert, Assume, Past, Rose, Fell 11 | 12 | from IC_7416244 import IC_mux32 13 | from util import main 14 | 15 | 16 | class IC_7416374(Elaboratable): 17 | """Contains logic for a 7416374 16-bit register.""" 18 | 19 | def __init__(self, clk: str, ext_init: bool = False): 20 | """Creats a 7416374 register. 21 | 22 | clk is the name of the domain the register will clock on. 23 | """ 24 | attrs = [] if not ext_init else [("uninitialized", "")] 25 | 26 | self.clk = clk 27 | 28 | self.d = Signal(16) 29 | self.n_oe = Signal() 30 | self.q = Signal(16) 31 | 32 | self._q = Signal(16, attrs=attrs) 33 | 34 | def elaborate(self, _: Platform) -> Module: 35 | """Implements the logic of the register.""" 36 | m = Module() 37 | 38 | m.d[self.clk] += self._q.eq(self.d) 39 | m.d.comb += self.q.eq(0) 40 | with m.If(~self.n_oe): 41 | m.d.comb += self.q.eq(self._q) 42 | 43 | return m 44 | 45 | @classmethod 46 | def formal(cls) -> Tuple[Module, List[Signal]]: 47 | m = Module() 48 | ph = ClockDomain("ph") 49 | clk = ClockSignal("ph") 50 | 51 | m.domains += ph 52 | m.d.sync += clk.eq(~clk) 53 | 54 | s = IC_7416374(clk="ph") 55 | 56 | m.submodules += s 57 | 58 | with m.If(s.n_oe): 59 | m.d.comb += Assert(s.q == 0) 60 | 61 | with m.If(~s.n_oe & Rose(clk)): 62 | m.d.comb += Assert(s.q == Past(s.d)) 63 | 64 | with m.If(~s.n_oe & Fell(clk) & ~Past(s.n_oe)): 65 | m.d.comb += Assert(s.q == Past(s.q)) 66 | 67 | sync_clk = ClockSignal("sync") 68 | sync_rst = ResetSignal("sync") 69 | 70 | # Make sure the clock is clocking 71 | m.d.comb += Assume(sync_clk == ~Past(sync_clk)) 72 | 73 | # Don't want to test what happens when we reset. 74 | m.d.comb += Assume(~sync_rst) 75 | m.d.comb += Assume(~ResetSignal("ph")) 76 | 77 | return m, [sync_clk, sync_rst, s.n_oe, s.q, s.d] 78 | 79 | 80 | class IC_reg32(Elaboratable): 81 | """A 32-bit register from a pair of 16-bit registers.""" 82 | 83 | def __init__(self, clk: str, ext_init: bool = False): 84 | self.clk = clk 85 | self.d = Signal(32) 86 | self.n_oe = Signal() 87 | self.q = Signal(32) 88 | self.ext_init = ext_init 89 | 90 | def elaborate(self, _: Platform) -> Module: 91 | """Implements the logic of the register.""" 92 | m = Module() 93 | regs = [IC_7416374(self.clk, self.ext_init), 94 | IC_7416374(self.clk, self.ext_init)] 95 | m.submodules += regs 96 | 97 | for i in range(2): 98 | m.d.comb += regs[i].n_oe.eq(self.n_oe) 99 | m.d.comb += regs[i].d.eq(self.d[i*16:i*16+16]) 100 | m.d.comb += self.q[i*16:i*16+16].eq(regs[i].q) 101 | 102 | return m 103 | 104 | 105 | class IC_reg32_with_mux(Elaboratable): 106 | """A 32-bit register that multiplexes any of its inputs, or itself. 107 | 108 | There is no output enable input. 109 | """ 110 | 111 | def __init__(self, clk: str, N: int, ext_init: bool = False, faster: bool = False): 112 | """Constructs a 32-bit register with multiplexed inputs. 113 | 114 | Setting faster will make formal verification somewhat faster, since there aren't 115 | so many nested submodules and extra logic. 116 | """ 117 | self.N = N 118 | self.clk = clk 119 | self.d = Array([Signal(32, name=f"d{i}") for i in range(N)]) 120 | self.n_sel = Signal(N) 121 | self.q = Signal(32) 122 | self._ext_init = ext_init 123 | self._faster = faster 124 | 125 | def elaborate(self, _: Platform) -> Module: 126 | """The logic.""" 127 | m = Module() 128 | 129 | if self._faster: 130 | attrs = [] if not self._ext_init else [("uninitialized", "")] 131 | _q = Signal(32, attrs=attrs) 132 | m.d.comb += self.q.eq(_q) 133 | c = m.d[self.clk] 134 | for i in range(self.N): 135 | with m.If(~self.n_sel[i]): 136 | c += _q.eq(self.d[i]) 137 | return m 138 | 139 | r = IC_reg32(self.clk, self._ext_init) 140 | mux = IC_mux32(self.N + 1) 141 | m.submodules += [r, mux] 142 | 143 | m.d.comb += r.n_oe.eq(0) 144 | m.d.comb += r.d.eq(mux.y) 145 | m.d.comb += self.q.eq(r.q) 146 | 147 | m.d.comb += mux.n_sel.eq(self.n_sel) 148 | m.d.comb += mux.n_sel[-1].eq(~self.n_sel != 0) 149 | 150 | for i in range(self.N): 151 | m.d.comb += mux.a[i].eq(self.d[i]) 152 | m.d.comb += mux.a[-1].eq(r.q) 153 | 154 | return m 155 | 156 | @classmethod 157 | def formal(cls) -> Tuple[Module, List[Signal]]: 158 | m = Module() 159 | ph = ClockDomain("ph") 160 | clk = ClockSignal("ph") 161 | 162 | m.domains += ph 163 | m.d.sync += clk.eq(~clk) 164 | 165 | s = IC_reg32_with_mux(clk="ph", N=2, faster=True) 166 | 167 | m.submodules += s 168 | 169 | sync_clk = ClockSignal("sync") 170 | sync_rst = ResetSignal("sync") 171 | 172 | with m.If(Rose(clk)): 173 | with m.Switch(~Past(s.n_sel)): 174 | with m.Case(0b11): 175 | m.d.comb += Assert(0) 176 | with m.Case(0b01): 177 | m.d.comb += Assert(s.q == Past(s.d[0])) 178 | with m.Case(0b10): 179 | m.d.comb += Assert(s.q == Past(s.d[1])) 180 | with m.Default(): 181 | m.d.comb += Assert(s.q == Past(s.q)) 182 | 183 | # Make sure the clock is clocking 184 | m.d.comb += Assume(sync_clk == ~Past(sync_clk)) 185 | 186 | # Don't want to test what happens when we reset. 187 | m.d.comb += Assume(~sync_rst) 188 | m.d.comb += Assume(~ResetSignal("ph")) 189 | 190 | m.d.comb += Assume(s.n_sel != 0) 191 | 192 | return m, [sync_clk, sync_rst, s.d[0], s.d[1], s.n_sel, s.q] 193 | 194 | 195 | class IC_reg32_with_load(Elaboratable): 196 | """A 32-bit register that loads or retains its value.""" 197 | 198 | def __init__(self, clk): 199 | self.clk = clk 200 | self.d = Signal(32) 201 | self.n_oe = Signal() 202 | self.load = Signal() 203 | self.q = Signal(32) 204 | 205 | def elaborate(self, _: Platform) -> Module: 206 | """Implements the logic of the register.""" 207 | m = Module() 208 | reg = IC_reg32(self.clk) 209 | mux = IC_mux32(2) 210 | 211 | m.submodules += [reg, mux] 212 | 213 | m.d.comb += reg.n_oe.eq(self.n_oe) 214 | 215 | m.d.comb += mux.a[0].eq(reg.q) 216 | m.d.comb += mux.a[1].eq(self.d) 217 | m.d.comb += mux.n_sel[0].eq(self.load) 218 | m.d.comb += mux.n_sel[1].eq(~self.load) 219 | m.d.comb += reg.d.eq(mux.y) 220 | m.d.comb += self.q.eq(reg.q) 221 | 222 | return m 223 | 224 | @classmethod 225 | def formal(cls) -> Tuple[Module, List[Signal]]: 226 | m = Module() 227 | ph = ClockDomain("clk") 228 | clk = ClockSignal("ph") 229 | 230 | m.domains += ph 231 | m.d.sync += clk.eq(~clk) 232 | 233 | s = IC_reg32_with_load(clk="ph") 234 | 235 | m.submodules += s 236 | 237 | sync_clk = ClockSignal("sync") 238 | sync_rst = ResetSignal("sync") 239 | 240 | with m.If(s.n_oe): 241 | m.d.comb += Assert(s.q == 0) 242 | 243 | with m.Else(): 244 | with m.If(~Past(s.n_oe) & ~Past(s.load)): 245 | m.d.comb += Assert(s.q == Past(s.q)) 246 | 247 | with m.If(Past(s.load) & Rose(clk)): 248 | m.d.comb += Assert(s.q == Past(s.d)) 249 | 250 | with m.If(~Past(s.n_oe) & Fell(clk)): 251 | m.d.comb += Assert(s.q == Past(s.q)) 252 | 253 | # Make sure the clock is clocking 254 | m.d.comb += Assume(sync_clk == ~Past(sync_clk)) 255 | 256 | # Don't want to test what happens when we reset. 257 | m.d.comb += Assume(~sync_rst) 258 | m.d.comb += Assume(~ResetSignal("ph")) 259 | 260 | return m, [sync_clk, sync_rst, s.n_oe, s.load, s.d] 261 | 262 | 263 | if __name__ == "__main__": 264 | main(IC_reg32_with_mux) 265 | -------------------------------------------------------------------------------- /reg_card.py: -------------------------------------------------------------------------------- 1 | # Disable pylint's "your name is too short" warning. 2 | # pylint: disable=C0103 3 | from typing import List, Tuple 4 | 5 | from nmigen import Signal, Module, Elaboratable, ClockDomain, ClockSignal, Array 6 | from nmigen import Mux 7 | from nmigen.build import Platform 8 | from nmigen.asserts import Assert, Assume, Cover, Past, Stable, Rose, AnyConst, Initial 9 | 10 | from async_memory import AsyncMemory 11 | from transparent_latch import TransparentLatch 12 | from util import main 13 | 14 | 15 | class RegCard(Elaboratable): 16 | """Logic for the register card. 17 | 18 | Attributes: 19 | data_x: The X bus, always written to. 20 | data_y: The Y bus, always written to. 21 | data_z: The Z bus, always read from. 22 | reg_to_x: The control line indicating we want to output a register to X. 23 | reg_to_y: The control line indicating we want to output a register to Y. 24 | reg_x: The register to output to X. 25 | reg_y: The register to output to Y. 26 | reg_z: The register to write from Z. 27 | reg_page: The 32-register page to access. 28 | 29 | The card optionally outputs the data in reg_x to the data_x bus, 30 | and the data in reg_y to the data_y bus. It also writes the data 31 | on the data_z bus to reg_z. Of course, reg_z should be zero if you 32 | don't really want to write anything. 33 | 34 | This module uses two system-wide clocks: ph1 and ph2. The phases look 35 | like this: 36 | 37 | ________ ________ 38 | ph1 _| RD |___WR___| RD |___WR___| 39 | ___ ____ ____ ____ ___ 40 | ph2 |___| |___| |___| |___| 41 | 42 | ph1 controls whether we're reading or writing the memories, while 43 | ph2 controls the read/write pulse (and memory input buffers) to the memories 44 | if we are writing and latches if we are reading. 45 | 46 | The phases also don't both change at the same time. 47 | 48 | Strictly speaking that's 6 clocks per machine cycle. 49 | """ 50 | 51 | data_x: Signal 52 | data_y: Signal 53 | data_z: Signal 54 | reg_to_x: Signal 55 | reg_to_y: Signal 56 | reg_x: Signal 57 | reg_y: Signal 58 | reg_z: Signal 59 | reg_page: Signal 60 | 61 | def __init__(self, ext_init: bool = False): 62 | """Constructs a register card.""" 63 | attrs = [] if not ext_init else [("uninitialized", "")] 64 | 65 | # Buses 66 | self.data_x = Signal(32) 67 | self.data_y = Signal(32) 68 | self.data_z = Signal(32) 69 | 70 | # Controls 71 | self.reg_to_x = Signal() 72 | self.reg_to_y = Signal() 73 | self.reg_x = Signal(5) 74 | self.reg_y = Signal(5) 75 | self.reg_z = Signal(5) 76 | self.reg_page = Signal() 77 | 78 | # Submodules 79 | self._x_bank = AsyncMemory(width=32, addr_lines=6, ext_init=ext_init) 80 | self._y_bank = AsyncMemory(width=32, addr_lines=6, ext_init=ext_init) 81 | self._x_latch = TransparentLatch(size=32) 82 | self._y_latch = TransparentLatch(size=32) 83 | self._x_bank_wr_latch = TransparentLatch(size=32) 84 | self._y_bank_wr_latch = TransparentLatch(size=32) 85 | 86 | def elaborate(self, _: Platform) -> Module: 87 | """Implements the logic of the register card.""" 88 | m = Module() 89 | 90 | ph1 = ClockSignal("ph1") 91 | ph2 = ClockSignal("ph2") 92 | x_bank = self._x_bank 93 | y_bank = self._y_bank 94 | x_latch = self._x_latch 95 | y_latch = self._y_latch 96 | x_bank_wr_latch = self._x_bank_wr_latch 97 | y_bank_wr_latch = self._y_bank_wr_latch 98 | 99 | m.submodules.x_bank = x_bank 100 | m.submodules.y_bank = y_bank 101 | m.submodules.x_latch = x_latch 102 | m.submodules.y_latch = y_latch 103 | m.submodules.x_bank_wr_latch = x_bank_wr_latch 104 | m.submodules.y_bank_wr_latch = y_bank_wr_latch 105 | 106 | # Some aliases 107 | read_phase = ph1 108 | write_phase = ~ph1 109 | read_pulse = ph1 & ~ph2 # high pulse during read phase 110 | write_pulse = ph1 | ph2 # low pulse during write phase 111 | 112 | # Checks for register 0 113 | x_reg_0 = Signal() 114 | y_reg_0 = Signal() 115 | m.d.comb += [ 116 | x_reg_0.eq(self.reg_x == 0), 117 | y_reg_0.eq(self.reg_y == 0), 118 | ] 119 | 120 | # Write signals for both banks are tied together. 121 | n_wr = Signal() 122 | m.d.comb += [ 123 | x_bank.n_wr.eq(n_wr), 124 | y_bank.n_wr.eq(n_wr), 125 | ] 126 | 127 | # Pass through the write pulse to the memory banks. 128 | m.d.comb += n_wr.eq(write_pulse) 129 | 130 | # We also read from the memories on ph1, but only if we're 131 | # not reading from register 0. Strictly speaking this doesn't 132 | # matter, since if we are reading from register 0, the memory 133 | # output is switched out by the output multiplexer. 134 | x_bank_oe = ~x_reg_0 & read_phase 135 | y_bank_oe = ~y_reg_0 & read_phase 136 | m.d.comb += [ 137 | x_bank.n_oe.eq(~x_bank_oe), 138 | y_bank.n_oe.eq(~y_bank_oe), 139 | ] 140 | 141 | # The address for each bank is switched between x/y during the 142 | # read phase, and z during the write phase. 143 | x_addr = Signal(6) 144 | y_addr = Signal(6) 145 | m.d.comb += [ 146 | x_addr[:5].eq(Mux(read_phase, self.reg_x, self.reg_z)), 147 | y_addr[:5].eq(Mux(read_phase, self.reg_y, self.reg_z)), 148 | x_addr[5].eq(self.reg_page), 149 | y_addr[5].eq(self.reg_page), 150 | x_bank.addr.eq(x_addr), 151 | y_bank.addr.eq(y_addr), 152 | ] 153 | 154 | # The memory data is the data coming out of the memory, or the 155 | # data coming out of the write latches. Since high-Z is simulated 156 | # by outputting zeros, this is fine. 157 | x_mem_data = Signal(32) 158 | y_mem_data = Signal(32) 159 | m.d.comb += [ 160 | x_mem_data.eq(x_bank.data_out | x_bank_wr_latch.data_out), 161 | y_mem_data.eq(y_bank.data_out | y_bank_wr_latch.data_out), 162 | ] 163 | 164 | # The memory data inputs are the memory data. 165 | m.d.comb += [ 166 | x_bank.data_in.eq(x_mem_data), 167 | y_bank.data_in.eq(y_mem_data), 168 | ] 169 | 170 | # The write latches aren't actually used as latches, just 171 | # tri-state buffers. They are always transparent. But, n_oe 172 | # is used to control the output. 173 | wr_latch_oe = ~n_wr 174 | m.d.comb += [ 175 | x_bank_wr_latch.data_in.eq(self.data_z), 176 | y_bank_wr_latch.data_in.eq(self.data_z), 177 | x_bank_wr_latch.le.eq(1), 178 | y_bank_wr_latch.le.eq(1), 179 | x_bank_wr_latch.n_oe.eq(~wr_latch_oe), 180 | y_bank_wr_latch.n_oe.eq(~wr_latch_oe), 181 | ] 182 | 183 | # The bus output latches take data from the memory data and 184 | # optionally pass it to the x/y buses, transparently during 185 | # the read cycle, but the data is latched so it remains during 186 | # the write cycle. However, the output of the latches to the 187 | # buses is controlled by the control signals. 188 | # 189 | # Also, multiplex between the memory data and zero depending 190 | # on whether we're reading register 0. 191 | x_zero_oe = x_reg_0 & read_phase 192 | y_zero_oe = y_reg_0 & read_phase 193 | 194 | m.d.comb += [ 195 | x_latch.data_in.eq(Mux(x_zero_oe, 0, x_mem_data)), 196 | y_latch.data_in.eq(Mux(y_zero_oe, 0, y_mem_data)), 197 | x_latch.le.eq(read_pulse), 198 | y_latch.le.eq(read_pulse), 199 | x_latch.n_oe.eq(~self.reg_to_x), 200 | y_latch.n_oe.eq(~self.reg_to_y), 201 | ] 202 | 203 | # The x and y buses get the output latch outputs. 204 | m.d.comb += [ 205 | self.data_x.eq(x_latch.data_out), 206 | self.data_y.eq(y_latch.data_out), 207 | ] 208 | 209 | return m 210 | 211 | @classmethod 212 | def formal(cls) -> Tuple[Module, List[Signal]]: 213 | """Formal verification for the register card.""" 214 | m = Module() 215 | 216 | ph1 = ClockDomain("ph1") 217 | ph2 = ClockDomain("ph2") 218 | regs = RegCard() 219 | 220 | m.domains += [ph1, ph2] 221 | m.submodules += regs 222 | 223 | # Generate the ph1 and ph2 clocks. 224 | cycle_count = Signal(8, reset=0, reset_less=True) 225 | phase_count = Signal(3, reset=0, reset_less=True) 226 | 227 | m.d.sync += phase_count.eq(phase_count + 1) 228 | with m.Switch(phase_count): 229 | with m.Case(0, 1, 2): 230 | m.d.comb += ph1.clk.eq(1) 231 | with m.Default(): 232 | m.d.comb += ph1.clk.eq(0) 233 | with m.Switch(phase_count): 234 | with m.Case(1, 4): 235 | m.d.comb += ph2.clk.eq(0) 236 | with m.Default(): 237 | m.d.comb += ph2.clk.eq(1) 238 | with m.If(phase_count == 5): 239 | m.d.sync += phase_count.eq(0) 240 | m.d.sync += cycle_count.eq(cycle_count + 1) 241 | 242 | # This is how we expect to use the card. 243 | with m.If(phase_count > 0): 244 | m.d.comb += [ 245 | Assume(Stable(regs.reg_x)), 246 | Assume(Stable(regs.reg_y)), 247 | Assume(Stable(regs.reg_z)), 248 | Assume(Stable(regs.reg_page)), 249 | Assume(Stable(regs.reg_to_x)), 250 | Assume(Stable(regs.reg_to_y)), 251 | Assume(Stable(regs.data_z)), 252 | ] 253 | 254 | # Figure out how to get to the point where X and Y are nonzero and different. 255 | m.d.comb += Cover((regs.data_x != 0) & (regs.data_y != 0) 256 | & (regs.data_x != regs.data_y)) 257 | 258 | # X and Y buses should not change during a cycle, except for the first phase 259 | with m.Switch(phase_count): 260 | with m.Case(2, 3, 4, 5): 261 | with m.If(regs.data_x != 0): 262 | m.d.comb += Assert(Stable(regs.data_x)) 263 | with m.If(regs.data_y != 0): 264 | m.d.comb += Assert(Stable(regs.data_y)) 265 | 266 | # X and Y buses should be zero if there is no data transfer. 267 | with m.If(regs.reg_to_x == 0): 268 | m.d.comb += Assert(regs.data_x == 0) 269 | with m.If(regs.reg_to_y == 0): 270 | m.d.comb += Assert(regs.data_y == 0) 271 | 272 | with m.If(phase_count > 0): 273 | # X and Y buses should be zero if we read from register 0. 274 | with m.If(regs.reg_to_x & (regs.reg_x == 0)): 275 | m.d.comb += Assert(regs.data_x == 0) 276 | with m.If(regs.reg_to_y & (regs.reg_y == 0)): 277 | m.d.comb += Assert(regs.data_y == 0) 278 | 279 | write_pulse = Signal() 280 | m.d.comb += write_pulse.eq(phase_count != 4) 281 | 282 | # On write, the data should have been written to both banks. 283 | past_mem_addr = Signal(6) 284 | m.d.comb += past_mem_addr[:5].eq(Past(regs.reg_z)) 285 | m.d.comb += past_mem_addr[5].eq(Past(regs.reg_page)) 286 | past_z = Past(regs.data_z) 287 | with m.If(Rose(write_pulse)): 288 | m.d.comb += Assert(regs._x_bank._mem[past_mem_addr] == past_z) 289 | m.d.comb += Assert(regs._y_bank._mem[past_mem_addr] == past_z) 290 | 291 | # Pick an register, any register, except 0. We assert that unless 292 | # it is written, its data will not change. 293 | 294 | check_addr = AnyConst(5) 295 | check_page = AnyConst(1) 296 | saved_data = Signal(32) 297 | stored_x_data = Signal(32) 298 | stored_y_data = Signal(32) 299 | 300 | write_pulse_domain = ClockDomain("write_pulse_domain", local=True) 301 | m.domains.write_pulse_domain = write_pulse_domain 302 | write_pulse_domain.clk = write_pulse 303 | 304 | mem_addr = Signal(6) 305 | 306 | m.d.comb += Assume(check_addr != 0) 307 | m.d.comb += [ 308 | mem_addr[:5].eq(check_addr), 309 | mem_addr[5].eq(check_page), 310 | stored_x_data.eq(regs._x_bank._mem[mem_addr]), 311 | stored_y_data.eq(regs._y_bank._mem[mem_addr]), 312 | ] 313 | 314 | with m.If((regs.reg_z == check_addr) & (regs.reg_page == check_page)): 315 | m.d.write_pulse_domain += saved_data.eq(regs.data_z) 316 | 317 | with m.If(Initial()): 318 | m.d.comb += Assume(saved_data == stored_x_data) 319 | m.d.comb += Assume(stored_x_data == stored_y_data) 320 | with m.Else(): 321 | m.d.comb += Assert(saved_data == stored_x_data) 322 | m.d.comb += Assert(saved_data == stored_y_data) 323 | 324 | return m, [regs.data_z, regs.reg_to_x, regs.reg_to_y, 325 | regs.reg_x, regs.reg_y, regs.reg_z, regs.reg_page, ph1.clk, ph2.clk, saved_data, 326 | stored_x_data, stored_y_data] 327 | 328 | 329 | if __name__ == "__main__": 330 | main(RegCard) 331 | -------------------------------------------------------------------------------- /pla_parser.py: -------------------------------------------------------------------------------- 1 | # Minimally parses a PLA file. 2 | import json 3 | import os 4 | import pprint 5 | import sys 6 | 7 | import pyeda 8 | 9 | from typing import List, Dict 10 | 11 | from munkres import Munkres, make_cost_matrix, DISALLOWED, print_matrix 12 | from pyeda.inter import * 13 | 14 | # PLA files that I'm interested in look like this: 15 | # 16 | # # Benchmark "top" written by ABC on Mon Jan 25 13:56:23 2021 17 | # .i 12 18 | # .o 8 19 | # .ilb b[0] b[1] b[2] b[3] s[0] s[1] s[2] s[3] a[0] a[1] a[2] a[3] 20 | # .ob x[0] x[1] x[2] x[3] x[4] x[5] x[6] x[7] 21 | # .p 20 22 | # 1---0---0--- 10000000 23 | # 0----0--0--- 10000000 24 | # ...more lines 25 | # .e 26 | # 27 | # Some format information here: 28 | # http://www.ecs.umass.edu/ece/labs/vlsicad/ece667/links/espresso.5.html 29 | # 30 | # So the format spec is: 31 | # A '#' in the first character of the line is a comment. 32 | # .i %d: 33 | # Number of input variables 34 | # .o %d: 35 | # Number of output functions 36 | # .ilb : 37 | # Names of input variables. Must come after .i. There must be the same 38 | # number of names as there is in .i. 39 | # .ob : 40 | # Names of output functions. Must come after .o. There must be the same 41 | # number of names as there is in .o. 42 | # .p %d: 43 | # Number of product terms. May be ignored. 44 | # .e or .end: 45 | # Optionally marks end of description. 46 | # Product term line: 47 | # .i number of 1/0/- characters, followed by whitespace, followed by 48 | # .o number of 1/0 characters. These are in the same order as the 49 | # input and output names. 50 | 51 | # Note that this kind of PLA file only represents and-or (aka sum-of-products). 52 | # Because we're also interested in xor layers, and also multiple layers, we 53 | # have to use multiples of these files, and also a custom file for xor layers. 54 | 55 | 56 | class ProductTerm(): 57 | ones: List[str] 58 | zeros: List[str] 59 | 60 | def __init__(self): 61 | # List of symbolic inputs 62 | self.ones = [] 63 | self.zeros = [] 64 | self.expr = expr(1) 65 | 66 | def __repr__(self): 67 | pp = pprint.PrettyPrinter(indent=4) 68 | return pp.pformat({'ones': self.ones, 'zeros': self.zeros}) 69 | 70 | 71 | class OrTerm(): 72 | products: List[ProductTerm] 73 | 74 | def __init__(self): 75 | self.products = [] 76 | self.expr = expr(0) 77 | 78 | def __repr__(self): 79 | pp = pprint.PrettyPrinter(indent=4) 80 | return pp.pformat({'or_products': self.products, 'expr': self.expr}) 81 | 82 | 83 | def get_database(): 84 | for path in sys.path: 85 | file = os.path.join(path, "database.json") 86 | if os.path.isfile(file): 87 | with open(file) as f: 88 | return json.load(f) 89 | return None 90 | 91 | 92 | class PLAParser(): 93 | inputs: List[str] 94 | outputs: List[str] 95 | or_terms: Dict[str, OrTerm] 96 | # If the file is marked with .xor, it's an XOR layer. 97 | is_xor: bool 98 | # If the file is marked with .outputs, all outputs are routed to pins. 99 | is_outputs: bool 100 | 101 | def __init__(self, file: str): 102 | self.inputs = [] 103 | self.outputs = [] 104 | # A map of symbolic output to OrTerm (or Xor) 105 | self.or_terms = {} 106 | self.is_xor = False 107 | self.is_outputs = False 108 | 109 | with open(file) as f: 110 | for line in f.readlines(): 111 | if not self.readline(line): 112 | break 113 | print(f"Inputs : {self.inputs}") 114 | print(f"Outputs : {self.outputs}") 115 | pp = pprint.PrettyPrinter(indent=4, depth=3) 116 | print(f"OR Terms:") 117 | pprint.pprint(self.or_terms) 118 | 119 | def readline(self, line: str) -> bool: 120 | """Returns if there are more lines to parse.""" 121 | if len(line) == 0: 122 | return True 123 | if line.startswith('#'): 124 | return True 125 | if line.startswith(".i "): 126 | return True 127 | if line.startswith(".o "): 128 | return True 129 | if line.startswith(".p "): 130 | return True 131 | if line.startswith(".e") | line.startswith(".end"): 132 | return False 133 | if line.startswith(".xor"): 134 | assert not self.is_outputs 135 | self.is_xor = True 136 | if line.startswith(".outputs"): 137 | assert not self.is_xor 138 | self.is_outputs = True 139 | if line.startswith(".ilb "): 140 | self.inputs = line.split()[1:] 141 | return True 142 | if line.startswith(".ob "): 143 | self.outputs = line.split()[1:] 144 | for output in self.outputs: 145 | self.or_terms[output] = OrTerm() 146 | return True 147 | if line.startswith("1") | line.startswith("0") | line.startswith("-"): 148 | assert not self.is_outputs 149 | if self.is_xor: 150 | self.read_xor_term(line) 151 | else: 152 | self.read_or_term(line) 153 | return True 154 | return True 155 | 156 | def read_or_term(self, line: str): 157 | parts = line.split() 158 | assert len(parts) == 2 159 | assert len(parts[0]) == len(self.inputs) 160 | assert len(parts[1]) == len(self.outputs) 161 | 162 | inputs = parts[0] 163 | outputs = parts[1] 164 | product = ProductTerm() 165 | terms = [] 166 | for i, bit in enumerate(inputs): 167 | if bit == '0': 168 | product.zeros.append(self.inputs[i]) 169 | terms.append(Not(self.inputs[i])) 170 | elif bit == '1': 171 | product.ones.append(self.inputs[i]) 172 | terms.append(self.inputs[i]) 173 | product.expr = And(*terms) 174 | 175 | for i, bit in enumerate(outputs): 176 | if bit == '1': 177 | self.or_terms[self.outputs[i]].products.append(product) 178 | self.or_terms[self.outputs[i]].expr = Or( 179 | self.or_terms[self.outputs[i]].expr, product.expr) 180 | 181 | def read_xor_term(self, line: str): 182 | parts = line.split() 183 | assert len(parts) == 2 184 | assert len(parts[0]) == len(self.inputs) 185 | assert len(parts[1]) == len(self.outputs) 186 | 187 | inputs = parts[0] 188 | outputs = parts[1] 189 | terms = [] 190 | for i, bit in enumerate(inputs): 191 | if bit == '1': 192 | terms.append(self.inputs[i]) 193 | for i, bit in enumerate(outputs): 194 | if bit == '1': 195 | self.or_terms[self.outputs[i]].expr = Xor(*terms) 196 | 197 | 198 | class Fitter(): 199 | inputs: List[str] 200 | or_terms: Dict[str, OrTerm] 201 | all_or_terms: Dict[str, Dict[str, OrTerm]] 202 | input_mcs: Dict[str, int] 203 | input_sigs: Dict[str, str] 204 | 205 | def __init__(self): 206 | self.device = None 207 | self.next_mc = 1 208 | 209 | self.inputs = [] 210 | self.outputs = [] 211 | # A map of symbolic output to OrTerm 212 | self.or_terms = {} 213 | # A map of block to map of MC to OrTerm 214 | self.all_or_terms = {} 215 | self.all_or_exprs = {} 216 | 217 | # A map of symbolic input to macrocell number 218 | self.input_mcs = {} 219 | # A map of symbolic input to multiplexer signal name 220 | self.input_sigs = {} 221 | 222 | def map_inputs(self): 223 | print("Mapping pin inputs") 224 | db = get_database() 225 | self.device = db["ATF1502AS"] 226 | 227 | # For now, assuming this is an input layer, map inputs directly onto 228 | # MCs starting with MC1. We can use an input MC as an intermediate 229 | # output by routing its output to MCn_FB. 230 | 231 | self.input_mcs = {top_input: self.get_next_mc() 232 | for top_input in self.inputs} 233 | for top_input, input_mc in self.input_mcs.items(): 234 | pin = self.device["pins"]["PLCC44"][f"M{input_mc}"] 235 | self.input_sigs = {top_input: f"M{input_mc}_PAD" for top_input, 236 | input_mc in self.input_mcs.items()} 237 | print(f"assign input {top_input} to MC{input_mc} (pin {pin})") 238 | print(f" set MC{input_mc}.oe_mux GND") 239 | 240 | # This isn't accurate. It's only accurate when the number of intermediate 241 | # outputs exceeds the number of inputs. 242 | self.next_mc = 1 243 | 244 | # Initialize blocks in all_or_terms 245 | for block in self.device["blocks"].keys(): 246 | self.all_or_terms[block] = {} 247 | self.all_or_exprs[block] = {} 248 | 249 | def get_next_mc(self) -> int: 250 | specials = [4, 9, 25, 20] # TDI, TMS, TCK, TDO 251 | if self.next_mc in specials: 252 | self.next_mc += 2 253 | elif self.next_mc > 32: 254 | return None 255 | else: 256 | self.next_mc += 1 257 | return self.next_mc-1 258 | 259 | def map_output_layer(self): 260 | device = self.device 261 | 262 | for i, output in enumerate(self.outputs): 263 | mc = self.input_mcs[output] 264 | pin = device["pins"]["PLCC44"][f"M{mc}"] 265 | print(f"Output {output} is at MC{mc} (pin {pin})") 266 | print(f" set MC{mc}.o_mux comb") 267 | print(f" set MC{mc}.oe_mux pt5") 268 | print(f" set MC{mc}.pt5_func as") 269 | 270 | def map_and_or_layer(self): 271 | print("Mapping AND-OR layer") 272 | device = self.device 273 | 274 | # For now, map the outputs directly onto MCs starting with 275 | # MC1. 276 | for output in self.outputs: 277 | or_term = self.or_terms[output] 278 | or_expr = or_term.expr 279 | inv = False 280 | print(f"{output} = {or_term.expr}") 281 | if isinstance(or_expr, pyeda.boolalg.expr.OrOp) and len(or_expr.xs) > 5: 282 | # Maybe we can invert, and then use the macrocell's inverter to invert 283 | # the result? 284 | nor_expr = espresso_exprs(Not(or_term.expr).to_dnf()) 285 | # espresso_expr returns a tuple 286 | # to_dnf converts an expression to disjunctive normal form 287 | # (i.e. sum of products). 288 | nor_expr = nor_expr[0].to_dnf() 289 | print(f"Try the inverse of this instead: {nor_expr}") 290 | if isinstance(nor_expr, pyeda.boolalg.expr.OrOp) and len(or_expr.xs) > 5: 291 | print( 292 | f"ERROR: or-term for {output} needs more than" 293 | " one macrocell (5 products), which is not supported yet.") 294 | return 295 | or_expr = nor_expr 296 | inv = True 297 | 298 | mc = self.get_next_mc() 299 | assert mc is not None, "Ran out of macrocells" 300 | mc_name = f"MC{mc}" 301 | macrocell = device["macrocells"][mc_name] 302 | block = macrocell["block"] 303 | print(f"output {output} mapped to {mc_name}.FB in block {block}") 304 | self.all_or_terms[block][mc_name] = or_term 305 | self.all_or_exprs[block][mc_name] = or_expr 306 | self.input_mcs[output] = mc 307 | self.input_sigs[output] = f"MC{mc}_FB" 308 | 309 | print(f"set {mc_name}.pt_power on") 310 | print(f"set {mc_name}.pt1_mux sum") 311 | print(f"set {mc_name}.pt2_mux sum") 312 | print(f"set {mc_name}.pt3_mux sum") 313 | print(f"set {mc_name}.pt4_mux sum") 314 | print(f"set {mc_name}.pt5_mux sum") 315 | print(f"set {mc_name}.fb_mux xt") 316 | print(f"set {mc_name}.xor_a_mux sum") 317 | print(f"set {mc_name}.xor_b_mux VCC_pt12") 318 | 319 | # It's weird, but because we have to feed a 1 into one input of 320 | # the macrocell's XOR, it naturally inverts. There's another 321 | # optional inverter after that, so if we want the non-inverted 322 | # output of the OR gate, we have to turn that inverter on! 323 | if inv: 324 | print(f"set {mc_name}.xor_invert off") 325 | else: 326 | print(f"set {mc_name}.xor_invert on") 327 | 328 | # Now that we've mapped inputs to outputs, 329 | # add them to the inputs and clear out the outputs. 330 | self.inputs += self.outputs 331 | self.outputs = [] 332 | 333 | print("Input mcs:") 334 | pprint.pprint(self.input_mcs) 335 | print("Input sigs:") 336 | pprint.pprint(self.input_sigs) 337 | 338 | def map_and_xor_layer(self): 339 | print("Mapping XOR layer") 340 | device = self.device 341 | 342 | # For now, map the outputs directly onto MCs starting with 343 | # the next MC 344 | for output in self.outputs: 345 | expr = self.or_terms[output].expr 346 | assert isinstance(expr, pyeda.boolalg.expr.XorOp) 347 | if len(expr.xs) != 2: 348 | print( 349 | f"ERROR: xor-term for {output} does not have 2 products, which is not supported yet.") 350 | return 351 | mc = self.get_next_mc() 352 | assert mc is not None, "Ran out of macrocells" 353 | mc_name = f"MC{mc}" 354 | macrocell = device["macrocells"][mc_name] 355 | block = macrocell["block"] 356 | print(f"output {output} mapped to {mc_name}.FB in block {block}") 357 | self.all_or_exprs[block][mc_name] = expr 358 | self.input_mcs[output] = mc 359 | self.input_sigs[output] = f"MC{mc}_FB" 360 | 361 | print(f"set {mc_name}.pt_power on") 362 | print(f"set {mc_name}.pt1_mux sum") 363 | print(f"set {mc_name}.pt2_mux xor") 364 | print(f"set {mc_name}.pt3_mux sum") 365 | print(f"set {mc_name}.pt4_mux sum") 366 | print(f"set {mc_name}.pt5_mux sum") 367 | print(f"set {mc_name}.fb_mux xt") 368 | print(f"set {mc_name}.xor_a_mux sum") 369 | print(f"set {mc_name}.xor_b_mux VCC_pt12") 370 | print(f"set {mc_name}.xor_invert on") 371 | 372 | # Now that we've mapped inputs to outputs, 373 | # add them to the inputs and clear out the outputs. 374 | self.inputs += self.outputs 375 | self.outputs = [] 376 | 377 | print("Input mcs:") 378 | pprint.pprint(self.input_mcs) 379 | print("Input sigs:") 380 | pprint.pprint(self.input_sigs) 381 | 382 | def set_uims(self): 383 | # Collect all MCn_FB and Mn_PAD before choosing UIMs for each block. 384 | # This is an instance of the assignment problem, which we solve using the 385 | # Hungarian algorithm, which is O(n^3). The hope is that because the matrix 386 | # is extremely sparse, the algorithm runs very quickly. 387 | 388 | switches = self.device["switches"] 389 | 390 | # Map signals to UIMs, per block 391 | sig_to_uim = {} 392 | for blk in dev["blocks"].keys(): 393 | sig_to_uim[blk] = {} 394 | for switch, data in switches.items(): 395 | blk = data["block"] 396 | switch_sigs = data["mux"]["values"].keys() 397 | for sig in switch_sigs: 398 | if sig not in sig_to_uim[blk]: 399 | sig_to_uim[blk][sig] = [] 400 | sig_to_uim[blk][sig].append(switch) 401 | 402 | for blk in self.all_or_exprs: 403 | print(f"Constructing set of signals in block {blk}") 404 | # Construct the set of needed signals. 405 | sigs = set() 406 | for or_expr in self.all_or_exprs[blk].values(): 407 | sigs.update(set(self.input_sigs[str(term)] 408 | for term in or_expr.support)) 409 | 410 | # Convert to ordered array 411 | sigs = [s for s in sigs] 412 | if len(sigs) == 0: 413 | print(f"No used signals in block {blk}") 414 | continue 415 | print(f"Used signals in block {blk}: {sigs}") 416 | 417 | # Construct the set of candidate switches for those signals. 418 | candidate_switches = set() 419 | for sig in sigs: 420 | candidate_switches.update(set(s for s in sig_to_uim[blk][sig])) 421 | # Convert to ordered array 422 | candidate_switches = [s for s in candidate_switches] 423 | print(f"Candidate switches in block {blk}: {candidate_switches}") 424 | 425 | # Construct the cost matrix. We assign an different cost per candidate 426 | # switch to help the algorithm be stable. 427 | matrix = [[DISALLOWED for _ in range( 428 | len(candidate_switches))] for _ in range(len(sigs))] 429 | for row, sig in enumerate(sigs): 430 | cost = 1 431 | for candidate_switch in sig_to_uim[blk][sig]: 432 | col = candidate_switches.index(candidate_switch) 433 | matrix[row][col] = cost 434 | cost += 1 435 | cost_matrix = make_cost_matrix( 436 | matrix, lambda cost: cost if cost != DISALLOWED else DISALLOWED) 437 | 438 | # Assign signals to switches. 439 | m = Munkres() 440 | indexes = m.compute(cost_matrix) 441 | sig_to_switch = {} 442 | # print_matrix(matrix, 'Based on this matrix:') 443 | print("Setting UIM fuses:") 444 | for r, c in indexes: 445 | v = matrix[r][c] 446 | print(f"set {candidate_switches[c]} {sigs[r]}") 447 | sig_to_switch[sigs[r]] = candidate_switches[c] 448 | # pprint.pprint(sig_to_switch) 449 | 450 | print("Setting product term fuses:") 451 | for mc_name, or_expr in self.all_or_exprs[blk].items(): 452 | products = or_expr.xs if isinstance(or_expr, pyeda.boolalg.expr.OrOp) or isinstance( 453 | or_expr, pyeda.boolalg.expr.XorOp) else [or_expr] 454 | 455 | for ptn, product in enumerate(products): 456 | terms = product.xs if isinstance( 457 | product, pyeda.boolalg.expr.AndOp) else [product] 458 | for sig in terms: 459 | inv = isinstance(sig, pyeda.boolalg.expr.Complement) 460 | sig = str(Not(sig) if inv else sig) 461 | uim = sig_to_switch[self.input_sigs[sig]] 462 | switch_polarity = "_N" if inv else "_P" 463 | print( 464 | f" set {mc_name}.PT{ptn} +{uim}{switch_polarity}") 465 | 466 | 467 | if __name__ == "__main__": 468 | db = get_database() 469 | dev = db["ATF1502AS"] 470 | 471 | parse = PLAParser(sys.argv[1]) 472 | 473 | p = Fitter() 474 | p.inputs = parse.inputs 475 | p.outputs = parse.outputs 476 | p.or_terms = parse.or_terms 477 | 478 | p.map_inputs() 479 | 480 | for arg in sys.argv[1:]: 481 | parse = PLAParser(arg) 482 | p.outputs = parse.outputs 483 | p.or_terms = parse.or_terms 484 | 485 | if parse.is_xor: 486 | p.map_and_xor_layer() 487 | elif parse.is_outputs: 488 | p.map_output_layer() 489 | else: 490 | p.map_and_or_layer() 491 | 492 | p.set_uims() 493 | -------------------------------------------------------------------------------- /ics.py: -------------------------------------------------------------------------------- 1 | # Disable pylint's "your name is too short" warning. 2 | # pylint: disable=C0103 3 | """Modules for ICs.""" 4 | from typing import List, Tuple 5 | 6 | from nmigen import Signal, Module, Elaboratable, Cat 7 | from nmigen.build import Platform 8 | from nmigen.asserts import Assert, Assume 9 | 10 | from util import main 11 | 12 | 13 | class IC_7416373(Elaboratable): 14 | """Logic for the 7416373 16-bit transparent latch.""" 15 | 16 | def __init__(self): 17 | pass 18 | 19 | def elaborate(self, _: Platform) -> Module: 20 | """Implements the logic of the 7416373 chip.""" 21 | m = Module() 22 | return m 23 | 24 | 25 | class IC_74181(Elaboratable): 26 | """Logic for the 74181 4-bit ALU. 27 | 28 | Attributes: 29 | a: The A input. 30 | b: The B input. 31 | f: The output. 32 | s: The function to perform. 33 | m: The mode: 0 = arithmetic, 1 = logical. 34 | n_carryin: Carry in, active low. 35 | n_carryout: Carry out, active low. 36 | x: X output (only meaningful in add/subtract mode). This output is NOT 37 | the same as a carry propagate, but it does work with the 74182 CLU 38 | when hooked up to its np input -- and then the group_np output of the 39 | CLU is actually the group X. 40 | y: Y output (only meaningful in add/subtract mode). This output is NOT 41 | the same as a carry generate, but it does work with the 74182 CLU 42 | when hooked up to its ng input -- and then the group_ng output of the 43 | CLU is actually the group Y. 44 | a_eq_b: Equality (only meaningful in subtract mode with n_carryin = 1). 45 | """ 46 | 47 | a: Signal 48 | b: Signal 49 | s: Signal 50 | f: Signal 51 | m: Signal 52 | n_carryin: Signal 53 | n_carryout: Signal 54 | x: Signal 55 | y: Signal 56 | a_eq_b: Signal 57 | 58 | def __init__(self): 59 | self.a = Signal(4) 60 | self.b = Signal(4) 61 | self.s = Signal(4) 62 | self.f = Signal(4) 63 | self.m = Signal() 64 | self.n_carryin = Signal() 65 | self.n_carryout = Signal() 66 | self.x = Signal() 67 | self.y = Signal() 68 | self.a_eq_b = Signal() # open-collector in the actual chip 69 | 70 | def ports(self): 71 | return [self.a, self.b, self.s, self.f, self.m, self.n_carryin, 72 | self.n_carryout, self.x, self.y, self.a_eq_b] 73 | 74 | def elaborate(self, _: Platform) -> Module: 75 | """Implements the logic of the 74181 chip. 76 | 77 | The logic is from the logic diagram in the data 78 | sheet, so that we're not fooled by any quirks. 79 | """ 80 | m = Module() 81 | a = self.a 82 | b = self.b 83 | s = self.s 84 | n_cin = self.n_carryin 85 | arith = ~self.m 86 | 87 | # Intermediate signals, nor of ands. 88 | x = [None] * 8 89 | for i in range(4): 90 | ab_a0 = a[i] 91 | ab_a1 = b[i] & s[0] 92 | ab_a2 = ~b[i] & s[1] 93 | x[2*i] = ~(ab_a0 | ab_a1 | ab_a2) 94 | 95 | ab_b0 = a[i] & ~b[i] & s[2] 96 | ab_b1 = a[i] & b[i] & s[3] 97 | x[2*i+1] = ~(ab_b0 | ab_b1) 98 | 99 | x0 = x[0] 100 | x1 = x[1] 101 | x2 = x[2] 102 | x3 = x[3] 103 | x4 = x[4] 104 | x5 = x[5] 105 | x6 = x[6] 106 | x7 = x[7] 107 | 108 | # Next set of intermediate signals. 109 | 110 | y0a = arith & n_cin 111 | y0 = ~y0a 112 | 113 | y1 = x0 ^ x1 114 | 115 | y2a = arith & x0 116 | y2b = arith & x1 & n_cin 117 | y2 = ~(y2a | y2b) 118 | 119 | y3 = x2 ^ x3 120 | 121 | y4a = arith & x2 122 | y4b = arith & x0 & x3 123 | y4c = arith & x1 & x3 & n_cin 124 | y4 = ~(y4a | y4b | y4c) 125 | 126 | y5 = x4 ^ x5 127 | 128 | y6a = arith & x4 129 | y6b = arith & x2 & x5 130 | y6c = arith & x0 & x3 & x5 131 | y6d = arith & x1 & x3 & x5 & n_cin 132 | y6 = ~(y6a | y6b | y6c | y6d) 133 | 134 | y7 = x6 ^ x7 135 | 136 | y8 = ~(x1 & x3 & x5 & x7) 137 | y9 = ~(x1 & x3 & x5 & x7 & n_cin) 138 | 139 | y10a = x0 & x3 & x5 & x7 140 | y10b = x2 & x5 & x7 141 | y10c = x4 & x7 142 | y10d = x6 143 | y10 = ~(y10a | y10b | y10c | y10d) 144 | 145 | m.d.comb += self.f[0].eq(y0 ^ y1) 146 | m.d.comb += self.f[1].eq(y2 ^ y3) 147 | m.d.comb += self.f[2].eq(y4 ^ y5) 148 | m.d.comb += self.f[3].eq(y6 ^ y7) 149 | # This only works if function is minus with n_cin = 1. 150 | # F = A - B - 1 = 1111 only if A = B. 151 | m.d.comb += self.a_eq_b.eq(self.f[0] 152 | & self.f[1] & self.f[2] & self.f[3]) 153 | m.d.comb += self.x.eq(y8) 154 | m.d.comb += self.y.eq(y10) 155 | m.d.comb += self.n_carryout.eq(~y9 | ~y10) 156 | 157 | return m 158 | 159 | @classmethod 160 | def formal_single(cls) -> Tuple[Module, List[Signal]]: 161 | """Formal verification for a single 74181.""" 162 | m = Module() 163 | m.submodules.alu = alu = IC_74181() 164 | 165 | cin = Signal() 166 | m.d.comb += cin.eq(~alu.n_carryin) 167 | cout = Signal() 168 | m.d.comb += cout.eq(~alu.n_carryout) 169 | 170 | with m.If(alu.m): 171 | with m.Switch(alu.s): 172 | with m.Case(0): 173 | m.d.comb += Assert(alu.f == ~alu.a) 174 | with m.Case(1): 175 | m.d.comb += Assert(alu.f == ~(alu.a | alu.b)) 176 | with m.Case(2): 177 | m.d.comb += Assert(alu.f == ~alu.a & alu.b) 178 | with m.Case(3): 179 | m.d.comb += Assert(alu.f == 0) 180 | with m.Case(4): 181 | m.d.comb += Assert(alu.f == ~(alu.a & alu.b)) 182 | with m.Case(5): 183 | m.d.comb += Assert(alu.f == ~alu.b) 184 | with m.Case(6): 185 | m.d.comb += Assert(alu.f == alu.a ^ alu.b) 186 | with m.Case(7): 187 | m.d.comb += Assert(alu.f == alu.a & ~alu.b) 188 | with m.Case(8): 189 | m.d.comb += Assert(alu.f == ~alu.a | alu.b) 190 | with m.Case(9): 191 | m.d.comb += Assert(alu.f == ~(alu.a ^ alu.b)) 192 | with m.Case(10): 193 | m.d.comb += Assert(alu.f == alu.b) 194 | with m.Case(11): 195 | m.d.comb += Assert(alu.f == alu.a & alu.b) 196 | with m.Case(12): 197 | m.d.comb += Assert(alu.f == 0xF) 198 | with m.Case(13): 199 | m.d.comb += Assert(alu.f == alu.a | ~alu.b) 200 | with m.Case(14): 201 | m.d.comb += Assert(alu.f == alu.a | alu.b) 202 | with m.Case(15): 203 | m.d.comb += Assert(alu.f == alu.a) 204 | with m.Else(): 205 | sm = Signal(5) 206 | inv = Signal() 207 | m.d.comb += inv.eq(0) 208 | 209 | with m.Switch(alu.s): 210 | with m.Case(0): 211 | m.d.comb += sm.eq(alu.a + cin) 212 | with m.Case(1): 213 | m.d.comb += sm.eq((alu.a | alu.b) + cin) 214 | with m.Case(2): 215 | m.d.comb += sm.eq((alu.a | ~alu.b) + cin) 216 | with m.Case(3): 217 | m.d.comb += sm.eq(0xF + cin) 218 | with m.Case(4): 219 | m.d.comb += sm.eq(alu.a + (alu.a & ~alu.b) + cin) 220 | with m.Case(5): 221 | m.d.comb += sm.eq((alu.a | alu.b) + (alu.a & ~alu.b) + cin) 222 | 223 | with m.Case(6): 224 | m.d.comb += sm.eq(alu.a - alu.b - ~cin) 225 | m.d.comb += inv.eq(1) 226 | 227 | # Check how equality, unsigned gt, and unsigned gte comparisons work. 228 | with m.If(~cin): 229 | m.d.comb += Assert(alu.a_eq_b == (alu.a == alu.b)) 230 | m.d.comb += Assert(cout == (alu.a > alu.b)) 231 | with m.Else(): 232 | m.d.comb += Assert(cout == (alu.a >= alu.b)) 233 | 234 | with m.Case(7): 235 | m.d.comb += sm.eq((alu.a & ~alu.b) - ~cin) 236 | m.d.comb += inv.eq(1) 237 | with m.Case(8): 238 | m.d.comb += sm.eq(alu.a + (alu.a & alu.b) + cin) 239 | 240 | with m.Case(9): 241 | m.d.comb += sm.eq(alu.a + alu.b + cin) 242 | 243 | with m.Case(10): 244 | m.d.comb += sm.eq((alu.a | ~alu.b) + (alu.a & alu.b) + cin) 245 | with m.Case(11): 246 | m.d.comb += sm.eq((alu.a & alu.b) - ~cin) 247 | m.d.comb += inv.eq(1) 248 | with m.Case(12): 249 | m.d.comb += sm.eq(alu.a + alu.a + cin) 250 | with m.Case(13): 251 | m.d.comb += sm.eq((alu.a | alu.b) + alu.a + cin) 252 | with m.Case(14): 253 | m.d.comb += sm.eq((alu.a | ~alu.b) + alu.a + cin) 254 | with m.Case(15): 255 | m.d.comb += sm.eq(alu.a - ~cin) 256 | m.d.comb += inv.eq(1) 257 | m.d.comb += Assert(alu.f == sm[:4]) 258 | m.d.comb += Assert(cout == inv ^ sm[4]) 259 | 260 | return m, alu.ports() 261 | 262 | @classmethod 263 | def formal_ripple(cls) -> Tuple[Module, List[Signal]]: 264 | """Formal verification for a bunch of ALUs in ripple-carry mode.""" 265 | m = Module() 266 | 267 | alus = [None] * 8 268 | m.submodules.alu0 = alus[0] = IC_74181() 269 | m.submodules.alu1 = alus[1] = IC_74181() 270 | m.submodules.alu2 = alus[2] = IC_74181() 271 | m.submodules.alu3 = alus[3] = IC_74181() 272 | m.submodules.alu4 = alus[4] = IC_74181() 273 | m.submodules.alu5 = alus[5] = IC_74181() 274 | m.submodules.alu6 = alus[6] = IC_74181() 275 | m.submodules.alu7 = alus[7] = IC_74181() 276 | 277 | a = Signal(32) 278 | b = Signal(32) 279 | f = Signal(32) 280 | cin = Signal() 281 | cout = Signal() 282 | s = Signal(4) 283 | mt = Signal() 284 | 285 | for x in range(8): 286 | m.d.comb += alus[x].a.eq(a[x*4:x*4+4]) 287 | m.d.comb += alus[x].b.eq(b[x*4:x*4+4]) 288 | m.d.comb += f[x*4:x*4+4].eq(alus[x].f) 289 | m.d.comb += alus[x].m.eq(mt) 290 | m.d.comb += alus[x].s.eq(s) 291 | for x in range(7): 292 | m.d.comb += alus[x+1].n_carryin.eq(alus[x].n_carryout) 293 | m.d.comb += alus[0].n_carryin.eq(~cin) 294 | m.d.comb += cout.eq(~alus[7].n_carryout) 295 | 296 | add_mode = (s == 9) & (mt == 0) 297 | sub_mode = (s == 6) & (mt == 0) 298 | m.d.comb += Assume(add_mode | sub_mode) 299 | 300 | y = Signal(33) 301 | with m.If(add_mode): 302 | m.d.comb += y.eq(a + b + cin) 303 | m.d.comb += Assert(f == y[:32]) 304 | m.d.comb += Assert(cout == y[32]) 305 | with m.Elif(sub_mode): 306 | m.d.comb += y.eq(a - b - ~cin) 307 | m.d.comb += Assert(f == y[:32]) 308 | m.d.comb += Assert(cout == ~y[32]) 309 | 310 | # Check how equality, unsigned gt, and unsigned gte comparisons work. 311 | with m.If(cin == 0): 312 | all_eq = Cat(*[i.a_eq_b for i in alus]).all() 313 | m.d.comb += Assert(all_eq == (a == b)) 314 | m.d.comb += Assert(cout == (a > b)) 315 | with m.Else(): 316 | m.d.comb += Assert(cout == (a >= b)) 317 | 318 | return m, [a, b, f, cin, cout, s, mt, y] 319 | 320 | @classmethod 321 | def formal(cls) -> Tuple[Module, List[Signal]]: 322 | """Formal verification for the 74181 chip.""" 323 | m = Module() 324 | 325 | m1, ports1 = cls.formal_single() 326 | m2, ports2 = cls.formal_ripple() 327 | 328 | m.submodules += [m1, m2] 329 | return m, ports1 + ports2 330 | 331 | @classmethod 332 | def toRTL(cls) -> Tuple[Module, List[Signal]]: 333 | m = Module() 334 | m.submodules.chip = chip = cls() 335 | 336 | return m, [chip.a, chip.b, chip.s, chip.f, chip.m, chip.n_carryin, chip.n_carryout, chip.x, chip.y, chip.a_eq_b] 337 | 338 | 339 | class IC_74182_active_low(Elaboratable): 340 | """Logic for the active low 74182 4-bit carry look-ahead unit. 341 | 342 | Attributes: 343 | ng: The four input generate signals, active low. 344 | np: The four input propagate signals, active low. 345 | carryin: The carry in input. 346 | carryout_x: The carry out for the first group. 347 | carryout_y: The carry out for the second group. 348 | carryout_z: The carry out for the third group. 349 | group_ng: The group generate output, active low. 350 | group_np: The group propagate output, active low. 351 | """ 352 | 353 | ng: Signal 354 | np: Signal 355 | carryin: Signal 356 | carryout_x: Signal 357 | carryout_y: Signal 358 | carryout_z: Signal 359 | group_ng: Signal 360 | group_np: Signal 361 | 362 | def __init__(self): 363 | self.ng = Signal(4) 364 | self.np = Signal(4) 365 | self.carryin = Signal() 366 | self.carryout_x = Signal() 367 | self.carryout_y = Signal() 368 | self.carryout_z = Signal() 369 | self.group_ng = Signal() 370 | self.group_np = Signal() 371 | 372 | def ports(self): 373 | return [self.ng, self.np, self.carryin, 374 | self.carryout_x, self.carryout_y, self.carryout_z, 375 | self.group_ng, self.group_np] 376 | 377 | def elaborate(self, _: Platform) -> Module: 378 | """Implements the logic of the 74182 chip. 379 | 380 | The logic is from the logic diagram in the data 381 | sheet, so that we're not fooled by any quirks. 382 | """ 383 | m = Module() 384 | 385 | np0 = self.np[0] 386 | np1 = self.np[1] 387 | np2 = self.np[2] 388 | np3 = self.np[3] 389 | ng0 = self.ng[0] 390 | ng1 = self.ng[1] 391 | ng2 = self.ng[2] 392 | ng3 = self.ng[3] 393 | n_cin = ~self.carryin 394 | 395 | m.d.comb += self.group_np.eq(np0 | np1 | np2 | np3) 396 | 397 | cx0 = np0 & ng0 398 | cx1 = n_cin & ng0 399 | m.d.comb += self.carryout_x.eq(~(cx0 | cx1)) 400 | 401 | cy0 = np1 & ng1 402 | cy1 = ng0 & ng1 & np0 403 | cy2 = n_cin & ng0 & ng1 404 | m.d.comb += self.carryout_y.eq(~(cy0 | cy1 | cy2)) 405 | 406 | cz0 = np2 & ng2 407 | cz1 = ng1 & ng2 & np1 408 | cz2 = ng0 & ng1 & ng2 & np0 409 | cz3 = n_cin & ng0 & ng1 & ng2 410 | m.d.comb += self.carryout_z.eq(~(cz0 | cz1 | cz2 | cz3)) 411 | 412 | gg0 = np3 & ng3 413 | gg1 = ng2 & ng3 & np2 414 | gg2 = ng1 & ng2 & ng3 & np1 415 | gg3 = ng0 & ng1 & ng2 & ng3 416 | m.d.comb += self.group_ng.eq(gg0 | gg1 | gg2 | gg3) 417 | 418 | return m 419 | 420 | @classmethod 421 | def formal(cls) -> Tuple[Module, List[Signal]]: 422 | """Formal verification for the active low 74182 chip.""" 423 | m = Module() 424 | m.submodules.clu = clu = IC_74182_active_low() 425 | 426 | # Verify the truth tables in the datasheet 427 | 428 | with m.If(clu.np.matches("0000")): 429 | m.d.comb += Assert(clu.group_np == 0) 430 | with m.Else(): 431 | m.d.comb += Assert(clu.group_np == 1) 432 | 433 | with m.If(clu.ng.matches("0---") & clu.np.matches("----")): 434 | m.d.comb += Assert(clu.group_ng == 0) 435 | with m.Elif(clu.ng.matches("-0--") & clu.np.matches("0---")): 436 | m.d.comb += Assert(clu.group_ng == 0) 437 | with m.Elif(clu.ng.matches("--0-") & clu.np.matches("00--")): 438 | m.d.comb += Assert(clu.group_ng == 0) 439 | with m.Elif(clu.ng.matches("---0") & clu.np.matches("000-")): 440 | m.d.comb += Assert(clu.group_ng == 0) 441 | with m.Else(): 442 | m.d.comb += Assert(clu.group_ng == 1) 443 | 444 | with m.If(clu.ng[0] == 0): 445 | m.d.comb += Assert(clu.carryout_x == 1) 446 | with m.Elif((clu.np[0] == 0) & (clu.carryin == 1)): 447 | m.d.comb += Assert(clu.carryout_x == 1) 448 | with m.Else(): 449 | m.d.comb += Assert(clu.carryout_x == 0) 450 | 451 | with m.If(clu.ng.matches("--0-") & clu.np.matches("----")): 452 | m.d.comb += Assert(clu.carryout_y == 1) 453 | with m.Elif(clu.ng.matches("---0") & clu.np.matches("--0-")): 454 | m.d.comb += Assert(clu.carryout_y == 1) 455 | with m.Elif(clu.ng.matches("----") & clu.np.matches("--00") & (clu.carryin == 1)): 456 | m.d.comb += Assert(clu.carryout_y == 1) 457 | with m.Else(): 458 | m.d.comb += Assert(clu.carryout_y == 0) 459 | 460 | with m.If(clu.ng.matches("-0--") & clu.np.matches("----")): 461 | m.d.comb += Assert(clu.carryout_z == 1) 462 | with m.Elif(clu.ng.matches("--0-") & clu.np.matches("-0--")): 463 | m.d.comb += Assert(clu.carryout_z == 1) 464 | with m.Elif(clu.ng.matches("---0") & clu.np.matches("-00-")): 465 | m.d.comb += Assert(clu.carryout_z == 1) 466 | with m.Elif(clu.ng.matches("----") & clu.np.matches("-000") & (clu.carryin == 1)): 467 | m.d.comb += Assert(clu.carryout_z == 1) 468 | with m.Else(): 469 | m.d.comb += Assert(clu.carryout_z == 0) 470 | 471 | return m, clu.ports() 472 | 473 | 474 | class IC_74182_active_high(Elaboratable): 475 | """Logic for the active high 74182 4-bit carry look-ahead unit. 476 | 477 | This is just a renaming of the signals to correspond to the 478 | active-high set. 479 | 480 | Attributes: 481 | y: The four input generate signals, active low. 482 | x: The four input propagate signals, active low. 483 | n_carryin: The carry in input. 484 | n_carryout_x: The carry out for the first group. 485 | n_carryout_y: The carry out for the second group. 486 | n_carryout_z: The carry out for the third group. 487 | group_y: The group generate output, active low. 488 | group_x: The group propagate output, active low. 489 | """ 490 | 491 | y: Signal 492 | x: Signal 493 | n_carryin: Signal 494 | n_carryout_x: Signal 495 | n_carryout_y: Signal 496 | n_carryout_z: Signal 497 | group_y: Signal 498 | group_x: Signal 499 | 500 | def __init__(self): 501 | self.y = Signal(4) 502 | self.x = Signal(4) 503 | self.n_carryin = Signal() 504 | self.n_carryout_x = Signal() 505 | self.n_carryout_y = Signal() 506 | self.n_carryout_z = Signal() 507 | self.group_y = Signal() 508 | self.group_x = Signal() 509 | 510 | def ports(self): 511 | return [self.y, self.x, self.n_carryin, 512 | self.n_carryout_x, self.n_carryout_y, self.n_carryout_z, 513 | self.group_y, self.group_x] 514 | 515 | def elaborate(self, _: Platform) -> Module: 516 | """Implements the logic of the active high 74182 chip.""" 517 | m = Module() 518 | m.submodules.low = low = IC_74182_active_low() 519 | 520 | m.d.comb += [ 521 | low.ng.eq(self.y), 522 | low.np.eq(self.x), 523 | low.carryin.eq(self.n_carryin), 524 | self.n_carryout_x.eq(low.carryout_x), 525 | self.n_carryout_z.eq(low.carryout_z), 526 | self.n_carryout_z.eq(low.carryout_z), 527 | self.group_y.eq(low.group_ng), 528 | self.group_x.eq(low.group_np), 529 | ] 530 | 531 | return m 532 | 533 | @classmethod 534 | def formal(cls) -> Tuple[Module, List[Signal]]: 535 | """Formal verification for the active high 74182 chip. 536 | 537 | Used with an active high 74181. 538 | """ 539 | m = Module() 540 | m.submodules.clu = clu = IC_74182_active_high() 541 | m.submodules.alu = alu = IC_74181() 542 | 543 | add_mode = (alu.s == 9) & (alu.m == 0) 544 | m.d.comb += Assume(add_mode) 545 | 546 | m.d.comb += [ 547 | clu.x[0].eq(alu.x), 548 | clu.y[0].eq(alu.y), 549 | clu.x[1:].eq(0), 550 | clu.y[1:].eq(0), 551 | clu.n_carryin.eq(alu.n_carryin), 552 | ] 553 | 554 | m.d.comb += Assert(clu.n_carryout_x == alu.n_carryout) 555 | 556 | return m, clu.ports() + alu.ports() 557 | 558 | 559 | class IC_74181_and_or_layer1(Elaboratable): 560 | """Logic for the first and-or layer of the 74181 4-bit ALU. 561 | """ 562 | 563 | def __init__(self): 564 | self.a = Signal(4) 565 | self.b = Signal(4) 566 | self.s = Signal(4) 567 | self.x = Signal(8) 568 | self.m = Signal() 569 | self.n_carryin = Signal() 570 | 571 | def ports(self): 572 | return [self.a, self.b, self.s, self.x, self.m, self.n_carryin] 573 | 574 | def elaborate(self, _: Platform) -> Module: 575 | """Implements the logic of the 74181 chip. 576 | 577 | The logic is from the logic diagram in the data 578 | sheet, so that we're not fooled by any quirks. 579 | """ 580 | m = Module() 581 | a = self.a 582 | b = self.b 583 | s = self.s 584 | x = self.x 585 | 586 | for i in range(4): 587 | ab_a0 = a[i] 588 | ab_a1 = b[i] & s[0] 589 | ab_a2 = ~b[i] & s[1] 590 | m.d.comb += x[2*i].eq(~(ab_a0 | ab_a1 | ab_a2)) 591 | 592 | ab_b0 = a[i] & ~b[i] & s[2] 593 | ab_b1 = a[i] & b[i] & s[3] 594 | m.d.comb += x[2*i+1].eq(~(ab_b0 | ab_b1)) 595 | 596 | return m 597 | 598 | @classmethod 599 | def toRTL(cls) -> Tuple[Module, List[Signal]]: 600 | m = Module() 601 | m.submodules.chip = chip = cls() 602 | 603 | return m, chip.ports() 604 | 605 | 606 | class IC_74181_and_or_layer2(Elaboratable): 607 | """Logic for the second and-or layer of the 74181 4-bit ALU. 608 | """ 609 | 610 | def __init__(self): 611 | self.x = Signal(8) 612 | self.m = Signal() 613 | self.n_carryin = Signal() 614 | self.n = Signal(7) 615 | 616 | def ports(self): 617 | return [self.x, self.m, self.n_carryin, self.n] 618 | 619 | def elaborate(self, _: Platform) -> Module: 620 | """Implements the logic of the 74181 chip. 621 | 622 | The logic is from the logic diagram in the data 623 | sheet, so that we're not fooled by any quirks. 624 | """ 625 | m = Module() 626 | n_cin = self.n_carryin 627 | arith = ~self.m 628 | x = self.x 629 | n = self.n 630 | 631 | x0 = x[0] 632 | x1 = x[1] 633 | x2 = x[2] 634 | x3 = x[3] 635 | x4 = x[4] 636 | x5 = x[5] 637 | x6 = x[6] 638 | x7 = x[7] 639 | 640 | # Next set of intermediate signals. 641 | 642 | m.d.comb += n[0].eq(~(arith & n_cin)) 643 | 644 | y2a = arith & x0 645 | y2b = arith & x1 & n_cin 646 | m.d.comb += n[1].eq(~(y2a | y2b)) 647 | 648 | y4a = arith & x2 649 | y4b = arith & x0 & x3 650 | y4c = arith & x1 & x3 & n_cin 651 | m.d.comb += n[2].eq(~(y4a | y4b | y4c)) 652 | 653 | y6a = arith & x4 654 | y6b = arith & x2 & x5 655 | y6c = arith & x0 & x3 & x5 656 | y6d = arith & x1 & x3 & x5 & n_cin 657 | m.d.comb += n[3].eq(~(y6a | y6b | y6c | y6d)) 658 | 659 | m.d.comb += n[4].eq(~(x1 & x3 & x5 & x7 & n_cin)) 660 | 661 | y10a = x0 & x3 & x5 & x7 662 | y10b = x2 & x5 & x7 663 | y10c = x4 & x7 664 | y10d = x6 665 | m.d.comb += n[5].eq(~(y10a | y10b | y10c | y10d)) 666 | 667 | m.d.comb += n[6].eq(~(x1 & x3 & x5 & x7)) 668 | 669 | return m 670 | 671 | @classmethod 672 | def toRTL(cls) -> Tuple[Module, List[Signal]]: 673 | m = Module() 674 | m.submodules.chip = chip = cls() 675 | 676 | return m, chip.ports() 677 | 678 | 679 | class IC_74181_and_or_layer3(Elaboratable): 680 | """Logic for the third and-or layer of the 74181 4-bit ALU. 681 | """ 682 | 683 | def __init__(self): 684 | self.f = Signal(4) 685 | self.n = Signal(7) 686 | self.n_carryout = Signal() 687 | self.a_eq_b = Signal() # open-collector in the actual chip 688 | 689 | def ports(self): 690 | return [self.f, self.n, self.n_carryout, self.a_eq_b] 691 | 692 | def elaborate(self, _: Platform) -> Module: 693 | """Implements the logic of the 74181 chip. 694 | 695 | The logic is from the logic diagram in the data 696 | sheet, so that we're not fooled by any quirks. 697 | """ 698 | m = Module() 699 | f = self.f 700 | n = self.n 701 | n_cout = self.n_carryout 702 | a_eq_b = self.a_eq_b 703 | 704 | # This only works if function is minus with n_cin = 1. 705 | # F = A - B - 1 = 1111 only if A = B. 706 | m.d.comb += a_eq_b.eq(f[0] & f[1] & f[2] & f[3]) 707 | m.d.comb += n_cout.eq(~n[4] | ~n[5]) 708 | 709 | return m 710 | 711 | @classmethod 712 | def toRTL(cls) -> Tuple[Module, List[Signal]]: 713 | m = Module() 714 | m.submodules.chip = chip = cls() 715 | 716 | return m, chip.ports() 717 | 718 | 719 | class IC_74688_and_or_layer1(Elaboratable): 720 | def __init__(self): 721 | self.x = Signal(8) 722 | self.p_neq_q = Signal() 723 | 724 | def ports(self): 725 | return [self.x, self.p_neq_q] 726 | 727 | def elaborate(self, _: Platform) -> Module: 728 | m = Module() 729 | x = self.x 730 | 731 | m.d.comb += self.p_neq_q.eq(x[0] | x[1] | 732 | x[2] | x[3] | x[4] | x[5] | x[6] | x[7]) 733 | 734 | return m 735 | 736 | @ classmethod 737 | def toRTL(cls) -> Tuple[Module, List[Signal]]: 738 | m = Module() 739 | m.submodules.chip = chip = cls() 740 | 741 | return m, chip.ports() 742 | 743 | 744 | if __name__ == "__main__": 745 | main(IC_74688_and_or_layer1) 746 | -------------------------------------------------------------------------------- /sequencer_rom.py: -------------------------------------------------------------------------------- 1 | # Disable pylint's "your name is too short" warning. 2 | # pylint: disable=C0103 3 | # Disable protected access warnings 4 | # pylint: disable=W0212 5 | from nmigen import Signal, Module, Elaboratable 6 | from nmigen.build import Platform 7 | 8 | from consts import AluOp, AluFunc, BranchCond, MemAccessWidth 9 | from consts import OpcodeFormat, SystemFunc, TrapCauseSelect 10 | from consts import InstrReg, OpcodeSelect 11 | from consts import NextPC, SeqMuxSelect, ConstSelect 12 | 13 | 14 | class SequencerROM(Elaboratable): 15 | """ROM for the sequencer card state machine.""" 16 | 17 | def __init__(self): 18 | # Control line 19 | self.enable_sequencer_rom = Signal() 20 | 21 | # Inputs: 9 + 11 decoder 22 | # Since this is implemented by a ROM, the address lines 23 | # must be stable in order for the outputs to start becoming 24 | # stable. This means that if any input address depends on 25 | # any output data combinatorically, there's a danger of 26 | # going unstable. Therefore, all address lines must be 27 | # registered, or come combinatorically from registered data. 28 | 29 | self.memaddr_2_lsb = Signal(2) 30 | self.branch_cond = Signal() 31 | self._instr_phase = Signal(2) 32 | # Only used on instruction phase 1 in BRANCH. 33 | self.data_z_in_2_lsb0 = Signal() 34 | self.imm0 = Signal() 35 | self.rd0 = Signal() 36 | self.rs1_0 = Signal() 37 | 38 | # Instruction decoding 39 | self.opcode_select = Signal(OpcodeSelect) # 4 bits 40 | self._funct3 = Signal(3) 41 | self._alu_func = Signal(4) 42 | 43 | ############## 44 | # Outputs (66 bits total) 45 | ############## 46 | 47 | # Raised on the last phase of an instruction. 48 | self.set_instr_complete = Signal() 49 | 50 | # Raised when the exception card should store trap data. 51 | self.save_trap_csrs = Signal() 52 | 53 | # CSR lines 54 | self.csr_to_x = Signal() 55 | self.z_to_csr = Signal() 56 | 57 | # Memory 58 | self.mem_rd = Signal(reset=1) 59 | self.mem_wr = Signal() 60 | # Bytes in memory word to write 61 | self.mem_wr_mask = Signal(4) 62 | 63 | self._next_instr_phase = Signal(2) 64 | 65 | self._x_reg_select = Signal(InstrReg) # 2 bits 66 | self._y_reg_select = Signal(InstrReg) # 2 bits 67 | self._z_reg_select = Signal(InstrReg) # 2 bits 68 | 69 | # -> X 70 | self.x_mux_select = Signal(SeqMuxSelect) 71 | self.reg_to_x = Signal() 72 | 73 | # -> Y 74 | self.y_mux_select = Signal(SeqMuxSelect) 75 | self.reg_to_y = Signal() 76 | 77 | # -> Z 78 | self.z_mux_select = Signal(SeqMuxSelect) 79 | self.alu_op_to_z = Signal(AluOp) # 4 bits 80 | 81 | # -> PC 82 | self.pc_mux_select = Signal(SeqMuxSelect) 83 | 84 | # -> tmp 85 | self.tmp_mux_select = Signal(SeqMuxSelect) 86 | 87 | # -> csr_num 88 | self._funct12_to_csr_num = Signal() 89 | self._mepc_num_to_csr_num = Signal() 90 | self._mcause_to_csr_num = Signal() 91 | 92 | # -> memaddr 93 | self.memaddr_mux_select = Signal(SeqMuxSelect) 94 | 95 | # -> memdata 96 | self.memdata_wr_mux_select = Signal(SeqMuxSelect) 97 | 98 | self._const = Signal(ConstSelect) # select: 4 bits 99 | 100 | self.enter_trap = Signal() 101 | self.exit_trap = Signal() 102 | 103 | # Signals for next registers 104 | self.load_trap = Signal() 105 | self.next_trap = Signal() 106 | self.load_exception = Signal() 107 | self.next_exception = Signal() 108 | self.next_fatal = Signal() 109 | 110 | def elaborate(self, _: Platform) -> Module: 111 | """Implements the logic of the sequencer card.""" 112 | m = Module() 113 | 114 | # Defaults 115 | m.d.comb += [ 116 | self._next_instr_phase.eq(0), 117 | self.reg_to_x.eq(0), 118 | self.reg_to_y.eq(0), 119 | self.alu_op_to_z.eq(AluOp.NONE), 120 | self.mem_rd.eq(0), 121 | self.mem_wr.eq(0), 122 | self.mem_wr_mask.eq(0), 123 | self.csr_to_x.eq(0), 124 | self.z_to_csr.eq(0), 125 | self._funct12_to_csr_num.eq(0), 126 | self._mepc_num_to_csr_num.eq(0), 127 | self._mcause_to_csr_num.eq(0), 128 | self._x_reg_select.eq(0), 129 | self._y_reg_select.eq(0), 130 | self._z_reg_select.eq(0), 131 | self.enter_trap.eq(0), 132 | self.exit_trap.eq(0), 133 | self.save_trap_csrs.eq(0), 134 | self.pc_mux_select.eq(SeqMuxSelect.PC), 135 | self.memaddr_mux_select.eq(SeqMuxSelect.MEMADDR), 136 | self.memdata_wr_mux_select.eq(SeqMuxSelect.MEMDATA_WR), 137 | self.tmp_mux_select.eq(SeqMuxSelect.TMP), 138 | self.x_mux_select.eq(SeqMuxSelect.X), 139 | self.y_mux_select.eq(SeqMuxSelect.Y), 140 | self.z_mux_select.eq(SeqMuxSelect.Z), 141 | self._const.eq(0), 142 | ] 143 | 144 | m.d.comb += [ 145 | self.load_trap.eq(0), 146 | self.next_trap.eq(0), 147 | self.load_exception.eq(0), 148 | self.next_exception.eq(0), 149 | self.next_fatal.eq(0), 150 | ] 151 | 152 | with m.If(self.enable_sequencer_rom): 153 | 154 | # Output control signals 155 | with m.Switch(self.opcode_select): 156 | with m.Case(OpcodeSelect.LUI): 157 | self.handle_lui(m) 158 | 159 | with m.Case(OpcodeSelect.AUIPC): 160 | self.handle_auipc(m) 161 | 162 | with m.Case(OpcodeSelect.OP_IMM): 163 | self.handle_op_imm(m) 164 | 165 | with m.Case(OpcodeSelect.OP): 166 | self.handle_op(m) 167 | 168 | with m.Case(OpcodeSelect.JAL): 169 | self.handle_jal(m) 170 | 171 | with m.Case(OpcodeSelect.JALR): 172 | self.handle_jalr(m) 173 | 174 | with m.Case(OpcodeSelect.BRANCH): 175 | self.handle_branch(m) 176 | 177 | with m.Case(OpcodeSelect.LOAD): 178 | self.handle_load(m) 179 | 180 | with m.Case(OpcodeSelect.STORE): 181 | self.handle_store(m) 182 | 183 | with m.Case(OpcodeSelect.CSRS): 184 | self.handle_csrs(m) 185 | 186 | with m.Case(OpcodeSelect.MRET): 187 | self.handle_MRET(m) 188 | 189 | with m.Case(OpcodeSelect.ECALL): 190 | self.handle_ECALL(m) 191 | 192 | with m.Case(OpcodeSelect.EBREAK): 193 | self.handle_EBREAK(m) 194 | 195 | with m.Default(): 196 | self.handle_illegal_instr(m) 197 | 198 | return m 199 | 200 | def next_instr(self, m: Module, next_pc: NextPC = NextPC.PC_PLUS_4): 201 | """Sets signals to advance to the next instruction. 202 | 203 | next_pc is the signal to load the PC and MEMADDR registers with 204 | at the end of the instruction cycle. 205 | """ 206 | m.d.comb += self.set_instr_complete.eq(1) 207 | if next_pc == NextPC.PC_PLUS_4: 208 | m.d.comb += self.pc_mux_select.eq(SeqMuxSelect.PC_PLUS_4) 209 | m.d.comb += self.memaddr_mux_select.eq(SeqMuxSelect.PC_PLUS_4) 210 | elif next_pc == NextPC.MEMADDR: 211 | m.d.comb += self.pc_mux_select.eq(SeqMuxSelect.MEMADDR) 212 | elif next_pc == NextPC.MEMADDR_NO_LSB: 213 | m.d.comb += self.pc_mux_select.eq(SeqMuxSelect.MEMADDR_LSB_MASKED) 214 | elif next_pc == NextPC.Z: 215 | m.d.comb += self.pc_mux_select.eq(SeqMuxSelect.Z) 216 | m.d.comb += self.memaddr_mux_select.eq(SeqMuxSelect.Z) 217 | elif next_pc == NextPC.X: 218 | m.d.comb += self.pc_mux_select.eq(SeqMuxSelect.X) 219 | m.d.comb += self.memaddr_mux_select.eq(SeqMuxSelect.X) 220 | 221 | def set_exception(self, m: Module, exc: ConstSelect, mtval: SeqMuxSelect, fatal: bool = True): 222 | m.d.comb += self.load_exception.eq(1) 223 | m.d.comb += self.next_exception.eq(1) 224 | m.d.comb += self.next_fatal.eq(1 if fatal else 0) 225 | 226 | m.d.comb += self._const.eq(exc) 227 | m.d.comb += self.x_mux_select.eq(SeqMuxSelect.CONST) 228 | m.d.comb += self.z_mux_select.eq(mtval) 229 | 230 | if fatal: 231 | m.d.comb += self.y_mux_select.eq(SeqMuxSelect.PC) 232 | else: 233 | m.d.comb += self.y_mux_select.eq(SeqMuxSelect.PC_PLUS_4) 234 | 235 | # X -> MCAUSE, Y -> MEPC, Z -> MTVAL 236 | m.d.comb += self.save_trap_csrs.eq(1) 237 | m.d.comb += self.load_trap.eq(1) 238 | m.d.comb += self.next_trap.eq(1) 239 | m.d.comb += self._next_instr_phase.eq(0) 240 | 241 | def handle_illegal_instr(self, m: Module): 242 | self.set_exception(m, ConstSelect.EXC_ILLEGAL_INSTR, 243 | mtval=SeqMuxSelect.INSTR) 244 | 245 | def handle_lui(self, m: Module): 246 | """Adds the LUI logic to the given module. 247 | 248 | rd <- r0 + imm 249 | PC <- PC + 4 250 | 251 | r0 -> X 252 | imm -> Y 253 | ALU ADD -> Z 254 | Z -> rd 255 | PC + 4 -> PC 256 | PC + 4 -> memaddr 257 | """ 258 | m.d.comb += [ 259 | self.reg_to_x.eq(1), 260 | self._x_reg_select.eq(InstrReg.ZERO), 261 | self.y_mux_select.eq(SeqMuxSelect.IMM), 262 | self.alu_op_to_z.eq(AluOp.ADD), 263 | self._z_reg_select.eq(InstrReg.RD), 264 | ] 265 | self.next_instr(m) 266 | 267 | def handle_auipc(self, m: Module): 268 | """Adds the AUIPC logic to the given module. 269 | 270 | rd <- PC + imm 271 | PC <- PC + 4 272 | 273 | PC -> X 274 | imm -> Y 275 | ALU ADD -> Z 276 | Z -> rd 277 | PC + 4 -> PC 278 | PC + 4 -> memaddr 279 | """ 280 | m.d.comb += [ 281 | self.x_mux_select.eq(SeqMuxSelect.PC), 282 | self.y_mux_select.eq(SeqMuxSelect.IMM), 283 | self.alu_op_to_z.eq(AluOp.ADD), 284 | self._z_reg_select.eq(InstrReg.RD), 285 | ] 286 | self.next_instr(m) 287 | 288 | def handle_op_imm(self, m: Module): 289 | """Adds the OP_IMM logic to the given module. 290 | 291 | rd <- rs1 op imm 292 | PC <- PC + 4 293 | 294 | rs1 -> X 295 | imm -> Y 296 | ALU op -> Z 297 | Z -> rd 298 | PC + 4 -> PC 299 | PC + 4 -> memaddr 300 | """ 301 | with m.If(~self._alu_func.matches(AluFunc.ADD, AluFunc.SUB, AluFunc.SLL, AluFunc.SLT, 302 | AluFunc.SLTU, AluFunc.XOR, AluFunc.SRL, AluFunc.SRA, AluFunc.OR, AluFunc.AND)): 303 | self.handle_illegal_instr(m) 304 | with m.Else(): 305 | m.d.comb += [ 306 | self.reg_to_x.eq(1), 307 | self._x_reg_select.eq(InstrReg.RS1), 308 | self.y_mux_select.eq(SeqMuxSelect.IMM), 309 | self._z_reg_select.eq(InstrReg.RD), 310 | ] 311 | with m.Switch(self._alu_func): 312 | with m.Case(AluFunc.ADD): 313 | m.d.comb += self.alu_op_to_z.eq(AluOp.ADD) 314 | with m.Case(AluFunc.SUB): 315 | m.d.comb += self.alu_op_to_z.eq(AluOp.SUB) 316 | with m.Case(AluFunc.SLL): 317 | m.d.comb += self.alu_op_to_z.eq(AluOp.SLL) 318 | with m.Case(AluFunc.SLT): 319 | m.d.comb += self.alu_op_to_z.eq(AluOp.SLT) 320 | with m.Case(AluFunc.SLTU): 321 | m.d.comb += self.alu_op_to_z.eq(AluOp.SLTU) 322 | with m.Case(AluFunc.XOR): 323 | m.d.comb += self.alu_op_to_z.eq(AluOp.XOR) 324 | with m.Case(AluFunc.SRL): 325 | m.d.comb += self.alu_op_to_z.eq(AluOp.SRL) 326 | with m.Case(AluFunc.SRA): 327 | m.d.comb += self.alu_op_to_z.eq(AluOp.SRA) 328 | with m.Case(AluFunc.OR): 329 | m.d.comb += self.alu_op_to_z.eq(AluOp.OR) 330 | with m.Case(AluFunc.AND): 331 | m.d.comb += self.alu_op_to_z.eq(AluOp.AND) 332 | self.next_instr(m) 333 | 334 | def handle_op(self, m: Module): 335 | """Adds the OP logic to the given module. 336 | 337 | rd <- rs1 op rs2 338 | PC <- PC + 4 339 | 340 | rs1 -> X 341 | rs2 -> Y 342 | ALU op -> Z 343 | Z -> rd 344 | PC + 4 -> PC 345 | PC + 4 -> memaddr 346 | """ 347 | with m.If(~self._alu_func.matches(AluFunc.ADD, AluFunc.SUB, AluFunc.SLL, AluFunc.SLT, 348 | AluFunc.SLTU, AluFunc.XOR, AluFunc.SRL, AluFunc.SRA, AluFunc.OR, AluFunc.AND)): 349 | self.handle_illegal_instr(m) 350 | with m.Else(): 351 | m.d.comb += [ 352 | self.reg_to_x.eq(1), 353 | self._x_reg_select.eq(InstrReg.RS1), 354 | self.reg_to_y.eq(1), 355 | self._y_reg_select.eq(InstrReg.RS2), 356 | self._z_reg_select.eq(InstrReg.RD), 357 | ] 358 | with m.Switch(self._alu_func): 359 | with m.Case(AluFunc.ADD): 360 | m.d.comb += self.alu_op_to_z.eq(AluOp.ADD) 361 | with m.Case(AluFunc.SUB): 362 | m.d.comb += self.alu_op_to_z.eq(AluOp.SUB) 363 | with m.Case(AluFunc.SLL): 364 | m.d.comb += self.alu_op_to_z.eq(AluOp.SLL) 365 | with m.Case(AluFunc.SLT): 366 | m.d.comb += self.alu_op_to_z.eq(AluOp.SLT) 367 | with m.Case(AluFunc.SLTU): 368 | m.d.comb += self.alu_op_to_z.eq(AluOp.SLTU) 369 | with m.Case(AluFunc.XOR): 370 | m.d.comb += self.alu_op_to_z.eq(AluOp.XOR) 371 | with m.Case(AluFunc.SRL): 372 | m.d.comb += self.alu_op_to_z.eq(AluOp.SRL) 373 | with m.Case(AluFunc.SRA): 374 | m.d.comb += self.alu_op_to_z.eq(AluOp.SRA) 375 | with m.Case(AluFunc.OR): 376 | m.d.comb += self.alu_op_to_z.eq(AluOp.OR) 377 | with m.Case(AluFunc.AND): 378 | m.d.comb += self.alu_op_to_z.eq(AluOp.AND) 379 | self.next_instr(m) 380 | 381 | def handle_jal(self, m: Module): 382 | """Adds the JAL logic to the given module. 383 | 384 | rd <- PC + 4, PC <- PC + imm 385 | 386 | PC -> X 387 | imm -> Y 388 | ALU ADD -> Z 389 | Z -> memaddr 390 | --------------------- 391 | PC + 4 -> Z 392 | Z -> rd 393 | memaddr -> PC # This will zero the least significant bit 394 | 395 | Note that because the immediate value for JAL has its least 396 | significant bit set to zero by definition, and the PC is also 397 | assumed to be aligned, there is no loss in generality to clear 398 | the least significant bit when transferring memaddr to PC. 399 | """ 400 | with m.If(self._instr_phase == 0): 401 | m.d.comb += [ 402 | self.x_mux_select.eq(SeqMuxSelect.PC), 403 | self.y_mux_select.eq(SeqMuxSelect.IMM), 404 | self.alu_op_to_z.eq(AluOp.ADD), 405 | self.memaddr_mux_select.eq(SeqMuxSelect.Z), 406 | self._next_instr_phase.eq(1), 407 | ] 408 | with m.Else(): 409 | with m.If(self.memaddr_2_lsb[1] != 0): 410 | self.set_exception( 411 | m, ConstSelect.EXC_INSTR_ADDR_MISALIGN, mtval=SeqMuxSelect.MEMADDR) 412 | with m.Else(): 413 | m.d.comb += [ 414 | self.z_mux_select.eq(SeqMuxSelect.PC_PLUS_4), 415 | self._z_reg_select.eq(InstrReg.RD), 416 | ] 417 | self.next_instr(m, NextPC.MEMADDR_NO_LSB) 418 | 419 | def handle_jalr(self, m: Module): 420 | """Adds the JALR logic to the given module. 421 | 422 | rd <- PC + 4, PC <- (rs1 + imm) & 0xFFFFFFFE 423 | 424 | rs1 -> X 425 | imm -> Y 426 | ALU ADD -> Z 427 | Z -> memaddr 428 | --------------------- 429 | PC + 4 -> Z 430 | Z -> rd 431 | memaddr -> PC # This will zero the least significant bit 432 | """ 433 | with m.If(self._instr_phase == 0): 434 | m.d.comb += [ 435 | self.reg_to_x.eq(1), 436 | self._x_reg_select.eq(InstrReg.RS1), 437 | self.y_mux_select.eq(SeqMuxSelect.IMM), 438 | self.alu_op_to_z.eq(AluOp.ADD), 439 | self.memaddr_mux_select.eq(SeqMuxSelect.Z), 440 | self._next_instr_phase.eq(1), 441 | ] 442 | with m.Else(): 443 | with m.If(self.memaddr_2_lsb[1] != 0): 444 | self.set_exception( 445 | m, ConstSelect.EXC_INSTR_ADDR_MISALIGN, mtval=SeqMuxSelect.MEMADDR_LSB_MASKED) 446 | with m.Else(): 447 | m.d.comb += [ 448 | self.z_mux_select.eq(SeqMuxSelect.PC_PLUS_4), 449 | self._z_reg_select.eq(InstrReg.RD), 450 | ] 451 | self.next_instr(m, NextPC.MEMADDR_NO_LSB) 452 | 453 | def handle_branch(self, m: Module): 454 | """Adds the BRANCH logic to the given module. 455 | 456 | cond <- rs1 - rs2 < 0, rs1 - rs2 == 0 457 | if f(cond): 458 | PC <- PC + imm 459 | else: 460 | PC <- PC + 4 461 | 462 | rs1 -> X 463 | rs2 -> Y 464 | ALU SUB -> Z, cond 465 | --------------------- cond == 1 466 | PC -> X 467 | imm/4 -> Y (imm for cond == 1, 4 otherwise) 468 | ALU ADD -> Z 469 | Z -> PC 470 | Z -> memaddr 471 | --------------------- cond == 0 472 | PC + 4 -> PC 473 | PC + 4 -> memaddr 474 | """ 475 | with m.If(self._instr_phase == 0): 476 | m.d.comb += [ 477 | self.reg_to_x.eq(1), 478 | self._x_reg_select.eq(InstrReg.RS1), 479 | self.reg_to_y.eq(1), 480 | self._y_reg_select.eq(InstrReg.RS2), 481 | self.alu_op_to_z.eq(AluOp.SUB), 482 | self._next_instr_phase.eq(1), 483 | ] 484 | with m.Elif(self._instr_phase == 1): 485 | with m.If(~self._funct3.matches(BranchCond.EQ, BranchCond.NE, 486 | BranchCond.LT, BranchCond.GE, 487 | BranchCond.LTU, BranchCond.GEU)): 488 | self.handle_illegal_instr(m) 489 | 490 | with m.Else(): 491 | with m.If(self.branch_cond): 492 | m.d.comb += self.y_mux_select.eq(SeqMuxSelect.IMM) 493 | with m.Else(): 494 | m.d.comb += self._const.eq(ConstSelect.SHAMT_4) 495 | m.d.comb += self.y_mux_select.eq(SeqMuxSelect.CONST) 496 | 497 | m.d.comb += [ 498 | self.x_mux_select.eq(SeqMuxSelect.PC), 499 | self.alu_op_to_z.eq(AluOp.ADD), 500 | ] 501 | 502 | with m.If(self.data_z_in_2_lsb0): 503 | self.next_instr(m, NextPC.Z) 504 | 505 | with m.Else(): 506 | m.d.comb += self._next_instr_phase.eq(2) 507 | m.d.comb += self.tmp_mux_select.eq(SeqMuxSelect.Z) 508 | 509 | with m.Else(): 510 | self.set_exception( 511 | m, ConstSelect.EXC_INSTR_ADDR_MISALIGN, mtval=SeqMuxSelect.TMP) 512 | 513 | def handle_load(self, m: Module): 514 | """Adds the LOAD logic to the given module. 515 | 516 | Note that byte loads are byte-aligned, half-word loads 517 | are 16-bit aligned, and word loads are 32-bit aligned. 518 | Attempting to load unaligned will lead to undefined 519 | behavior. 520 | 521 | Operation is to load 32 bits from a 32-bit aligned 522 | address, and then perform at most two shifts to get 523 | the desired behavior: a shift left to get the most 524 | significant byte into the leftmost position, then a 525 | shift right to zero or sign extend the value. 526 | 527 | For example, for loading a half-word starting at 528 | address A where A%4=0, we first load the full 32 529 | bits at that address, resulting in XYHL, where X and 530 | Y are unwanted and H and L are the half-word we want 531 | to load. Then we shift left by 16: HL00. And finally 532 | we shift right by 16, either signed or unsigned 533 | depending on whether we are doing an LH or an LHU: 534 | ssHL / 00HL. 535 | 536 | addr <- rs1 + imm 537 | rd <- data at addr, possibly sign-extended 538 | PC <- PC + 4 539 | 540 | If we let N be addr%4, then: 541 | 542 | instr N shift1 shift2 543 | -------------------------- 544 | LB 0 SLL 24 SRA 24 545 | LB 1 SLL 16 SRA 24 546 | LB 2 SLL 8 SRA 24 547 | LB 3 SLL 0 SRA 24 548 | LBU 0 SLL 24 SRL 24 549 | LBU 1 SLL 16 SRL 24 550 | LBU 2 SLL 8 SRL 24 551 | LBU 3 SLL 0 SRL 24 552 | LH 0 SLL 16 SRA 16 553 | LH 2 SLL 0 SRA 16 554 | LHU 0 SLL 16 SRL 16 555 | LHU 2 SLL 0 SRL 16 556 | LW 0 SLL 0 SRA 0 557 | (all other N are misaligned accesses) 558 | 559 | Where there is an SLL 0, the machine cycle 560 | could be skipped, but in the interests of 561 | simpler logic, we will not do that. 562 | 563 | rs1 -> X 564 | imm -> Y 565 | ALU ADD -> Z 566 | Z -> memaddr 567 | --------------------- 568 | memdata -> X 569 | shamt1 -> Y 570 | ALU SLL -> Z 571 | Z -> rd 572 | --------------------- 573 | rd -> X 574 | shamt2 -> Y 575 | ALU SRA/SRL -> Z 576 | Z -> rd 577 | PC + 4 -> PC 578 | PC + 4 -> memaddr 579 | """ 580 | with m.If(self._instr_phase == 0): 581 | m.d.comb += [ 582 | self.reg_to_x.eq(1), 583 | self._x_reg_select.eq(InstrReg.RS1), 584 | self.y_mux_select.eq(SeqMuxSelect.IMM), 585 | self.alu_op_to_z.eq(AluOp.ADD), 586 | self.memaddr_mux_select.eq(SeqMuxSelect.Z), 587 | self._next_instr_phase.eq(1), 588 | ] 589 | 590 | with m.Elif(self._instr_phase == 1): 591 | # Check for exception conditions first 592 | with m.If(self._funct3.matches(MemAccessWidth.H, MemAccessWidth.HU) & 593 | self.memaddr_2_lsb[0]): 594 | self.set_exception( 595 | m, ConstSelect.EXC_LOAD_ADDR_MISALIGN, mtval=SeqMuxSelect.MEMADDR) 596 | 597 | with m.Elif((self._funct3 == MemAccessWidth.W) & 598 | (self.memaddr_2_lsb != 0)): 599 | self.set_exception( 600 | m, ConstSelect.EXC_LOAD_ADDR_MISALIGN, mtval=SeqMuxSelect.MEMADDR) 601 | 602 | with m.Elif(~self._funct3.matches(MemAccessWidth.B, MemAccessWidth.BU, 603 | MemAccessWidth.H, MemAccessWidth.HU, 604 | MemAccessWidth.W)): 605 | self.handle_illegal_instr(m) 606 | 607 | with m.Else(): 608 | m.d.comb += [ 609 | self.mem_rd.eq(1), 610 | self.x_mux_select.eq(SeqMuxSelect.MEMDATA_RD), 611 | self.y_mux_select.eq(SeqMuxSelect.CONST), 612 | self.alu_op_to_z.eq(AluOp.SLL), 613 | self._z_reg_select.eq(InstrReg.RD), 614 | self._next_instr_phase.eq(2), 615 | ] 616 | 617 | with m.Switch(self._funct3): 618 | 619 | with m.Case(MemAccessWidth.B, MemAccessWidth.BU): 620 | with m.Switch(self.memaddr_2_lsb): 621 | with m.Case(0): 622 | m.d.comb += self._const.eq( 623 | ConstSelect.SHAMT_24) 624 | with m.Case(1): 625 | m.d.comb += self._const.eq( 626 | ConstSelect.SHAMT_16) 627 | with m.Case(2): 628 | m.d.comb += self._const.eq(ConstSelect.SHAMT_8) 629 | with m.Case(3): 630 | m.d.comb += self._const.eq(ConstSelect.SHAMT_0) 631 | 632 | with m.Case(MemAccessWidth.H, MemAccessWidth.HU): 633 | with m.Switch(self.memaddr_2_lsb): 634 | with m.Case(0): 635 | m.d.comb += self._const.eq( 636 | ConstSelect.SHAMT_16) 637 | with m.Case(2): 638 | m.d.comb += self._const.eq(ConstSelect.SHAMT_0) 639 | 640 | with m.Case(MemAccessWidth.W): 641 | m.d.comb += self._const.eq(ConstSelect.SHAMT_0) 642 | 643 | with m.Else(): 644 | m.d.comb += [ 645 | self.reg_to_x.eq(1), 646 | self._x_reg_select.eq(InstrReg.RD), 647 | self.y_mux_select.eq(SeqMuxSelect.CONST), 648 | self._z_reg_select.eq(InstrReg.RD), 649 | ] 650 | 651 | with m.Switch(self._funct3): 652 | with m.Case(MemAccessWidth.B): 653 | m.d.comb += [ 654 | self._const.eq(ConstSelect.SHAMT_24), 655 | self.alu_op_to_z.eq(AluOp.SRA), 656 | ] 657 | with m.Case(MemAccessWidth.BU): 658 | m.d.comb += [ 659 | self._const.eq(ConstSelect.SHAMT_24), 660 | self.alu_op_to_z.eq(AluOp.SRL), 661 | ] 662 | with m.Case(MemAccessWidth.H): 663 | m.d.comb += [ 664 | self._const.eq(ConstSelect.SHAMT_16), 665 | self.alu_op_to_z.eq(AluOp.SRA), 666 | ] 667 | with m.Case(MemAccessWidth.HU): 668 | m.d.comb += [ 669 | self._const.eq(ConstSelect.SHAMT_16), 670 | self.alu_op_to_z.eq(AluOp.SRL), 671 | ] 672 | with m.Case(MemAccessWidth.W): 673 | m.d.comb += [ 674 | self._const.eq(ConstSelect.SHAMT_0), 675 | self.alu_op_to_z.eq(AluOp.SRL), 676 | ] 677 | 678 | self.next_instr(m) 679 | 680 | def handle_store(self, m: Module): 681 | """Adds the STORE logic to the given module. 682 | 683 | Note that byte stores are byte-aligned, half-word stores 684 | are 16-bit aligned, and word stores are 32-bit aligned. 685 | Attempting to stores unaligned will lead to undefined 686 | behavior. 687 | 688 | addr <- rs1 + imm 689 | data <- rs2 690 | PC <- PC + 4 691 | 692 | rs1 -> X 693 | imm -> Y 694 | ALU ADD -> Z 695 | Z -> memaddr 696 | --------------------- 697 | rs2 -> X 698 | shamt -> Y 699 | ALU SLL -> Z 700 | Z -> wrdata 701 | -> wrmask 702 | --------------------- 703 | PC + 4 -> PC 704 | PC + 4 -> memaddr 705 | """ 706 | with m.If(self._instr_phase == 0): 707 | m.d.comb += [ 708 | self.reg_to_x.eq(1), 709 | self._x_reg_select.eq(InstrReg.RS1), 710 | self.y_mux_select.eq(SeqMuxSelect.IMM), 711 | self.alu_op_to_z.eq(AluOp.ADD), 712 | self.memaddr_mux_select.eq(SeqMuxSelect.Z), 713 | self._next_instr_phase.eq(1), 714 | ] 715 | 716 | with m.Elif(self._instr_phase == 1): 717 | # Check for exception conditions first 718 | with m.If((self._funct3 == MemAccessWidth.H) & self.memaddr_2_lsb[0]): 719 | self.set_exception( 720 | m, ConstSelect.EXC_STORE_AMO_ADDR_MISALIGN, mtval=SeqMuxSelect.MEMADDR) 721 | 722 | with m.Elif((self._funct3 == MemAccessWidth.W) & (self.memaddr_2_lsb != 0)): 723 | self.set_exception( 724 | m, ConstSelect.EXC_STORE_AMO_ADDR_MISALIGN, mtval=SeqMuxSelect.MEMADDR) 725 | 726 | with m.Elif(~self._funct3.matches(MemAccessWidth.B, 727 | MemAccessWidth.H, 728 | MemAccessWidth.W)): 729 | self.handle_illegal_instr(m) 730 | 731 | with m.Else(): 732 | m.d.comb += [ 733 | self.reg_to_x.eq(1), 734 | self._x_reg_select.eq(InstrReg.RS2), 735 | self.y_mux_select.eq(SeqMuxSelect.CONST), 736 | self.alu_op_to_z.eq(AluOp.SLL), 737 | self.memdata_wr_mux_select.eq(SeqMuxSelect.Z), 738 | self._next_instr_phase.eq(2), 739 | ] 740 | 741 | with m.Switch(self._funct3): 742 | 743 | with m.Case(MemAccessWidth.B): 744 | with m.Switch(self.memaddr_2_lsb): 745 | with m.Case(0): 746 | m.d.comb += self._const.eq(ConstSelect.SHAMT_0) 747 | with m.Case(1): 748 | m.d.comb += self._const.eq(ConstSelect.SHAMT_8) 749 | with m.Case(2): 750 | m.d.comb += self._const.eq( 751 | ConstSelect.SHAMT_16) 752 | with m.Case(3): 753 | m.d.comb += self._const.eq( 754 | ConstSelect.SHAMT_24) 755 | 756 | with m.Case(MemAccessWidth.H): 757 | with m.Switch(self.memaddr_2_lsb): 758 | with m.Case(0): 759 | m.d.comb += self._const.eq(ConstSelect.SHAMT_0) 760 | with m.Case(2): 761 | m.d.comb += self._const.eq( 762 | ConstSelect.SHAMT_16) 763 | 764 | with m.Case(MemAccessWidth.W): 765 | m.d.comb += self._const.eq(ConstSelect.SHAMT_0) 766 | 767 | with m.Else(): 768 | with m.Switch(self._funct3): 769 | 770 | with m.Case(MemAccessWidth.B): 771 | with m.Switch(self.memaddr_2_lsb): 772 | with m.Case(0): 773 | m.d.comb += self.mem_wr_mask.eq(0b0001) 774 | with m.Case(1): 775 | m.d.comb += self.mem_wr_mask.eq(0b0010) 776 | with m.Case(2): 777 | m.d.comb += self.mem_wr_mask.eq(0b0100) 778 | with m.Case(3): 779 | m.d.comb += self.mem_wr_mask.eq(0b1000) 780 | 781 | with m.Case(MemAccessWidth.H): 782 | with m.Switch(self.memaddr_2_lsb): 783 | with m.Case(0): 784 | m.d.comb += self.mem_wr_mask.eq(0b0011) 785 | with m.Case(2): 786 | m.d.comb += self.mem_wr_mask.eq(0b1100) 787 | 788 | with m.Case(MemAccessWidth.W): 789 | m.d.comb += self.mem_wr_mask.eq(0b1111) 790 | 791 | m.d.comb += self.mem_wr.eq(1) 792 | self.next_instr(m) 793 | 794 | def handle_csrs(self, m: Module): 795 | """Adds the SYSTEM (CSR opcodes) logic to the given module. 796 | 797 | Some points of interest: 798 | 799 | * Attempts to write a read-only register 800 | result in an illegal instruction exception. 801 | * Attempts to access a CSR that doesn't exist 802 | result in an illegal instruction exception. 803 | * Attempts to write read-only bits to a read/write CSR 804 | are ignored. 805 | 806 | Because we're building this in hardware, which is 807 | expensive, we're not implementing any CSRs that aren't 808 | strictly necessary. The documentation for the misa, mvendorid, 809 | marchid, and mimpid registers state that they can return zero if 810 | unimplemented. This implies that unimplemented CSRs still 811 | exist. 812 | 813 | The mhartid, because we only have one HART, can just return zero. 814 | """ 815 | with m.Switch(self._funct3): 816 | with m.Case(SystemFunc.CSRRW): 817 | self.handle_CSRRW(m) 818 | with m.Case(SystemFunc.CSRRWI): 819 | self.handle_CSRRWI(m) 820 | with m.Case(SystemFunc.CSRRS): 821 | self.handle_CSRRS(m) 822 | with m.Case(SystemFunc.CSRRSI): 823 | self.handle_CSRRSI(m) 824 | with m.Case(SystemFunc.CSRRC): 825 | self.handle_CSRRC(m) 826 | with m.Case(SystemFunc.CSRRCI): 827 | self.handle_CSRRCI(m) 828 | with m.Default(): 829 | self.handle_illegal_instr(m) 830 | 831 | def handle_CSRRW(self, m: Module): 832 | m.d.comb += self._funct12_to_csr_num.eq(1) 833 | 834 | with m.If(self._instr_phase == 0): 835 | with m.If(self.rd0): 836 | m.d.comb += [ 837 | self._x_reg_select.eq(InstrReg.ZERO), 838 | self.reg_to_x.eq(1), 839 | ] 840 | with m.Else(): 841 | m.d.comb += [ 842 | self.csr_to_x.eq(1) 843 | ] 844 | m.d.comb += [ 845 | self._y_reg_select.eq(InstrReg.RS1), 846 | self.reg_to_y.eq(1), 847 | self.alu_op_to_z.eq(AluOp.Y), 848 | self.z_to_csr.eq(1), 849 | self.tmp_mux_select.eq(SeqMuxSelect.X), 850 | self._next_instr_phase.eq(1), 851 | ] 852 | 853 | with m.Else(): 854 | m.d.comb += [ 855 | self.z_mux_select.eq(SeqMuxSelect.TMP), 856 | self._z_reg_select.eq(InstrReg.RD), 857 | ] 858 | self.next_instr(m) 859 | 860 | def handle_CSRRWI(self, m: Module): 861 | m.d.comb += self._funct12_to_csr_num.eq(1) 862 | 863 | with m.If(self._instr_phase == 0): 864 | with m.If(self.rd0): 865 | m.d.comb += [ 866 | self._x_reg_select.eq(InstrReg.ZERO), 867 | self.reg_to_x.eq(1), 868 | ] 869 | with m.Else(): 870 | m.d.comb += [ 871 | self.csr_to_x.eq(1) 872 | ] 873 | m.d.comb += [ 874 | self.y_mux_select.eq(SeqMuxSelect.IMM), 875 | self.alu_op_to_z.eq(AluOp.Y), 876 | self.z_to_csr.eq(1), 877 | self.tmp_mux_select.eq(SeqMuxSelect.X), 878 | self._next_instr_phase.eq(1), 879 | ] 880 | 881 | with m.Else(): 882 | m.d.comb += [ 883 | self.z_mux_select.eq(SeqMuxSelect.TMP), 884 | self._z_reg_select.eq(InstrReg.RD), 885 | ] 886 | self.next_instr(m) 887 | 888 | def handle_CSRRS(self, m: Module): 889 | m.d.comb += self._funct12_to_csr_num.eq(1) 890 | 891 | with m.If(self._instr_phase == 0): 892 | m.d.comb += [ 893 | self.csr_to_x.eq(1), 894 | self._y_reg_select.eq(InstrReg.RS1), 895 | self.reg_to_y.eq(1), 896 | self.alu_op_to_z.eq(AluOp.OR), 897 | self.z_to_csr.eq(~self.rs1_0), 898 | self.tmp_mux_select.eq(SeqMuxSelect.X), 899 | self._next_instr_phase.eq(1), 900 | ] 901 | 902 | with m.Else(): 903 | m.d.comb += [ 904 | self.z_mux_select.eq(SeqMuxSelect.TMP), 905 | self._z_reg_select.eq(InstrReg.RD), 906 | ] 907 | self.next_instr(m) 908 | 909 | def handle_CSRRSI(self, m: Module): 910 | m.d.comb += self._funct12_to_csr_num.eq(1) 911 | 912 | with m.If(self._instr_phase == 0): 913 | m.d.comb += [ 914 | self.csr_to_x.eq(1), 915 | self.y_mux_select.eq(SeqMuxSelect.IMM), 916 | self.alu_op_to_z.eq(AluOp.OR), 917 | self.z_to_csr.eq(~self.imm0), 918 | self.tmp_mux_select.eq(SeqMuxSelect.X), 919 | self._next_instr_phase.eq(1), 920 | ] 921 | 922 | with m.Else(): 923 | m.d.comb += [ 924 | self.z_mux_select.eq(SeqMuxSelect.TMP), 925 | self._z_reg_select.eq(InstrReg.RD), 926 | ] 927 | self.next_instr(m) 928 | 929 | def handle_CSRRC(self, m: Module): 930 | m.d.comb += self._funct12_to_csr_num.eq(1) 931 | 932 | with m.If(self._instr_phase == 0): 933 | m.d.comb += [ 934 | self.csr_to_x.eq(1), 935 | self._y_reg_select.eq(InstrReg.RS1), 936 | self.reg_to_y.eq(1), 937 | self.alu_op_to_z.eq(AluOp.AND_NOT), 938 | self.z_to_csr.eq(~self.rs1_0), 939 | self.tmp_mux_select.eq(SeqMuxSelect.X), 940 | self._next_instr_phase.eq(1), 941 | ] 942 | 943 | with m.Else(): 944 | m.d.comb += [ 945 | self.z_mux_select.eq(SeqMuxSelect.TMP), 946 | self._z_reg_select.eq(InstrReg.RD), 947 | ] 948 | self.next_instr(m) 949 | 950 | def handle_CSRRCI(self, m: Module): 951 | m.d.comb += self._funct12_to_csr_num.eq(1) 952 | 953 | with m.If(self._instr_phase == 0): 954 | m.d.comb += [ 955 | self.csr_to_x.eq(1), 956 | self.y_mux_select.eq(SeqMuxSelect.IMM), 957 | self.alu_op_to_z.eq(AluOp.AND_NOT), 958 | self.z_to_csr.eq(~self.imm0), 959 | self.tmp_mux_select.eq(SeqMuxSelect.X), 960 | self._next_instr_phase.eq(1), 961 | ] 962 | 963 | with m.Else(): 964 | m.d.comb += [ 965 | self.z_mux_select.eq(SeqMuxSelect.TMP), 966 | self._z_reg_select.eq(InstrReg.RD), 967 | ] 968 | self.next_instr(m) 969 | 970 | def handle_MRET(self, m: Module): 971 | m.d.comb += [ 972 | self._mepc_num_to_csr_num.eq(1), 973 | self.csr_to_x.eq(1), 974 | self.exit_trap.eq(1), 975 | ] 976 | self.next_instr(m, NextPC.X) 977 | 978 | def handle_ECALL(self, m: Module): 979 | """Handles the ECALL instruction. 980 | 981 | Note that normally, ECALL is used from a lower privelege mode, which stores 982 | the PC of the instruction in the appropriate lower EPC CSR (e.g. SEPC or UEPC). 983 | This allows interrupts to be handled during the call, because we're in a higher 984 | privelege level. However, in machine mode, there is no higher privelege level, 985 | so we have no choice but to disable interrupts for an ECALL. 986 | """ 987 | self.set_exception( 988 | m, ConstSelect.EXC_ECALL_FROM_MACH_MODE, mtval=SeqMuxSelect.PC, fatal=False) 989 | 990 | def handle_EBREAK(self, m: Module): 991 | """Handles the EBREAK instruction. 992 | 993 | Note that normally, EBREAK is used from a lower privelege mode, which stores 994 | the PC of the instruction in the appropriate lower EPC CSR (e.g. SEPC or UEPC). 995 | This allows interrupts to be handled during the call, because we're in a higher 996 | privelege level. However, in machine mode, there is no higher privelege level, 997 | so we have no choice but to disable interrupts for an EBREAK. 998 | """ 999 | self.set_exception( 1000 | m, ConstSelect.EXC_BREAKPOINT, mtval=SeqMuxSelect.PC, fatal=False) 1001 | -------------------------------------------------------------------------------- /sequencer_card.py: -------------------------------------------------------------------------------- 1 | # Disable pylint's "your name is too short" warning. 2 | # pylint: disable=C0103 3 | # Disable protected access warnings 4 | # pylint: disable=W0212 5 | from typing import List, Tuple 6 | 7 | from nmigen import Signal, Module, Elaboratable, signed, ClockSignal, ClockDomain, Repl 8 | from nmigen import Mux 9 | from nmigen.build import Platform 10 | from nmigen.asserts import Assert, Assume, Cover, Stable, Past 11 | 12 | from consts import AluOp, AluFunc, BranchCond, CSRAddr, MemAccessWidth 13 | from consts import Opcode, OpcodeFormat, SystemFunc, TrapCause, PrivFunc 14 | from consts import InstrReg, OpcodeSelect, TrapCauseSelect 15 | from consts import NextPC, Instr, SeqMuxSelect, ConstSelect 16 | from transparent_latch import TransparentLatch 17 | from util import main, all_true 18 | from IC_7416244 import IC_mux32 19 | from IC_7416374 import IC_reg32_with_mux 20 | from IC_GAL import IC_GAL_imm_format_decoder 21 | from sequencer_rom import SequencerROM 22 | from trap_rom import TrapROM 23 | from irq_load_rom import IrqLoadInstrROM 24 | 25 | 26 | class SequencerState: 27 | """Contains only the registers in the sequencer card. 28 | 29 | This is useful to take snapshots of the entire state. 30 | """ 31 | 32 | def __init__(self, ext_init: bool = False): 33 | attrs = [] if not ext_init else [("uninitialized", "")] 34 | 35 | self._pc = Signal(32, attrs=attrs) 36 | self._instr_phase = Signal(2) 37 | # Not quite a register, but the output of a latch 38 | self._instr = Signal(32) 39 | self._stored_alu_eq = Signal() 40 | self._stored_alu_lt = Signal() 41 | self._stored_alu_ltu = Signal() 42 | 43 | self.memaddr = Signal(32) 44 | self.memdata_wr = Signal(32) 45 | 46 | self._tmp = Signal(32) 47 | self.reg_page = Signal() 48 | 49 | # Trap handling 50 | # Goes high when we are handling a trap condition, 51 | # but not yet in the trap routine. 52 | self.trap = Signal() 53 | 54 | # Raised when an exception occurs. 55 | self.exception = Signal() 56 | self.fatal = Signal() 57 | 58 | self._mtvec = Signal(32, attrs=attrs) 59 | 60 | 61 | class SequencerCard(Elaboratable): 62 | """Logic for the sequencer card. 63 | 64 | Control lines indicate to the other cards what to do now, 65 | if the control lines control combinatorial logic, or 66 | to do next, if the control lines control sequential logic. 67 | 68 | This module uses two system-wide clocks: ph1 and ph2. The phases look 69 | like this: 70 | 71 | ________ ________ 72 | ph1 _| RD |___WR___| RD |___WR___| 73 | ___ ____ ____ ____ ___ 74 | ph2 |___| |___| |___| |___| 75 | 76 | There's also a clock ph2w which is just ph2 on the WR phase: 77 | 78 | ____________ _____________ ___ 79 | ph2w |___| |___| 80 | """ 81 | 82 | def __init__(self, ext_init: bool = False, chips: bool = False): 83 | self.chips = chips 84 | self.ext_init = ext_init 85 | 86 | self.state = SequencerState(ext_init) 87 | self.rom = SequencerROM() 88 | self.trap_rom = TrapROM() 89 | self.irq_load_rom = IrqLoadInstrROM() 90 | 91 | # A clock-based signal, high only at the end of a machine 92 | # cycle (i.e. phase 5, the very end of the write phase). 93 | self.mcycle_end = Signal() 94 | 95 | # Control signals. 96 | self.alu_eq = Signal() 97 | self.alu_lt = Signal() 98 | self.alu_ltu = Signal() 99 | 100 | self.x_reg = Signal(5) 101 | self.y_reg = Signal(5) 102 | self.z_reg = Signal(5) 103 | 104 | # Raised when an interrupt based on an external timer goes off. 105 | self.time_irq = Signal() 106 | # Raised when any other external interrupt goes off. 107 | self.ext_irq = Signal() 108 | # Raised on the last phase of an instruction. 109 | self.set_instr_complete = Signal() 110 | self.instr_complete = Signal() 111 | # Raised when the exception card should store trap data. 112 | self.save_trap_csrs = Signal() 113 | self.is_interrupted = Signal() 114 | self.instr_misalign = Signal() 115 | self.bad_instr = Signal() 116 | 117 | # CSR lines 118 | self.csr_num = Signal(CSRAddr) 119 | self.csr_num_is_mtvec = Signal() 120 | self.csr_to_x = Signal() 121 | self.z_to_csr = Signal() 122 | 123 | # Buses, bidirectional 124 | self.data_x_in = Signal(32) 125 | self.data_x_out = Signal(32) 126 | self.data_y_in = Signal(32) 127 | self.data_y_out = Signal(32) 128 | self.data_z_in = Signal(32) 129 | self.data_z_out = Signal(32) 130 | self.data_z_in_2_lsb0 = Signal() 131 | 132 | # Memory 133 | self.mem_rd = Signal(reset=1) 134 | self.mem_wr = Signal() 135 | # Bytes in memory word to write 136 | self.mem_wr_mask = Signal(4) 137 | self.memaddr_2_lsb = Signal(2) 138 | 139 | # Memory bus, bidirectional 140 | self.memdata_rd = Signal(32) 141 | 142 | # Internals 143 | 144 | # This opens the instr transparent latch to memdata. The enable 145 | # (i.e. load_instr) on the latch is a register, so setting load_instr 146 | # now opens the transparent latch next. 147 | self._load_instr = Signal(reset=1) 148 | 149 | self._pc_plus_4 = Signal(32) 150 | self._next_instr_phase = Signal(len(self.state._instr_phase)) 151 | self._next_reg_page = Signal() 152 | 153 | # Instruction decoding 154 | self._opcode = Signal(7) 155 | self.opcode_select = Signal(OpcodeSelect) 156 | self._rs1 = Signal(5) 157 | self._rs2 = Signal(5) 158 | self._rd = Signal(5) 159 | self._funct3 = Signal(3) 160 | self._funct7 = Signal(7) 161 | self._funct12 = Signal(12) 162 | self._alu_func = Signal(4) 163 | self._imm_format = Signal(OpcodeFormat) 164 | self._imm = Signal(32) 165 | self.branch_cond = Signal() 166 | self.imm0 = Signal() 167 | self.rd0 = Signal() 168 | self.rs1_0 = Signal() 169 | 170 | self._x_reg_select = Signal(InstrReg) 171 | self._y_reg_select = Signal(InstrReg) 172 | self._z_reg_select = Signal(InstrReg) 173 | 174 | self._const = Signal(ConstSelect) 175 | 176 | # -> X 177 | self.reg_to_x = Signal() 178 | self.x_mux_select = Signal(SeqMuxSelect) 179 | 180 | # -> Y 181 | self.reg_to_y = Signal() 182 | self.y_mux_select = Signal(SeqMuxSelect) 183 | 184 | # -> Z 185 | self.z_mux_select = Signal(SeqMuxSelect) 186 | self.alu_op_to_z = Signal(AluOp) # 4 bits 187 | 188 | # -> PC 189 | self.pc_mux_select = Signal(SeqMuxSelect) 190 | 191 | # -> tmp 192 | self.tmp_mux_select = Signal(SeqMuxSelect) 193 | 194 | # -> csr_num 195 | self._funct12_to_csr_num = Signal() 196 | self._mepc_num_to_csr_num = Signal() 197 | self._mcause_to_csr_num = Signal() 198 | self.mtvec_mux_select = Signal(SeqMuxSelect) 199 | 200 | # -> memaddr 201 | self.memaddr_mux_select = Signal(SeqMuxSelect) 202 | 203 | # -> memdata 204 | self.memdata_wr_mux_select = Signal(SeqMuxSelect) 205 | 206 | # memory load shamt 207 | self._shamt = Signal(5) 208 | 209 | # -> various CSRs 210 | self._trapcause = Signal(TrapCause) 211 | self._trapcause_select = Signal(TrapCauseSelect) 212 | self.clear_pend_mti = Signal() 213 | self.clear_pend_mei = Signal() 214 | self.mei_pend = Signal() 215 | self.mti_pend = Signal() 216 | self.vec_mode = Signal(2) 217 | 218 | self.enter_trap = Signal() 219 | self.exit_trap = Signal() 220 | 221 | # Signals for next registers 222 | self.load_trap = Signal() 223 | self.next_trap = Signal() 224 | self.load_exception = Signal() 225 | self.next_exception = Signal() 226 | self.next_fatal = Signal() 227 | 228 | self.enable_sequencer_rom = Signal() 229 | 230 | def elaborate(self, _: Platform) -> Module: 231 | """Implements the logic of the sequencer card.""" 232 | m = Module() 233 | 234 | m.submodules.rom = self.rom 235 | m.submodules.trap_rom = self.trap_rom 236 | m.submodules.irq_load_rom = self.irq_load_rom 237 | 238 | m.d.comb += self._pc_plus_4.eq(self.state._pc + 4) 239 | m.d.comb += self.vec_mode.eq(self.state._mtvec[:2]) 240 | m.d.comb += self.memaddr_2_lsb.eq(self.state.memaddr[0:2]) 241 | m.d.comb += self.imm0.eq(self._imm == 0) 242 | m.d.comb += self.rd0.eq(self._rd == 0) 243 | m.d.comb += self.rs1_0.eq(self._rs1 == 0) 244 | m.d.comb += self.csr_num_is_mtvec.eq(self.csr_num == CSRAddr.MTVEC) 245 | m.d.comb += self.mtvec_mux_select.eq(Mux(self.z_to_csr & self.csr_num_is_mtvec, 246 | SeqMuxSelect.Z, SeqMuxSelect.MTVEC)) 247 | # Only used on instruction phase 1 in BRANCH, which is why we can 248 | # register this on phase 1. Also, because it's an input to a ROM, 249 | # we have to ensure the signal is registered. 250 | m.d.ph1 += self.data_z_in_2_lsb0.eq(self.data_z_in[0:2] == 0) 251 | 252 | with m.If(self.set_instr_complete): 253 | m.d.comb += self.instr_complete.eq(self.mcycle_end) 254 | with m.Else(): 255 | m.d.comb += self.instr_complete.eq(0) 256 | 257 | # We only check interrupts once we're about to load an instruction but we're not already 258 | # trapping an interrupt. 259 | m.d.comb += self.is_interrupted.eq(all_true(self.instr_complete, 260 | ~self.state.trap, 261 | (self.mei_pend | self.mti_pend))) 262 | 263 | # Because we don't support the C (compressed instructions) 264 | # extension, the PC must be 32-bit aligned. 265 | m.d.comb += self.instr_misalign.eq( 266 | all_true(self.state._pc[0:2] != 0, ~self.state.trap)) 267 | 268 | m.d.comb += self.enable_sequencer_rom.eq( 269 | ~self.state.trap & ~self.instr_misalign & ~self.bad_instr) 270 | 271 | self.encode_opcode_select(m) 272 | self.connect_roms(m) 273 | self.process(m) 274 | self.updates(m) 275 | 276 | return m 277 | 278 | def connect_roms(self, m: Module): 279 | m.d.comb += self.rom.enable_sequencer_rom.eq(self.enable_sequencer_rom) 280 | m.d.comb += self.irq_load_rom.enable_sequencer_rom.eq( 281 | self.enable_sequencer_rom) 282 | 283 | # Inputs 284 | m.d.comb += [ 285 | self.trap_rom.is_interrupted.eq(self.is_interrupted), 286 | self.trap_rom.exception.eq(self.state.exception), 287 | self.trap_rom.fatal.eq(self.state.fatal), 288 | self.trap_rom.instr_misalign.eq(self.instr_misalign), 289 | self.trap_rom.bad_instr.eq(self.bad_instr), 290 | self.trap_rom.trap.eq(self.state.trap), 291 | self.trap_rom.mei_pend.eq(self.mei_pend), 292 | self.trap_rom.mti_pend.eq(self.mti_pend), 293 | self.trap_rom.vec_mode.eq(self.vec_mode), 294 | self.trap_rom._instr_phase.eq(self.state._instr_phase), 295 | 296 | self.irq_load_rom.is_interrupted.eq(self.is_interrupted), 297 | self.irq_load_rom._instr_phase.eq(self.state._instr_phase), 298 | 299 | self.rom._instr_phase.eq(self.state._instr_phase), 300 | 301 | self.rom.memaddr_2_lsb.eq(self.memaddr_2_lsb), 302 | self.rom.branch_cond.eq(self.branch_cond), 303 | self.rom.data_z_in_2_lsb0.eq(self.data_z_in_2_lsb0), 304 | 305 | # Instruction decoding 306 | self.rom.opcode_select.eq(self.opcode_select), 307 | self.rom._funct3.eq(self._funct3), 308 | self.rom._alu_func.eq(self._alu_func), 309 | 310 | self.rom.imm0.eq(self.imm0), 311 | self.rom.rd0.eq(self.rd0), 312 | self.rom.rs1_0.eq(self.rs1_0), 313 | ] 314 | 315 | # Outputs 316 | m.d.comb += [ 317 | # Raised on the last phase of an instruction. 318 | self.set_instr_complete.eq(Mux(self.enable_sequencer_rom, 319 | self.rom.set_instr_complete, self.trap_rom.set_instr_complete)), 320 | 321 | # Raised when the exception card should store trap data. 322 | self.save_trap_csrs.eq(Mux(self.enable_sequencer_rom, 323 | self.rom.save_trap_csrs, self.trap_rom.save_trap_csrs)), 324 | 325 | # CSR lines 326 | self.csr_to_x.eq(Mux(self.enable_sequencer_rom, 327 | self.rom.csr_to_x, self.trap_rom.csr_to_x)), 328 | self.z_to_csr.eq(self.rom.z_to_csr), 329 | 330 | # Memory 331 | self.mem_rd.eq(Mux(self.enable_sequencer_rom, 332 | self.rom.mem_rd, self.irq_load_rom.mem_rd)), 333 | self.mem_wr.eq(self.rom.mem_wr), 334 | # Bytes in memory word to write 335 | self.mem_wr_mask.eq(self.rom.mem_wr_mask), 336 | 337 | # Internals 338 | 339 | # This opens the instr transparent latch to memdata. The enable 340 | # (i.e. load_instr) on the latch is a register, so setting load_instr 341 | # now opens the transparent latch next. 342 | self._load_instr.eq(self.irq_load_rom._load_instr), 343 | self._next_instr_phase.eq(Mux(self.enable_sequencer_rom, 344 | self.rom._next_instr_phase, self.trap_rom._next_instr_phase)), 345 | 346 | self._x_reg_select.eq(self.rom._x_reg_select), 347 | self._y_reg_select.eq(self.rom._y_reg_select), 348 | self._z_reg_select.eq(self.rom._z_reg_select), 349 | 350 | # -> X 351 | self.reg_to_x.eq(self.rom.reg_to_x), 352 | self.x_mux_select.eq(Mux(self.csr_to_x & self.csr_num_is_mtvec, SeqMuxSelect.MTVEC, 353 | Mux(self.enable_sequencer_rom, 354 | self.rom.x_mux_select, self.trap_rom.x_mux_select))), 355 | self._const.eq(Mux(self.enable_sequencer_rom, 356 | self.rom._const, self.trap_rom._const)), 357 | 358 | # -> Y 359 | self.reg_to_y.eq(self.rom.reg_to_y), 360 | self.y_mux_select.eq(Mux(self.enable_sequencer_rom, 361 | self.rom.y_mux_select, self.trap_rom.y_mux_select)), 362 | 363 | # -> Z 364 | self.z_mux_select.eq(Mux(self.enable_sequencer_rom, 365 | self.rom.z_mux_select, self.trap_rom.z_mux_select)), 366 | self.alu_op_to_z.eq(Mux(self.enable_sequencer_rom, self.rom.alu_op_to_z, 367 | self.trap_rom.alu_op_to_z)), 368 | 369 | # -> PC 370 | self.pc_mux_select.eq(Mux(self.enable_sequencer_rom, 371 | self.rom.pc_mux_select, self.trap_rom.pc_mux_select)), 372 | 373 | # -> tmp 374 | self.tmp_mux_select.eq(self.rom.tmp_mux_select), 375 | 376 | # -> csr_num 377 | self._funct12_to_csr_num.eq(self.rom._funct12_to_csr_num), 378 | self._mepc_num_to_csr_num.eq(self.rom._mepc_num_to_csr_num), 379 | self._mcause_to_csr_num.eq(Mux(self.enable_sequencer_rom, 380 | self.rom._mcause_to_csr_num, self.trap_rom._mcause_to_csr_num)), 381 | 382 | # -> memaddr 383 | self.memaddr_mux_select.eq(Mux(self.enable_sequencer_rom, 384 | self.rom.memaddr_mux_select, self.trap_rom.memaddr_mux_select)), 385 | 386 | # -> memdata 387 | self.memdata_wr_mux_select.eq(self.rom.memdata_wr_mux_select), 388 | 389 | # -> various CSRs 390 | self.clear_pend_mti.eq(self.trap_rom.clear_pend_mti), 391 | self.clear_pend_mei.eq(self.trap_rom.clear_pend_mei), 392 | 393 | self.enter_trap.eq(self.rom.enter_trap | self.trap_rom.enter_trap), 394 | self.exit_trap.eq(self.rom.exit_trap | self.trap_rom.exit_trap), 395 | 396 | # Signals for next registers 397 | self.load_trap.eq( 398 | self.rom.load_trap | self.trap_rom.load_trap | self.irq_load_rom.load_trap), 399 | self.next_trap.eq( 400 | self.rom.next_trap | self.trap_rom.next_trap | self.irq_load_rom.next_trap), 401 | self.load_exception.eq( 402 | self.rom.load_exception | self.trap_rom.load_exception), 403 | self.next_exception.eq( 404 | self.rom.next_exception | self.trap_rom.next_exception), 405 | self.next_fatal.eq(self.rom.next_fatal | self.trap_rom.next_fatal), 406 | ] 407 | 408 | def encode_opcode_select(self, m: Module): 409 | m.d.comb += self.opcode_select.eq(OpcodeSelect.NONE) 410 | m.d.comb += self._imm_format.eq(OpcodeFormat.R) 411 | 412 | with m.Switch(self._opcode): 413 | with m.Case(Opcode.LUI): 414 | m.d.comb += self.opcode_select.eq(OpcodeSelect.LUI) 415 | m.d.comb += self._imm_format.eq(OpcodeFormat.U) 416 | 417 | with m.Case(Opcode.AUIPC): 418 | m.d.comb += self.opcode_select.eq(OpcodeSelect.AUIPC) 419 | m.d.comb += self._imm_format.eq(OpcodeFormat.U) 420 | 421 | with m.Case(Opcode.OP_IMM): 422 | m.d.comb += self.opcode_select.eq(OpcodeSelect.OP_IMM) 423 | m.d.comb += self._imm_format.eq(OpcodeFormat.I) 424 | 425 | with m.Case(Opcode.OP): 426 | m.d.comb += self.opcode_select.eq(OpcodeSelect.OP) 427 | m.d.comb += self._imm_format.eq(OpcodeFormat.R) 428 | 429 | with m.Case(Opcode.JAL): 430 | m.d.comb += self.opcode_select.eq(OpcodeSelect.JAL) 431 | m.d.comb += self._imm_format.eq(OpcodeFormat.J) 432 | 433 | with m.Case(Opcode.JALR): 434 | m.d.comb += self.opcode_select.eq(OpcodeSelect.JALR) 435 | m.d.comb += self._imm_format.eq(OpcodeFormat.J) 436 | 437 | with m.Case(Opcode.BRANCH): 438 | m.d.comb += self.opcode_select.eq(OpcodeSelect.BRANCH) 439 | m.d.comb += self._imm_format.eq(OpcodeFormat.B) 440 | 441 | with m.Case(Opcode.LOAD): 442 | m.d.comb += self.opcode_select.eq(OpcodeSelect.LOAD) 443 | m.d.comb += self._imm_format.eq(OpcodeFormat.I) 444 | 445 | with m.Case(Opcode.STORE): 446 | m.d.comb += self.opcode_select.eq(OpcodeSelect.STORE) 447 | m.d.comb += self._imm_format.eq(OpcodeFormat.S) 448 | 449 | with m.Case(Opcode.SYSTEM): 450 | m.d.comb += self._imm_format.eq(OpcodeFormat.SYS) 451 | 452 | with m.If(self._funct3 == SystemFunc.PRIV): 453 | with m.Switch(self.state._instr): 454 | with m.Case(Instr.MRET): 455 | m.d.comb += self.opcode_select.eq( 456 | OpcodeSelect.MRET) 457 | with m.Case(Instr.ECALL): 458 | m.d.comb += self.opcode_select.eq( 459 | OpcodeSelect.ECALL) 460 | with m.Case(Instr.EBREAK): 461 | m.d.comb += self.opcode_select.eq( 462 | OpcodeSelect.EBREAK) 463 | with m.Else(): 464 | m.d.comb += self.opcode_select.eq(OpcodeSelect.CSRS) 465 | 466 | def decode_const(self, m: Module): 467 | const_sig = Signal(32) 468 | 469 | with m.Switch(self._const): 470 | with m.Case(ConstSelect.EXC_INSTR_ADDR_MISALIGN): 471 | m.d.comb += const_sig.eq( 472 | TrapCause.EXC_INSTR_ADDR_MISALIGN) 473 | with m.Case(ConstSelect.EXC_ILLEGAL_INSTR): 474 | m.d.comb += const_sig.eq(TrapCause.EXC_ILLEGAL_INSTR) 475 | with m.Case(ConstSelect.EXC_BREAKPOINT): 476 | m.d.comb += const_sig.eq(TrapCause.EXC_BREAKPOINT) 477 | with m.Case(ConstSelect.EXC_LOAD_ADDR_MISALIGN): 478 | m.d.comb += const_sig.eq( 479 | TrapCause.EXC_LOAD_ADDR_MISALIGN) 480 | with m.Case(ConstSelect.EXC_STORE_AMO_ADDR_MISALIGN): 481 | m.d.comb += const_sig.eq( 482 | TrapCause.EXC_STORE_AMO_ADDR_MISALIGN) 483 | with m.Case(ConstSelect.EXC_ECALL_FROM_MACH_MODE): 484 | m.d.comb += const_sig.eq( 485 | TrapCause.EXC_ECALL_FROM_MACH_MODE) 486 | with m.Case(ConstSelect.INT_MACH_EXTERNAL): 487 | m.d.comb += const_sig.eq(TrapCause.INT_MACH_EXTERNAL) 488 | with m.Case(ConstSelect.INT_MACH_TIMER): 489 | m.d.comb += const_sig.eq(TrapCause.INT_MACH_TIMER) 490 | with m.Case(ConstSelect.SHAMT_0): 491 | m.d.comb += const_sig.eq(0) 492 | with m.Case(ConstSelect.SHAMT_4): 493 | m.d.comb += const_sig.eq(4) 494 | with m.Case(ConstSelect.SHAMT_8): 495 | m.d.comb += const_sig.eq(8) 496 | with m.Case(ConstSelect.SHAMT_16): 497 | m.d.comb += const_sig.eq(16) 498 | with m.Case(ConstSelect.SHAMT_24): 499 | m.d.comb += const_sig.eq(24) 500 | with m.Default(): 501 | m.d.comb += const_sig.eq(0) 502 | 503 | return const_sig 504 | 505 | def updates(self, m: Module): 506 | with m.If(self._load_instr): 507 | m.d.ph2r += self.state._instr.eq(self.memdata_rd) 508 | 509 | m.d.ph1 += self.state._instr_phase.eq(self._next_instr_phase) 510 | m.d.ph1 += self.state.reg_page.eq(self._next_reg_page) 511 | m.d.ph1 += self.state._stored_alu_eq.eq(self.alu_eq) 512 | m.d.ph1 += self.state._stored_alu_lt.eq(self.alu_lt) 513 | m.d.ph1 += self.state._stored_alu_ltu.eq(self.alu_ltu) 514 | 515 | with m.If(self.load_trap): 516 | m.d.ph1 += self.state.trap.eq(self.next_trap) 517 | 518 | with m.If(self.load_exception): 519 | m.d.ph2 += self.state.exception.eq(self.next_exception) 520 | m.d.ph2 += self.state.fatal.eq(self.next_fatal) 521 | 522 | if self.chips: 523 | self.multiplex_to_pc_chips(m) 524 | self.multiplex_to_memaddr_chips(m) 525 | self.multiplex_to_memdata_chips(m) 526 | self.multiplex_to_tmp_chips(m) 527 | self.multiplex_to_x_chips(m) 528 | self.multiplex_to_y_chips(m) 529 | self.multiplex_to_z_chips(m) 530 | self.multiplex_to_csr_num_chips(m) 531 | self.multiplex_to_reg_nums_chips(m) 532 | self.multiplex_to_csrs_chips(m) 533 | else: 534 | self.multiplex_to_pc(m) 535 | self.multiplex_to_memaddr(m) 536 | self.multiplex_to_memdata(m) 537 | self.multiplex_to_tmp(m) 538 | self.multiplex_to_x(m) 539 | self.multiplex_to_y(m) 540 | self.multiplex_to_z(m) 541 | self.multiplex_to_csr_num(m) 542 | self.multiplex_to_reg_nums(m) 543 | self.multiplex_to_csrs(m) 544 | 545 | def process(self, m: Module): 546 | # Decode instruction 547 | m.d.comb += [ 548 | self._opcode.eq(self.state._instr[:7]), 549 | self._rs1.eq(self.state._instr[15:20]), 550 | self._rs2.eq(self.state._instr[20:25]), 551 | self._rd.eq(self.state._instr[7:12]), 552 | self._funct3.eq(self.state._instr[12:15]), 553 | self._funct7.eq(self.state._instr[25:]), 554 | self._alu_func[:3].eq(self._funct3), 555 | self._alu_func[3].eq(self._funct7[5]), 556 | self._funct12.eq(self.state._instr[20:]), 557 | ] 558 | if (self.chips): 559 | self.decode_imm_chips(m) 560 | else: 561 | self.decode_imm(m) 562 | 563 | # We don't evaluate the instruction for badness until after it's loaded. 564 | ph2r_clk = ClockSignal("ph2r") 565 | m.d.comb += self.bad_instr.eq(ph2r_clk & ( 566 | (self.state._instr[:16] == 0) | 567 | (self.state._instr == 0xFFFFFFFF))) 568 | 569 | with m.Switch(self._funct3): 570 | with m.Case(BranchCond.EQ): 571 | m.d.comb += self.branch_cond.eq(self.state._stored_alu_eq == 1) 572 | with m.Case(BranchCond.NE): 573 | m.d.comb += self.branch_cond.eq(self.state._stored_alu_eq == 0) 574 | with m.Case(BranchCond.LT): 575 | m.d.comb += self.branch_cond.eq(self.state._stored_alu_lt == 1) 576 | with m.Case(BranchCond.GE): 577 | m.d.comb += self.branch_cond.eq(self.state._stored_alu_lt == 0) 578 | with m.Case(BranchCond.LTU): 579 | m.d.comb += self.branch_cond.eq( 580 | self.state._stored_alu_ltu == 1) 581 | with m.Case(BranchCond.GEU): 582 | m.d.comb += self.branch_cond.eq( 583 | self.state._stored_alu_ltu == 0) 584 | 585 | def multiplex_to_reg(self, m: Module, clk: str, reg: Signal, sels: List[Signal], sigs: List[Signal]): 586 | """Sets up a multiplexer with a register. 587 | 588 | clk is the clock domain on which the register is clocked. 589 | 590 | reg is the register signal. 591 | 592 | sels is an array of Signals which select that input for the multiplexer (active high). If 593 | no select is active, then the register retains its value. 594 | 595 | sigs is an array of Signals which are the inputs to the multiplexer. 596 | """ 597 | assert len(sels) == len(sigs) 598 | 599 | muxreg = IC_reg32_with_mux( 600 | clk=clk, N=len(sels), ext_init=self.ext_init, faster=True) 601 | m.submodules += muxreg 602 | m.d.comb += reg.eq(muxreg.q) 603 | for i in range(len(sels)): 604 | m.d.comb += muxreg.n_sel[i].eq(~sels[i]) 605 | m.d.comb += muxreg.d[i].eq(sigs[i]) 606 | 607 | def multiplex_to_bus(self, m: Module, bus: Signal, sels: List[Signal], sigs: List[Signal]): 608 | """Sets up a multiplexer to a bus.""" 609 | assert len(sels) == len(sigs) 610 | 611 | mux = IC_mux32(N=len(sels), faster=True) 612 | m.submodules += mux 613 | m.d.comb += bus.eq(mux.y) 614 | for i in range(len(sels)): 615 | m.d.comb += mux.n_sel[i].eq(~sels[i]) 616 | m.d.comb += mux.a[i].eq(sigs[i]) 617 | 618 | def multiplex_to_csrs(self, m: Module): 619 | self.multiplex_to(m, self.state._mtvec, 620 | self.mtvec_mux_select, clk="ph2w") 621 | # with m.If(self.z_to_csr & (self.csr_num == CSRAddr.MTVEC)): 622 | # m.d.ph2w += self.state._mtvec.eq(self.data_z_in) 623 | 624 | def multiplex_to_csrs_chips(self, m: Module): 625 | self.multiplex_to_csrs(m) 626 | # self.multiplex_to_reg(m, clk="ph2w", reg=self.state._mtvec, 627 | # sels=[ 628 | # self.z_to_csr & ( 629 | # self.csr_num == CSRAddr.MTVEC) 630 | # ], 631 | # sigs=[self.data_z_in]) 632 | 633 | def multiplex_to(self, m: Module, sig: Signal, sel: Signal, clk: str): 634 | with m.Switch(sel): 635 | with m.Case(SeqMuxSelect.MEMDATA_WR): 636 | m.d[clk] += sig.eq(self.state.memdata_wr) 637 | with m.Case(SeqMuxSelect.MEMDATA_RD): 638 | m.d[clk] += sig.eq(self.memdata_rd) 639 | with m.Case(SeqMuxSelect.MEMADDR): 640 | m.d[clk] += sig.eq(self.state.memaddr) 641 | with m.Case(SeqMuxSelect.MEMADDR_LSB_MASKED): 642 | m.d[clk] += sig.eq(self.state.memaddr & 0xFFFFFFFE) 643 | with m.Case(SeqMuxSelect.PC): 644 | m.d[clk] += sig.eq(self.state._pc) 645 | with m.Case(SeqMuxSelect.PC_PLUS_4): 646 | m.d[clk] += sig.eq(self._pc_plus_4) 647 | with m.Case(SeqMuxSelect.MTVEC): 648 | m.d[clk] += sig.eq(self.state._mtvec) 649 | with m.Case(SeqMuxSelect.MTVEC_LSR2): 650 | m.d[clk] += sig.eq(self.state._mtvec >> 2) 651 | with m.Case(SeqMuxSelect.TMP): 652 | m.d[clk] += sig.eq(self.state._tmp) 653 | with m.Case(SeqMuxSelect.IMM): 654 | m.d[clk] += sig.eq(self._imm) 655 | with m.Case(SeqMuxSelect.INSTR): 656 | m.d[clk] += sig.eq(self.state._instr) 657 | with m.Case(SeqMuxSelect.X): 658 | m.d[clk] += sig.eq(self.data_x_in) 659 | with m.Case(SeqMuxSelect.Y): 660 | m.d[clk] += sig.eq(self.data_y_in) 661 | with m.Case(SeqMuxSelect.Z): 662 | m.d[clk] += sig.eq(self.data_z_in) 663 | with m.Case(SeqMuxSelect.Z_LSL2): 664 | m.d[clk] += sig.eq(self.data_z_in << 2) 665 | with m.Case(SeqMuxSelect.CONST): 666 | m.d[clk] += sig.eq(self.decode_const(m)) 667 | 668 | def multiplex_to_pc(self, m: Module): 669 | self.multiplex_to(m, self.state._pc, self.pc_mux_select, clk="ph1") 670 | 671 | def multiplex_to_pc_chips(self, m: Module): 672 | self.multiplex_to_pc(m) 673 | # self.multiplex_to_reg(m, clk="ph1", reg=self.state._pc, 674 | # sels=[ 675 | # self._pc_plus_4_to_pc, 676 | # self._x_to_pc, 677 | # self._z_to_pc, 678 | # self._memaddr_to_pc, 679 | # self._memdata_to_pc, 680 | # self._z_30_to_pc, 681 | # ], 682 | # sigs=[ 683 | # self._pc_plus_4, 684 | # self.data_x_in, 685 | # self.data_z_in, 686 | # self.state.memaddr & 0xFFFFFFFE, 687 | # self.memdata_rd, 688 | # self.data_z_in << 2, 689 | # ]) 690 | 691 | def multiplex_to_tmp(self, m: Module): 692 | self.multiplex_to(m, self.state._tmp, self.tmp_mux_select, clk="ph2w") 693 | 694 | def multiplex_to_tmp_chips(self, m: Module): 695 | self.multiplex_to_tmp(m) 696 | # self.multiplex_to_reg(m, clk="ph2w", reg=self.state._tmp, 697 | # sels=[ 698 | # self._x_to_tmp, 699 | # self._z_to_tmp, 700 | # ], 701 | # sigs=[ 702 | # self.data_x_in, 703 | # self.data_z_in, 704 | # ]) 705 | 706 | def multiplex_to_memdata(self, m: Module): 707 | self.multiplex_to(m, self.state.memdata_wr, 708 | self.memdata_wr_mux_select, clk="ph1") 709 | 710 | def multiplex_to_memdata_chips(self, m: Module): 711 | self.multiplex_to_memdata(m) 712 | # self.multiplex_to_reg(m, clk="ph1", reg=self.state.memdata_wr, 713 | # sels=[ 714 | # self._z_to_memdata, 715 | # ], 716 | # sigs=[ 717 | # self.data_z_in, 718 | # ]) 719 | 720 | def multiplex_to_memaddr(self, m: Module): 721 | self.multiplex_to(m, self.state.memaddr, 722 | self.memaddr_mux_select, clk="ph1") 723 | 724 | def multiplex_to_memaddr_chips(self, m: Module): 725 | self.multiplex_to_memaddr(m) 726 | # self.multiplex_to_reg(m, clk="ph1", reg=self.state.memaddr, 727 | # sels=[ 728 | # self._pc_plus_4_to_memaddr, 729 | # self._x_to_memaddr, 730 | # self._z_to_memaddr, 731 | # self._memdata_to_memaddr, 732 | # self._z_30_to_memaddr, 733 | # ], 734 | # sigs=[ 735 | # self._pc_plus_4, 736 | # self.data_x_in, 737 | # self.data_z_in, 738 | # self.memdata_rd, 739 | # self.data_z_in << 2, 740 | # ]) 741 | 742 | def multiplex_to_x(self, m: Module): 743 | # with m.If(self.csr_to_x): 744 | # with m.Switch(self.csr_num): 745 | # with m.Case(CSRAddr.MTVEC): 746 | # m.d.comb += self.data_x_out.eq(self.state._mtvec) 747 | with m.If(self.x_mux_select != SeqMuxSelect.X): 748 | self.multiplex_to(m, self.data_x_out, 749 | self.x_mux_select, clk="comb") 750 | 751 | def multiplex_to_x_chips(self, m: Module): 752 | self.multiplex_to_x(m) 753 | # self.multiplex_to_bus(m, bus=self.data_x_out, 754 | # sels=[ 755 | # self._pc_to_x, 756 | # self._memdata_to_x, 757 | # self._trapcause_to_x, 758 | # self.csr_to_x & ( 759 | # self.csr_num == CSRAddr.MTVEC), 760 | # ], 761 | # sigs=[self.state._pc, self.memdata_rd, self._trapcause, 762 | # self.state._mtvec, 763 | # ]) 764 | 765 | def multiplex_to_y(self, m: Module): 766 | with m.If(self.y_mux_select != SeqMuxSelect.Y): 767 | self.multiplex_to(m, self.data_y_out, 768 | self.y_mux_select, clk="comb") 769 | 770 | def multiplex_to_y_chips(self, m: Module): 771 | self.multiplex_to_y(m) 772 | # self.multiplex_to_bus(m, bus=self.data_y_out, 773 | # sels=[ 774 | # self._imm_to_y, 775 | # self._shamt_to_y, 776 | # self._pc_to_y, 777 | # self._pc_plus_4_to_y, 778 | # self._mtvec_30_to_y, 779 | # ], 780 | # sigs=[ 781 | # self._imm, 782 | # self._shamt, 783 | # self.state._pc, 784 | # self._pc_plus_4, 785 | # self.state._mtvec >> 2, 786 | # ]) 787 | 788 | def multiplex_to_z(self, m: Module): 789 | with m.If(self.z_mux_select != SeqMuxSelect.Z): 790 | self.multiplex_to(m, self.data_z_out, 791 | self.z_mux_select, clk="comb") 792 | 793 | def multiplex_to_z_chips(self, m: Module): 794 | self.multiplex_to_z(m) 795 | # self.multiplex_to_bus(m, bus=self.data_z_out, 796 | # sels=[ 797 | # self._pc_plus_4_to_z, 798 | # self._tmp_to_z, 799 | # self._pc_to_z, 800 | # self._instr_to_z, 801 | # self._memaddr_to_z, 802 | # self._memaddr_lsb_masked_to_z, 803 | # ], 804 | # sigs=[ 805 | # self._pc_plus_4, 806 | # self.state._tmp, 807 | # self.state._pc, 808 | # self.state._instr, 809 | # self.state.memaddr, 810 | # self.state.memaddr & 0xFFFFFFFE, 811 | # ]) 812 | 813 | def multiplex_to_csr_num(self, m: Module): 814 | with m.If(self._funct12_to_csr_num): 815 | m.d.comb += self.csr_num.eq(self._funct12) 816 | with m.Elif(self._mepc_num_to_csr_num): 817 | m.d.comb += self.csr_num.eq(CSRAddr.MEPC) 818 | with m.Elif(self._mcause_to_csr_num): 819 | m.d.comb += self.csr_num.eq(CSRAddr.MCAUSE) 820 | 821 | def multiplex_to_csr_num_chips(self, m: Module): 822 | self.multiplex_to_bus(m, bus=self.csr_num, 823 | sels=[ 824 | self._funct12_to_csr_num, 825 | self._mepc_num_to_csr_num, 826 | self._mcause_to_csr_num, 827 | ], 828 | sigs=[ 829 | self._funct12, 830 | CSRAddr.MEPC, 831 | CSRAddr.MCAUSE, 832 | ]) 833 | 834 | def multiplex_to_reg_nums(self, m: Module): 835 | with m.Switch(self._x_reg_select): 836 | with m.Case(InstrReg.ZERO): 837 | m.d.comb += self.x_reg.eq(0) 838 | with m.Case(InstrReg.RS1): 839 | m.d.comb += self.x_reg.eq(self._rs1) 840 | with m.Case(InstrReg.RS2): 841 | m.d.comb += self.x_reg.eq(self._rs2) 842 | with m.Case(InstrReg.RD): 843 | m.d.comb += self.x_reg.eq(self._rd) 844 | with m.Switch(self._y_reg_select): 845 | with m.Case(InstrReg.ZERO): 846 | m.d.comb += self.y_reg.eq(0) 847 | with m.Case(InstrReg.RS1): 848 | m.d.comb += self.y_reg.eq(self._rs1) 849 | with m.Case(InstrReg.RS2): 850 | m.d.comb += self.y_reg.eq(self._rs2) 851 | with m.Case(InstrReg.RD): 852 | m.d.comb += self.y_reg.eq(self._rd) 853 | with m.Switch(self._z_reg_select): 854 | with m.Case(InstrReg.ZERO): 855 | m.d.comb += self.z_reg.eq(0) 856 | with m.Case(InstrReg.RS1): 857 | m.d.comb += self.z_reg.eq(self._rs1) 858 | with m.Case(InstrReg.RS2): 859 | m.d.comb += self.z_reg.eq(self._rs2) 860 | with m.Case(InstrReg.RD): 861 | m.d.comb += self.z_reg.eq(self._rd) 862 | 863 | def multiplex_to_reg_nums_chips(self, m: Module): 864 | self.multiplex_to_bus(m, bus=self.x_reg, 865 | sels=[ 866 | self._x_reg_select == InstrReg.ZERO, 867 | self._x_reg_select == InstrReg.RS1, 868 | self._x_reg_select == InstrReg.RS2, 869 | self._x_reg_select == InstrReg.RD, 870 | ], 871 | sigs=[ 872 | 0, 873 | self._rs1, 874 | self._rs2, 875 | self._rd, 876 | ]) 877 | self.multiplex_to_bus(m, bus=self.y_reg, 878 | sels=[ 879 | self._y_reg_select == InstrReg.ZERO, 880 | self._y_reg_select == InstrReg.RS1, 881 | self._y_reg_select == InstrReg.RS2, 882 | self._y_reg_select == InstrReg.RD, 883 | ], 884 | sigs=[ 885 | 0, 886 | self._rs1, 887 | self._rs2, 888 | self._rd, 889 | ]) 890 | self.multiplex_to_bus(m, bus=self.z_reg, 891 | sels=[ 892 | self._z_reg_select == InstrReg.ZERO, 893 | self._z_reg_select == InstrReg.RS1, 894 | self._z_reg_select == InstrReg.RS2, 895 | self._z_reg_select == InstrReg.RD, 896 | ], 897 | sigs=[ 898 | 0, 899 | self._rs1, 900 | self._rs2, 901 | self._rd, 902 | ]) 903 | 904 | def decode_imm(self, m: Module): 905 | """Decodes the immediate value out of the instruction.""" 906 | with m.Switch(self._imm_format): 907 | # Format I instructions. Surprisingly, SLTIU (Set if Less Than 908 | # Immediate Unsigned) actually does sign-extend the immediate 909 | # value, and then compare as if the sign-extended immediate value 910 | # were unsigned! 911 | with m.Case(OpcodeFormat.I): 912 | tmp = Signal(signed(12)) 913 | m.d.comb += tmp.eq(self.state._instr[20:]) 914 | m.d.comb += self._imm.eq(tmp) 915 | 916 | # Format S instructions: 917 | with m.Case(OpcodeFormat.S): 918 | tmp = Signal(signed(12)) 919 | m.d.comb += tmp[0:5].eq(self.state._instr[7:12]) 920 | m.d.comb += tmp[5:].eq(self.state._instr[25:]) 921 | m.d.comb += self._imm.eq(tmp) 922 | 923 | # Format R instructions: 924 | with m.Case(OpcodeFormat.R): 925 | m.d.comb += self._imm.eq(0) 926 | 927 | # Format U instructions: 928 | with m.Case(OpcodeFormat.U): 929 | m.d.comb += self._imm.eq(0) 930 | m.d.comb += self._imm[12:].eq(self.state._instr[12:]) 931 | 932 | # Format B instructions: 933 | with m.Case(OpcodeFormat.B): 934 | tmp = Signal(signed(13)) 935 | m.d.comb += [ 936 | tmp[12].eq(self.state._instr[31]), 937 | tmp[11].eq(self.state._instr[7]), 938 | tmp[5:11].eq(self.state._instr[25:31]), 939 | tmp[1:5].eq(self.state._instr[8:12]), 940 | tmp[0].eq(0), 941 | self._imm.eq(tmp), 942 | ] 943 | 944 | # Format J instructions: 945 | with m.Case(OpcodeFormat.J): 946 | tmp = Signal(signed(21)) 947 | m.d.comb += [ 948 | tmp[20].eq(self.state._instr[31]), 949 | tmp[12:20].eq(self.state._instr[12:20]), 950 | tmp[11].eq(self.state._instr[20]), 951 | tmp[1:11].eq(self.state._instr[21:31]), 952 | tmp[0].eq(0), 953 | self._imm.eq(tmp), 954 | ] 955 | 956 | with m.Case(OpcodeFormat.SYS): 957 | m.d.comb += [ 958 | self._imm[0:5].eq(self.state._instr[15:]), 959 | self._imm[5:].eq(0), 960 | ] 961 | 962 | def decode_imm_chips(self, m: Module): 963 | mux = IC_mux32(N=6, faster=True) 964 | gal = IC_GAL_imm_format_decoder() 965 | 966 | m.submodules += mux 967 | m.submodules += gal 968 | 969 | m.d.comb += gal.opcode.eq(self._opcode) 970 | m.d.comb += mux.n_sel[0].eq(gal.i_n_oe) 971 | m.d.comb += mux.n_sel[1].eq(gal.s_n_oe) 972 | m.d.comb += mux.n_sel[2].eq(gal.u_n_oe) 973 | m.d.comb += mux.n_sel[3].eq(gal.b_n_oe) 974 | m.d.comb += mux.n_sel[4].eq(gal.j_n_oe) 975 | m.d.comb += mux.n_sel[5].eq(gal.sys_n_oe) 976 | 977 | instr = self.state._instr 978 | 979 | # Format I 980 | m.d.comb += [ 981 | mux.a[0][0:12].eq(instr[20:]), 982 | mux.a[0][12:].eq(Repl(instr[31], 32)), # sext 983 | ] 984 | 985 | # Format S 986 | m.d.comb += [ 987 | mux.a[1][0:5].eq(instr[7:]), 988 | mux.a[1][5:11].eq(instr[25:]), 989 | mux.a[1][11:].eq(Repl(instr[31], 32)), # sext 990 | ] 991 | 992 | # Format U 993 | m.d.comb += [ 994 | mux.a[2][0:12].eq(0), 995 | mux.a[2][12:].eq(instr[12:]), 996 | ] 997 | 998 | # Format B 999 | m.d.comb += [ 1000 | mux.a[3][0].eq(0), 1001 | mux.a[3][1:5].eq(instr[8:]), 1002 | mux.a[3][5:11].eq(instr[25:]), 1003 | mux.a[3][11].eq(instr[7]), 1004 | mux.a[3][12:].eq(Repl(instr[31], 32)), # sext 1005 | ] 1006 | 1007 | # Format J 1008 | m.d.comb += [ 1009 | mux.a[4][0].eq(0), 1010 | mux.a[4][1:11].eq(instr[21:]), 1011 | mux.a[4][11].eq(instr[20]), 1012 | mux.a[4][12:20].eq(instr[12:]), 1013 | mux.a[4][20:].eq(Repl(instr[31], 32)), # sext 1014 | ] 1015 | 1016 | # Format SYS 1017 | m.d.comb += [ 1018 | mux.a[5][0:5].eq(instr[15:]), 1019 | mux.a[5][5:].eq(0), 1020 | ] 1021 | 1022 | m.d.comb += self._imm.eq(mux.y) 1023 | --------------------------------------------------------------------------------