code wiki / _hdl_build / nx_simtcore.nx
nx_simtcore.nx
buildroot/runtime/_hdl_build/nx_simtcore.nx
about
nx_simtcore.nx -- the SOVEREIGN SIMT GPGPU EXECUTION CORE.
This is the missing organ named by our OWN ruler: nx_sovgpu_census grades the cell
"open GPU core on OUR FPGA (Vortex/Nyuzi class)" as GAP. This file closes it. It is a
SIMT (single-instruction multiple-thread) execution core built from the first bit in
NishiLang on top of the sovereign RV64IM decoder + ALU already in nishi-silicon/hdl.
WHAT SIMT ADDS OVER THE SCALAR CORE (rv64im_min_sim):
scalar core : 1 PC, 1 register file, 1 instruction -> 1 result
SIMT core : NW warps, each with 1 PC and a THREAD MASK; each warp owns NT threads;
each thread owns its own 32-entry register file. One fetched instruction
executes in lockstep across every ACTIVE thread of the selected warp.
A masked-off thread commits NOTHING -- no register write, no memory write. That single
rule is what makes control-flow divergence expressible on a machine with one PC.
===== BENCHMARK ORACLE (external, measured, never copied) =====
Vortex (Georgia Tech, MICRO'54 2021, arXiv 2110.10857) is the reference open-source
RISC-V GPGPU. We use its PUBLISHED ARCHITECTURE AND MEASUREMENTS as a measuring stick.
We copy no Vortex source, RTL, or asset. Verified facts driving this design:
- Vortex extends RISC-V with SIX instructions: wspawn, tmc, split, join, bar, tex.
"by adding only six new instructions to the standard RISC-V ISA" (MICRO'21 sec 1).
- Divergence uses an IPDOM (immediate post-dominator) stack, TWO pushes per split.
- Thread mask: "If the bit in the thread mask for a specific thread is zero, no
modifications would be made to that thread's register file and no changes to the
cache would be made based on that thread." (arXiv 2002.12151).
- Scheduler picks ONE wavefront per cycle from a visible mask; stalled/barrier masks
remove warps from contention (MICRO'21 sec 4.1.1).
- Cache banks are kept equal to the threads-per-warp count (CARRV 2019).
- Baseline config 4W-4T; configs evaluated 4W-4T, 2W-8T, 8W-2T, 4W-8T, 8W-4T.
===== SOVEREIGN ENCODING, NOT VORTEX'S =====
RISC-V reserves four opcodes for user extensions: 0x0B, 0x2B, 0x5B, 0x7B. Vortex
documents 0x5B for its `tex`. We take custom-0 = 0x0B so there is ZERO encoding
collision with Vortex: the CAPABILITY set is benchmarked, the BITS are our own.
NEVER-BRICK (cardinal 26): this core is a pure behavioural model over caller-owned
memory. It writes no firmware, no device, no persistent hardware state. It is
deterministic and bounded by construction -- every run is replayable bit-for-bit.
Status: SEED. 2026-07-31. license_tier: ORIGINAL expect_exit: 0
dependencies 5 imports · 1 importers
imports: nx_syscalls.nxnishi_hdl_primitives.nxrv64im_min_decoder.nxrv64im_min_alu.nxrv64im_min_sim.nx
imported by: nx_simtcore_gate.nx
structs
| 228 | struct NxSimtCore |
| 1468 | struct NxSimtCluster |
consts
| 52 | const NX_SIMT_MAX_WARPS: i64 = 16 |
| 53 | const NX_SIMT_MAX_THREADS: i64 = 16 |
| 54 | const NX_SIMT_NREGS: i64 = 32 // RISC-V architectural GPRs, per thread |
| 55 | const NX_SIMT_IPDOM_DEPTH: i64 = 32 // divergence nesting depth, per warp |
| 56 | const NX_SIMT_NBARRIERS: i64 = 8 // hardware barrier slots |
| 60 | const NX_SIMT_BAR_GLOBAL: i64 = 128 |
| 79 | const NX_SIMT_LAT_ALU: i64 = 1 // issue-to-issue for an ALU op |
| 108 | const NX_SIMT_LAT_MULDIV: i64 = 8 |
| 109 | const NX_SIMT_LAT_MEM_SHORT: i64 = 24 // Vortex "sh" -- cache-resident / short latency |
| 110 | const NX_SIMT_LAT_MEM_LONG: i64 = 200 // Vortex "lg" -- DRAM / long latency |
| 111 | const NX_SIMT_CHAN_LOW: i64 = 2 // Vortex "2c" -- 2-channel memory |
| 112 | const NX_SIMT_CHAN_HIGH: i64 = 8 // Vortex "8c" -- 8-channel memory |
| 113 | const NX_SIMT_MAX_CHANNELS: i64 = 16 |
| 121 | const NX_SIMT_BURST_CYCLES: i64 = 4 |
| 136 | const NX_SIMT_CACHE_LINE_B: i64 = 64 |
| 137 | const NX_SIMT_CACHE_SETS: i64 = 128 |
| 138 | const NX_SIMT_CACHE_WAYS: i64 = 2 |
| 139 | const NX_SIMT_LAT_CACHE_HIT: i64 = 4 // cycles when every touched line is resident |
| 145 | const NX_SIMT_OPCODE: i64 = 0x0B |
| 147 | const NX_SIMT_F3_TMC: i64 = 0 // tmc rs1 -- thread mask control |
| 148 | const NX_SIMT_F3_WSPAWN: i64 = 1 // wspawn rs1, rs2 -- activate rs1 warps at PC rs2 |
| 149 | const NX_SIMT_F3_SPLIT: i64 = 2 // split rs1 -- control-flow divergence |
| 150 | const NX_SIMT_F3_JOIN: i64 = 3 // join -- control-flow reconvergence |
| 151 | const NX_SIMT_F3_BAR: i64 = 4 // bar rs1, rs2 -- barrier id rs1, warp count rs2 |
| 152 | const NX_SIMT_F3_TID: i64 = 5 // tid rd -- rd = this thread's lane id |
| 153 | const NX_SIMT_F3_WID: i64 = 6 // wid rd -- rd = this warp's id |
| 154 | const NX_SIMT_F3_NTID: i64 = 7 // ntid rd -- rd = threads per warp |
| 169 | const NX_SIMT_OPCODE_W: i64 = 0x2B |
| 171 | const NX_SIMT_W_SHFL: i64 = 0 // shfl rd, rs1, rs2 -- rd[t] = rs1[ rs2[t] ] |
| 172 | const NX_SIMT_W_SHFL_UP: i64 = 1 // shfl.up rd, rs1, rs2 -- rd[t] = rs1[t - rs2[t]] |
| 173 | const NX_SIMT_W_SHFL_DOWN:i64 = 2 // shfl.down rd, rs1, rs2 -- rd[t] = rs1[t + rs2[t]] |
| 174 | const NX_SIMT_W_SHFL_XOR: i64 = 3 // shfl.xor rd, rs1, rs2 -- rd[t] = rs1[t ^ rs2[t]] |
| 175 | const NX_SIMT_W_BALLOT: i64 = 4 // ballot rd, rs1 -- rd = mask of lanes with rs1 != 0 |
| 176 | const NX_SIMT_W_ALL: i64 = 5 // all rd, rs1 -- rd = 1 if every active lane nonzero |
| 177 | const NX_SIMT_W_ANY: i64 = 6 // any rd, rs1 -- rd = 1 if any active lane nonzero |
| 178 | const NX_SIMT_W_ACTIVEMASK: i64 = 7 // activemask rd -- rd = the current thread mask |
| 206 | const NX_SIMT_OPCODE_TEX: i64 = 0x7B |
| 207 | const NX_SIMT_TEX_FRACBITS: i64 = 8 // 24.8 fixed point |
| 208 | const NX_SIMT_TEX_FRACMASK: i64 = 255 |
| 209 | const NX_SIMT_LAT_TEX_FILTER: i64 = 2 // their sampler's two-cycle bilinear interpolation |
| 213 | const NX_SIMT_OK: i64 = 0 |
| 214 | const NX_SIMT_E_ARG: i64 = 1 // bad argument / unconfigured core |
| 215 | const NX_SIMT_E_IPDOM: i64 = 2 // IPDOM stack overflow or join-without-split |
| 216 | const NX_SIMT_E_ILLEGAL: i64 = 3 // illegal instruction |
| 217 | const NX_SIMT_E_BOUNDS: i64 = 4 // memory access out of the modelled aperture |
| 1465 | const NX_SIMT_MAX_CORES: i64 = 16 |
| 1466 | const NX_SIMT_CORE_STRIDE: i64 = 1024 // bytes reserved per core; struct is well under this |
functions
| 323 | func nx_simt_full_mask(nt: i64) -> i64 |
| 330 | func nx_simt_popcount(m: i64) -> i64 |
| 342 | func nx_simt_lowest_lane(tmask: i64) -> i64 called by 1: nx_simt_exec_warp |
| 356 | func nx_simt_alloc(c: *NxSimtCore) -> i64 |
| 385 | func nx_simt_init(c: *NxSimtCore, nw: i64, nt: i64, |
| 473 | func nx_simt_rf_read(c: *NxSimtCore, w: i64, t: i64, r: i64) -> i64 |
| 480 | func nx_simt_rf_write(c: *NxSimtCore, w: i64, t: i64, r: i64, v: i64) -> i64 called by 1: nx_simt_exec_warp |
| 492 | func nx_simt_in_range(c: *NxSimtCore, addr: i64, width: i64) -> i64 |
| 499 | func nx_simt_load32(c: *NxSimtCore, addr: i64) -> i64 |
| 509 | func nx_simt_store32(c: *NxSimtCore, addr: i64, v: i64) -> i64 |
| 519 | func nx_simt_load64(c: *NxSimtCore, addr: i64) -> i64 |
| 525 | func nx_simt_store64(c: *NxSimtCore, addr: i64, v: i64) -> i64 |
| 545 | func nx_simt_bank_cost(c: *NxSimtCore, addrs: *i64, active: i64, bank_hits: *i64) -> i64 called by 1: nx_simt_mem_access |
| 573 | func nx_simt_set_memory(c: *NxSimtCore, lat: i64, nchan: i64) -> i64 |
| 587 | func nx_simt_set_cache(c: *NxSimtCore, on: i64) -> i64 called by 1: run_config_full |
| 601 | func nx_simt_cache_probe(c: *NxSimtCore, line: i64) -> i64 called by 1: nx_simt_mem_access |
| 635 | func nx_simt_coalesce(c: *NxSimtCore, addrs: *i64, active: i64) -> i64 |
| 663 | func nx_simt_mem_issue(c: *NxSimtCore, w: i64, cost: i64, nmiss: i64) -> i64 called by 1: nx_simt_mem_access |
| 686 | func nx_simt_mem_access(c: *NxSimtCore, w: i64, addrs: *i64, bank_hits: *i64) -> i64 called by 1: nx_simt_exec_warp calls 4: nx_simt_bank_costnx_simt_mem_issuenx_simt_coalescenx_simt_cache_probe |
| 709 | func nx_simt_set_texture(c: *NxSimtCore, base: i64, w: i64, h: i64) -> i64 called by 1: run_config_full |
| 725 | func nx_simt_lerp_rgba(a: i64, b: i64, f: i64) -> i64 called by 1: nx_simt_tex_sample |
| 739 | func nx_simt_texel(c: *NxSimtCore, ix: i64, iy: i64) -> i64 |
| 752 | func nx_simt_tex_sample(c: *NxSimtCore, u: i64, v: i64) -> i64 |
| 784 | func nx_simt_ipdom_push(c: *NxSimtCore, w: i64, mask: i64, pc: i64, ft: i64) -> i64 called by 1: nx_simt_do_split |
| 795 | func nx_simt_do_split(c: *NxSimtCore, w: i64, rs1: i64) -> i64 |
| 828 | func nx_simt_do_join(c: *NxSimtCore, w: i64) -> i64 called by 1: nx_simt_exec_warp |
| 848 | func nx_simt_do_bar(c: *NxSimtCore, w: i64, bar_id: i64, expect: i64) -> i64 called by 1: nx_simt_exec_warp |
| 892 | func nx_simt_mark_dest(c: *NxSimtCore, w: i64, rd: i64, lat: i64) -> i64 called by 1: nx_simt_exec_warp |
| 903 | func nx_simt_srcs_ready(c: *NxSimtCore, w: i64) -> i64 |
| 940 | func nx_simt_pick_warp(c: *NxSimtCore) -> i64 |
| 967 | func nx_simt_any_active(c: *NxSimtCore) -> i64 |
| 981 | func nx_simt_exec_warp(c: *NxSimtCore, w: i64, addrs: *i64, bank_hits: *i64) -> i64 |
| 1428 | func nx_simt_run(c: *NxSimtCore, max_cycles: i64, addrs: *i64, bank_hits: *i64) -> i64 called by 3: run_config_fullrun_config_memrun_config calls 3: nx_simt_any_activenx_simt_pick_warpnx_simt_exec_warp |
| 1486 | func nx_simt_cluster_core(cl: *NxSimtCluster, i: i64) -> *NxSimtCore |
| 1490 | func nx_simt_cluster_init(cl: *NxSimtCluster, ncores: i64, nw: i64, nt: i64, called by 1: run_cluster calls 5: sys_mmapnx_simt_cluster_corenx_simt_allocnx_simt_initnx_simt_set_memory |
| 1532 | func nx_simt_cluster_run(cl: *NxSimtCluster, max_cycles: i64, called by 1: run_cluster calls 4: nx_simt_cluster_corenx_simt_any_activenx_simt_pick_warpnx_simt_exec_warp |
| 1594 | func nx_simt_cluster_ipc_permille(cl: *NxSimtCluster) -> i64 called by 1: run_cluster |
| 1603 | func nx_simt_ipc_permille(c: *NxSimtCore) -> i64 |
| 1612 | func nx_simt_lane_ipc_permille(c: *NxSimtCore) -> i64 |