code wiki / _hdl_build / rv64im_min_clint.nx

rv64im_min_clint.nx source

↩ module page · 240 lines · 9256 B

1// rv64im_min_clint.nx -- Core-Local Interruptor (CLINT) MMIO device. 2// 3// Drives the kernel's preemptive timer. Two memory-mapped registers: 4// 5// MTIME (0x0200BFF8, 64-bit RO from CPU side) 6// Monotonic counter; advances 1 per fabric-clock tick. Kernel 7// reads it on every preempt-tick handler to measure latency. 8// 9// MTIMECMP[0] (0x02004000, 64-bit RW) 10// Per-hart compare register. When MTIME >= MTIMECMP[0], the 11// CLINT asserts MTIP on the hart; the CPU takes a machine-timer 12// interrupt (mcause = (1 << 63) | 7) if mstatus.MIE and mie.MTIE 13// are both set. Kernel rearms by writing MTIMECMP[0] = MTIME + 14// TICK_CYCLES. 15// 16// Tier A FPGA: single-hart only, so MTIMECMP[N] for N>0 is absent. 17// The full RISC-V CLINT spec also defines MSIP (software interrupt) 18// at 0x02000000, but the kernel never uses it -- omitted here per 19// the spec's "must implement enough that kernel boots" cardinal. 20// 21// Memory map (matches QEMU virt machine; kernel hardcodes these): 22// 0x02000000 MSIP[0] -- NOT IMPLEMENTED in Tier A (4 bytes) 23// 0x02004000 MTIMECMP[0] -- 8 bytes 24// 0x02004008..0x0200BFF7 sparse; reads/writes return 0 25// 0x0200BFF8 MTIME -- 8 bytes 26// 0x0200C000 end of region 27// 28// Status: SEED. 2026-05-26. Register storage + access + interrupt 29// generation. Synth target: 2 64-bit flop registers + comparator + 30// memory-region address decoder. Tier A FPGA: trivial. 31 32import "nx_syscalls.nx" 33import "nishi_hdl_primitives.nx" 34 35// ===== MMIO addresses ================================================= 36const NX_CLINT_BASE: i64 = 0x02000000 37const NX_CLINT_MSIP_BASE: i64 = 0x02000000 38const NX_CLINT_MTIMECMP_BASE: i64 = 0x02004000 39const NX_CLINT_MTIME_ADDR: i64 = 0x0200BFF8 40const NX_CLINT_END: i64 = 0x0200C000 41 42// ===== Verdicts ================================================= 43const NX_CLINT_OK: i64 = 0 44const NX_CLINT_ADDR_OUT_OF_RANGE: i64 = 1 45const NX_CLINT_BAD_ALIGNMENT: i64 = 2 // 8-byte access required 46 47// ===== Storage ================================================= 48// 49// V1: caller allocates a 3-i64 backing buffer: 50// slot 0: MTIME (64-bit monotonic counter) 51// slot 1: MTIMECMP[0] (64-bit compare; init to INT_MAX so no 52// spurious interrupt before kernel programs it) 53// slot 2: MTIP latch (1-bit pending state; output to CPU's mip.MTIP) 54 55const NX_CLINT_SLOT_MTIME: i64 = 0 56const NX_CLINT_SLOT_MTIMECMP: i64 = 1 57const NX_CLINT_SLOT_MTIP: i64 = 2 58const NX_CLINT_SLOT_N: i64 = 3 59 60const NX_CLINT_INIT_MTIMECMP: i64 = 0x7FFFFFFFFFFFFFFF // INT64_MAX 61 62struct NxClint { 63 storage: *i64 // 3 i64s 64 valid: i64 65} 66 67func nx_clint_init(c: *NxClint, storage: *i64) -> i64 { 68 if (c as i64) == 0 { return 0 - NX_HDL_BAD_KIND } 69 if (storage as i64) == 0 { return 0 - NX_HDL_BAD_KIND } 70 c.storage = storage 71 c.valid = 1 72 storage[NX_CLINT_SLOT_MTIME] = 0 73 storage[NX_CLINT_SLOT_MTIMECMP] = NX_CLINT_INIT_MTIMECMP 74 storage[NX_CLINT_SLOT_MTIP] = 0 75 return NX_CLINT_OK 76} 77 78// ===== Range check ================================================= 79func nx_clint_addr_in_range(addr: i64) -> i64 { 80 if addr < NX_CLINT_BASE { return 0 } 81 if addr >= NX_CLINT_END { return 0 } 82 return 1 83} 84 85// ===== MMIO read ================================================= 86// 87// 64-bit aligned reads only. Returns the value via the out-param 88// + verdict via the return. Reads to unmapped offsets return 0 89// (matches QEMU virt behavior; kernel never reads them). 90 91func nx_clint_read64(c: *NxClint, addr: i64, value_out: *i64) -> i64 { 92 if c.valid != 1 { return 0 - NX_CLINT_ADDR_OUT_OF_RANGE } 93 if (value_out as i64) == 0 { return 0 - NX_HDL_BAD_KIND } 94 if nx_clint_addr_in_range(addr) != 1 { return 0 - NX_CLINT_ADDR_OUT_OF_RANGE } 95 if (addr & 0x7) != 0 { return 0 - NX_CLINT_BAD_ALIGNMENT } 96 97 if addr == NX_CLINT_MTIME_ADDR { 98 value_out[0] = c.storage[NX_CLINT_SLOT_MTIME] 99 return NX_CLINT_OK 100 } 101 if addr == NX_CLINT_MTIMECMP_BASE { 102 value_out[0] = c.storage[NX_CLINT_SLOT_MTIMECMP] 103 return NX_CLINT_OK 104 } 105 // Other addresses in the CLINT region: return 0. Includes MSIP 106 // (not implemented; kernel never reads it). 107 value_out[0] = 0 108 return NX_CLINT_OK 109} 110 111// ===== MMIO write ================================================= 112 113func nx_clint_write64(c: *NxClint, addr: i64, value: i64) -> i64 { 114 if c.valid != 1 { return 0 - NX_CLINT_ADDR_OUT_OF_RANGE } 115 if nx_clint_addr_in_range(addr) != 1 { return 0 - NX_CLINT_ADDR_OUT_OF_RANGE } 116 if (addr & 0x7) != 0 { return 0 - NX_CLINT_BAD_ALIGNMENT } 117 118 if addr == NX_CLINT_MTIMECMP_BASE { 119 c.storage[NX_CLINT_SLOT_MTIMECMP] = value 120 // Rearming may clear MTIP (if new compare > current MTIME) 121 // or set it (if new compare <= current MTIME). Recompute. 122 nx_clint_update_mtip(c) 123 return NX_CLINT_OK 124 } 125 if addr == NX_CLINT_MTIME_ADDR { 126 // RV64 spec: MTIME is writable in M-mode for diagnostic 127 // purposes. Kernel doesn't use this but we honor the spec. 128 c.storage[NX_CLINT_SLOT_MTIME] = value 129 nx_clint_update_mtip(c) 130 return NX_CLINT_OK 131 } 132 // Other addresses: silently dropped. 133 return NX_CLINT_OK 134} 135 136// ===== Pending-bit derivation ================================================= 137// 138// MTIP = (MTIME >= MTIMECMP). Refreshed: 139// - every clock tick (via nx_clint_tick) 140// - after MTIMECMP write (via nx_clint_write64) 141// - after MTIME write (via nx_clint_write64) 142// CPU reads via nx_clint_mtip_get(). 143 144func nx_clint_update_mtip(c: *NxClint) -> i64 { 145 if c.valid != 1 { return 0 - NX_CLINT_ADDR_OUT_OF_RANGE } 146 let now: i64 = c.storage[NX_CLINT_SLOT_MTIME] 147 let cmp: i64 = c.storage[NX_CLINT_SLOT_MTIMECMP] 148 // Unsigned compare via top-bit-flip trick (NishiLang < is signed). 149 let bit63: i64 = 0 - 9223372036854775808 150 let now_f: i64 = now ^ bit63 151 let cmp_f: i64 = cmp ^ bit63 152 if now_f < cmp_f { 153 c.storage[NX_CLINT_SLOT_MTIP] = 0 154 } 155 if now_f >= cmp_f { 156 c.storage[NX_CLINT_SLOT_MTIP] = 1 157 } 158 return NX_CLINT_OK 159} 160 161func nx_clint_mtip_get(c: *NxClint) -> i64 { 162 if c.valid != 1 { return 0 } 163 return c.storage[NX_CLINT_SLOT_MTIP] 164} 165 166// ===== Clock tick (advances MTIME) ================================================= 167// 168// Driven by the simulator each cycle; in silicon this is a free- 169// running 64-bit counter fed from the fabric clock. Tier A FPGA: 170// MTIME advances at 10 MHz (kernel's TICK_CYCLES=1000 yields ~10 kHz 171// preemption). 172 173func nx_clint_tick(c: *NxClint) -> i64 { 174 if c.valid != 1 { return 0 - NX_CLINT_ADDR_OUT_OF_RANGE } 175 c.storage[NX_CLINT_SLOT_MTIME] = c.storage[NX_CLINT_SLOT_MTIME] + 1 176 return nx_clint_update_mtip(c) 177} 178 179// ===== HDL-graph builder ================================================= 180 181const NX_CLINT_WIDTH_ADDR: i64 = 32 // physical address space 182const NX_CLINT_WIDTH_DATA: i64 = 64 183const NX_CLINT_WIDTH_VERDICT: i64 = 4 184const NX_CLINT_WIDTH_MTIP: i64 = 1 185 186struct NxClintPorts { 187 clk: i64 // fabric clock 188 reset: i64 // sync reset 189 rd_addr: i64 // input wire, 32-bit 190 rd_en: i64 // input wire, 1-bit 191 rd_data: i64 // output wire, 64-bit 192 rd_verdict: i64 // output wire, 4-bit 193 wr_addr: i64 // input wire, 32-bit 194 wr_data: i64 // input wire, 64-bit 195 wr_en: i64 // input wire, 1-bit 196 wr_verdict: i64 // output wire, 4-bit 197 mtip_out: i64 // output wire, 1-bit (feeds CPU's mip.MTIP) 198} 199 200func nx_clint_build(m: *NxHdlModule, ports: *NxClintPorts) -> i64 { 201 let p_clk: i64 = nx_hdl_clock(m) 202 if p_clk < 0 { return p_clk } 203 let p_rst: i64 = nx_hdl_reset(m) 204 if p_rst < 0 { return p_rst } 205 let p_rd_addr: i64 = nx_hdl_input(m, NX_CLINT_WIDTH_ADDR) 206 if p_rd_addr < 0 { return p_rd_addr } 207 let p_rd_en: i64 = nx_hdl_input(m, 1) 208 if p_rd_en < 0 { return p_rd_en } 209 let p_rd_data: i64 = nx_hdl_output(m, NX_CLINT_WIDTH_DATA) 210 if p_rd_data < 0 { return p_rd_data } 211 let p_rd_verdict: i64 = nx_hdl_output(m, NX_CLINT_WIDTH_VERDICT) 212 if p_rd_verdict < 0 { return p_rd_verdict } 213 let p_wr_addr: i64 = nx_hdl_input(m, NX_CLINT_WIDTH_ADDR) 214 if p_wr_addr < 0 { return p_wr_addr } 215 let p_wr_data: i64 = nx_hdl_input(m, NX_CLINT_WIDTH_DATA) 216 if p_wr_data < 0 { return p_wr_data } 217 let p_wr_en: i64 = nx_hdl_input(m, 1) 218 if p_wr_en < 0 { return p_wr_en } 219 let p_wr_verdict: i64 = nx_hdl_output(m, NX_CLINT_WIDTH_VERDICT) 220 if p_wr_verdict < 0 { return p_wr_verdict } 221 let p_mtip: i64 = nx_hdl_output(m, NX_CLINT_WIDTH_MTIP) 222 if p_mtip < 0 { return p_mtip } 223 224 ports.clk = p_clk 225 ports.reset = p_rst 226 ports.rd_addr = p_rd_addr 227 ports.rd_en = p_rd_en 228 ports.rd_data = p_rd_data 229 ports.rd_verdict = p_rd_verdict 230 ports.wr_addr = p_wr_addr 231 ports.wr_data = p_wr_data 232 ports.wr_en = p_wr_en 233 ports.wr_verdict = p_wr_verdict 234 ports.mtip_out = p_mtip 235 236 // Synth target: 2 64-bit flop registers (MTIME, MTIMECMP) + 64-bit 237 // unsigned comparator (~256 gates) + address decoder. Tier A 238 // FPGA fabric: trivial. 239 return NX_HDL_OK 240}