code wiki / _hdl_build / nx_simtcore_gate.nx
nx_simtcore_gate.nx
buildroot/runtime/_hdl_build/nx_simtcore_gate.nx
about
nx_simtcore_gate.nx -- GATE: the sovereign SIMT GPGPU core executes real SIMT kernels
and reproduces the ARCHITECTURAL RELATIONSHIPS published for Vortex (Georgia Tech).
This is the measuring stick for nx_simtcore.nx. It closes the nx_sovgpu_census cell
"open GPU core on OUR FPGA (Vortex/Nyuzi class)", previously graded GAP.
===== THE ORACLE =====
Vortex, MICRO'54 2021 (arXiv 2110.10857). The paper publishes NO per-benchmark absolute
IPC in its text -- the IPC figures are plots, and their y-axes are labelled CPI while the
captions say IPC, a contradiction the paper never resolves. So absolute IPC is NOT a
usable oracle and we do not pretend it is.
What the paper DOES state numerically, in prose, is two config relationships on sgemm --
and all three configs hold TOTAL THREADS CONSTANT at 16, so it is a fair comparison of
how you PARTITION the same threads:
"Moving from a 4W-4T configuration to a 2W-8T configuration, maximizing threads,
introduces a 69% area cost increase in LUT and registers, as well as a speedup of
20% for sgemm."
"changing the configuration to 8W-2T, maximizing wavefronts, generates cheaper
hardware, about a 27% area reduction. This comes with a reduction in performance in
terms of IPC, 36% for sgemm in the extreme case."
sgemm is classified COMPUTE-BOUNDED by the same paper. So the falsifiable predictions
our core must reproduce on a compute-bound kernel at equal total threads are:
P1 2W-8T is FASTER than 4W-4T
P2 8W-2T is SLOWER than 4W-4T
HONEST SCOPE: we test DIRECTION, not magnitude. Our model has no FPU, no DRAM
controller and no FPGA routing, so its idealised speedups are LARGER than Vortex's
achieved ones (Vortex got +20%/-36% where a pure issue-width model predicts +100%/-50%).
Claiming magnitude agreement would be dishonest; claiming direction agreement is exactly
what the evidence supports.
P3 is the counterpart the same paper implies and that every GPU exists for: on a
MEMORY-BOUND kernel the ordering INVERTS, because more warps hide more latency. A core
that reproduces P1/P2 but not P3 has modelled issue width and forgotten latency hiding.
===== TEETH =====
T1 CORRECTNESS -- 16 threads compute a per-thread result matching an independent
dependencies 6 imports · 0 importers
imports: nx_syscalls.nxnishi_hdl_primitives.nxrv64im_min_decoder.nxrv64im_min_alu.nxrv64im_min_sim.nxnx_simtcore.nx
imported by: nobody (leaf or entry point)
call flow from main pre-order; caps 40 nodes / depth 6 declared; ↻ = already shown
structs
| none |
consts
| 80 | const R_ZERO: i64 = 0 |
| 81 | const R_T0: i64 = 5 |
| 82 | const R_T1: i64 = 6 |
| 83 | const R_T2: i64 = 7 |
| 84 | const R_S0: i64 = 8 |
| 85 | const R_S1: i64 = 9 |
| 86 | const R_A0: i64 = 10 |
| 87 | const R_A1: i64 = 11 |
| 88 | const R_A2: i64 = 12 |
| 89 | const R_A3: i64 = 13 |
| 90 | const R_A4: i64 = 14 |
| 91 | const R_A5: i64 = 15 |
| 92 | const R_A6: i64 = 16 |
| 93 | const R_A7: i64 = 17 |
| 94 | const R_S2: i64 = 18 |
| 95 | const R_T3: i64 = 28 |
| 96 | const R_T4: i64 = 29 |
| 97 | const R_T5: i64 = 30 |
| 98 | const R_T6: i64 = 31 |
| 116 | const VTX_SGEMM_4W4T: i64 = 2000 // IPC x1000 |
| 117 | const VTX_SGEMM_2W8T: i64 = 2400 |
| 118 | const VTX_SGEMM_8W2T: i64 = 1470 |
| 125 | const VTX_RATIO_2W8T_PERMILLE: i64 = 1200 |
| 126 | const VTX_RATIO_8W2T_PERMILLE: i64 = 735 |
| 129 | const G_MEM_BASE: i64 = 0x80000000 |
| 130 | const G_MEM_SIZE: i64 = 0x8000 |
| 133 | const G_OUT_OFF: i64 = 0x1010 |
| 134 | const G_NTHREADS: i64 = 16 // total threads held CONSTANT across all three configs |
| 135 | const G_LOOP_K: i64 = 64 // loop trip count; large enough that the loop dominates |
| 351 | const G_TEX_OFF: i64 = 0x1800 |
| 352 | const G_TEX_W: i64 = 16 |
| 353 | const G_TEX_H: i64 = 16 |
| 354 | const G_TEX_REL: i64 = 2032 // G_TEX_OFF - G_OUT_OFF, fits a 12-bit signed immediate |
functions
| 64 | func gw(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 } |
| 65 | func gn(v: i64) -> i64 |
| 138 | func e_addi(rd: i64, rs1: i64, imm: i64) -> i64 { return ((imm & 0xFFF) << 20) | (rs1 << 15) | (rd << 7) | 0x13 } |
| 139 | func e_andi(rd: i64, rs1: i64, imm: i64) -> i64 { return ((imm & 0xFFF) << 20) | (rs1 << 15) | (7 << 12) | (rd << 7) | 0x13 } |
| 140 | func e_slli(rd: i64, rs1: i64, sh: i64) -> i64 { return ((sh & 0x3F) << 20) | (rs1 << 15) | (1 << 12) | (rd << 7) | 0x13 } |
| 141 | func e_auipc(rd: i64, imm20: i64) -> i64 { return ((imm20 & 0xFFFFF) << 12) | (rd << 7) | 0x17 } called by 1: emit_prologue |
| 142 | func e_rr(rd: i64, rs1: i64, rs2: i64, f3: i64, f7: i64) -> i64 |
| 145 | func e_sw(rs1: i64, rs2: i64, imm: i64) -> i64 |
| 148 | func e_lw(rd: i64, rs1: i64, imm: i64) -> i64 { return ((imm & 0xFFF) << 20) | (rs1 << 15) | (2 << 12) | (rd << 7) | 0x03 } |
| 149 | func e_branch(rs1: i64, rs2: i64, off: i64, f3: i64) -> i64 |
| 156 | func e_jal(rd: i64, off: i64) -> i64 called by 1: build_divergent |
| 164 | func e_simt(f3: i64, rd: i64, rs1: i64, rs2: i64) -> i64 |
| 168 | func e_simtw(f3: i64, rd: i64, rs1: i64, rs2: i64) -> i64 |
| 171 | func e_srli(rd: i64, rs1: i64, sh: i64) -> i64 { return ((sh & 0x3F) << 20) | (rs1 << 15) | (5 << 12) | (rd << 7) | 0x13 } called by 1: build_tex_sw |
| 173 | func e_tex(rd: i64, rs1: i64, rs2: i64, rs3: i64) -> i64 called by 1: build_tex_hw |
| 176 | func e_nop() -> i64 { return e_addi(R_ZERO, R_ZERO, 0) } |
| 181 | func emit_prologue(prog: *i64, nw: i64) -> i64 |
| 209 | func build_compute(prog: *i64, nw: i64, k: i64) -> i64 |
| 224 | func ref_compute(gid: i64, k: i64) -> i64 called by 1: main |
| 237 | func build_membound(prog: *i64, nw: i64, k: i64) -> i64 |
| 253 | func build_divergent(prog: *i64, nw: i64, nop_out: i64) -> i64 |
| 285 | func build_sgemm_like(prog: *i64, nw: i64, k: i64) -> i64 |
| 305 | func build_reduce_hw(prog: *i64, nw: i64) -> i64 |
| 327 | func build_reduce_sw(prog: *i64, nw: i64) -> i64 |
| 357 | func build_tex_hw(prog: *i64, nw: i64, k: i64) -> i64 |
| 376 | func build_tex_sw(prog: *i64, nw: i64, k: i64) -> i64 |
| 421 | func build_ballot(prog: *i64, nw: i64) -> i64 |
| 430 | func gabs(v: i64) -> i64 called by 1: main |
| 440 | func build_gbar(prog: *i64, nw: i64, expect: i64) -> i64 |
| 454 | func run_cluster(prog: *i64, nprog: i64, ncores: i64, nw: i64, nt: i64, called by 1: main calls 4: sys_mmapnx_simt_cluster_initnx_simt_cluster_runnx_simt_cluster_ipc_permille |
| 486 | func run_config_full(prog: *i64, nprog: i64, nw: i64, nt: i64, called by 1: main calls 8: sys_mmapnx_simt_allocnx_simt_initnx_simt_set_memorynx_simt_set_cachenx_simt_set_texture+2 |
| 552 | func run_config_mem(prog: *i64, nprog: i64, nw: i64, nt: i64, called by 1: main calls 6: sys_mmapnx_simt_allocnx_simt_initnx_simt_set_memorynx_simt_runnx_simt_load32 |
| 592 | func run_config(prog: *i64, nprog: i64, nw: i64, nt: i64, |
| 631 | func report_config(name: *u8, nw: i64, nt: i64, cycles: i64, stats: *i64) -> i64 |
| 642 | func main() -> i64 |