code wiki / _hdl_build / rv64im_min_regfile.nx

rv64im_min_regfile.nx source

↩ module page · 140 lines · 5443 B

1// rv64im_min_regfile.nx -- RV64IM-min general-purpose register file. 2// 3// 32 entries × 64 bits. Two combinational read ports (rs1, rs2) 4// and one sequential write port (rd on clock-edge if write_en). 5// x0 is hardwired to 0: reads return 0 regardless of the write 6// history; writes to x0 are silently dropped. 7// 8// This matches every RV64 in-order design from the SiFive E2 series 9// through the Rocket / BOOM cores. Out-of-order designs would 10// rename behind this surface; Tier A FPGA bring-up is single-issue 11// in-order so the rename layer is not needed. 12// 13// Status: SEED. 2026-05-26. Storage + access semantics; the HDL- 14// graph builder declares the module shape so nishi-sim / nishi-synth 15// can map storage onto BRAM (Tier A FPGA) or SRAM cells (Tier B+). 16 17import "nx_syscalls.nx" 18import "nishi_hdl_primitives.nx" 19 20// ===== Sizing ================================================= 21const NX_RV64IM_RF_N_REGS: i64 = 32 22const NX_RV64IM_RF_WIDTH: i64 = 64 23const NX_RV64IM_RF_IDX_BITS: i64 = 5 // log2(32) 24 25// ===== Storage ================================================= 26// 27// V1: caller allocates a 32-i64 backing buffer + passes it to 28// nx_rv64im_rf_init. Substrate-side use (simulator) loads/stores 29// against this buffer; silicon-side emit (nishi-synth, future) 30// turns it into a BRAM block or distributed-RAM. 31 32struct NxRv64imRegfile { 33 storage: *i64 // 32 i64s, allocated by caller 34 valid: i64 // 1 once nx_rv64im_rf_init ran successfully 35} 36 37func nx_rv64im_rf_init(rf: *NxRv64imRegfile, storage: *i64) -> i64 { 38 if (rf as i64) == 0 { return 0 - NX_HDL_BAD_KIND } 39 if (storage as i64) == 0 { return 0 - NX_HDL_BAD_KIND } 40 rf.storage = storage 41 rf.valid = 1 42 // x0 hardwired-zero invariant: stamp slot 0 so any read returns 0 43 // even before any write. 44 storage[0] = 0 45 var i: i64 = 1 46 while i < NX_RV64IM_RF_N_REGS { 47 storage[i] = 0 48 i = i + 1 49 } 50 return NX_HDL_OK 51} 52 53// ===== Combinational reads ================================================= 54// 55// Per RV64 spec: x0 always reads 0; any other register returns 56// whatever it was last written. No port-conflict semantics because 57// reads are not allocated to a fixed port (the synthesis layer 58// handles multi-port BRAM mapping). 59 60func nx_rv64im_rf_read(rf: *NxRv64imRegfile, idx: i64) -> i64 { 61 if rf.valid != 1 { return 0 } 62 if idx == 0 { return 0 } // x0 hardwired zero 63 if idx < 0 { return 0 } 64 if idx >= NX_RV64IM_RF_N_REGS { return 0 } 65 return rf.storage[idx] 66} 67 68// ===== Sequential write ================================================= 69// 70// "Sequential" in the silicon sense: the write commits on a clock 71// edge. The simulator path commits immediately; the synth path 72// emits a clock-edge sensitive flop write. Writes to x0 are 73// silently dropped per RV64 spec. 74 75func nx_rv64im_rf_write(rf: *NxRv64imRegfile, idx: i64, value: i64) -> i64 { 76 if rf.valid != 1 { return 0 - NX_HDL_BAD_KIND } 77 if idx == 0 { return NX_HDL_OK } // drop write to x0; no error 78 if idx < 0 { return 0 - NX_HDL_BAD_KIND } 79 if idx >= NX_RV64IM_RF_N_REGS { return 0 - NX_HDL_BAD_KIND } 80 rf.storage[idx] = value 81 return NX_HDL_OK 82} 83 84// ===== HDL-graph builder ================================================= 85// 86// Declares the regfile module's port shape. Two combinational read 87// ports (rs1, rs2 each producing a 64-bit data wire) and one 88// clock-edge-sensitive write port (rd index + value + write enable). 89 90const NX_RV64IM_RF_WIDTH_WR_EN: i64 = 1 91 92struct NxRv64imRegfilePorts { 93 clk: i64 // clock input 94 reset: i64 // reset input (sync; clears all regs to 0) 95 rs1_idx: i64 // input wire, 5-bit 96 rs2_idx: i64 // input wire, 5-bit 97 rd_idx: i64 // input wire, 5-bit 98 rd_value: i64 // input wire, 64-bit 99 write_en: i64 // input wire, 1-bit 100 rs1_data: i64 // output wire, 64-bit 101 rs2_data: i64 // output wire, 64-bit 102} 103 104func nx_rv64im_rf_build(m: *NxHdlModule, ports: *NxRv64imRegfilePorts) -> i64 { 105 let p_clk: i64 = nx_hdl_clock(m) 106 if p_clk < 0 { return p_clk } 107 let p_rst: i64 = nx_hdl_reset(m) 108 if p_rst < 0 { return p_rst } 109 110 let p_rs1_idx: i64 = nx_hdl_input(m, NX_RV64IM_RF_IDX_BITS) 111 if p_rs1_idx < 0 { return p_rs1_idx } 112 let p_rs2_idx: i64 = nx_hdl_input(m, NX_RV64IM_RF_IDX_BITS) 113 if p_rs2_idx < 0 { return p_rs2_idx } 114 let p_rd_idx: i64 = nx_hdl_input(m, NX_RV64IM_RF_IDX_BITS) 115 if p_rd_idx < 0 { return p_rd_idx } 116 let p_rd_value: i64 = nx_hdl_input(m, NX_RV64IM_RF_WIDTH) 117 if p_rd_value < 0 { return p_rd_value } 118 let p_write_en: i64 = nx_hdl_input(m, NX_RV64IM_RF_WIDTH_WR_EN) 119 if p_write_en < 0 { return p_write_en } 120 121 let p_rs1_data: i64 = nx_hdl_output(m, NX_RV64IM_RF_WIDTH) 122 if p_rs1_data < 0 { return p_rs1_data } 123 let p_rs2_data: i64 = nx_hdl_output(m, NX_RV64IM_RF_WIDTH) 124 if p_rs2_data < 0 { return p_rs2_data } 125 126 ports.clk = p_clk 127 ports.reset = p_rst 128 ports.rs1_idx = p_rs1_idx 129 ports.rs2_idx = p_rs2_idx 130 ports.rd_idx = p_rd_idx 131 ports.rd_value = p_rd_value 132 ports.write_en = p_write_en 133 ports.rs1_data = p_rs1_data 134 ports.rs2_data = p_rs2_data 135 136 // Synth target: Lattice ECP5 BRAM block (1024x64, dual-port). 137 // Tier A FPGA fits in a single block. Tier B+ MPW: SRAM cells 138 // with self-timed precharge per the standard-cell library. 139 return NX_HDL_OK 140}