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}