Skip to content

MUFU + Zfhmin - #432

Merged
tinebp merged 10 commits into
masterfrom
feature/mufu
Oct 23, 2026
Merged

tinebp merged 10 commits into
masterfrom
feature/mufu

Conversation

@jel221

@jel221 jel221 commented Oct 22, 2026

Copy link
Copy Markdown
Collaborator

Human comment: MUFU is the equivalent of the same unit in NVIDIA, performing arithmetic functions to speed up things like softmax. With the base WMMA on FlashAttention2, it achieves around 1.6x speedup. I predict the speedup is larger once it is measured with full WGMMA is merged into main. Zfhmin is also included in this branch, which includes converting fp16<->fp32 with a single instruction, which gives you a free 7% speedup for some ~500 LUTs and 148 flip flops on the FPGA.

MUFU: special-function unit

  • ISA (custom-2, 0x5B, R-type): funct7 = 0, funct3 = format (FP32 only), rs2 = function: 0 rcp, 1 rsqrt, 2 ex2, 3 lg2, 4 sinpi, 5 cospi, 6 tanh. rd/rs1 are FP registers. Intrinsics in sw/kernel/include/vx_mufu.h (vx_rcpf, vx_ex2f, ...) plus fast-math helpers (vx_expf_fast, vx_sigmoidf_fast, vx_geluf_fast, ...).
  • Units: a new execution unit, EX_MUFU. VX_CFG_NUM_MUFU_BLOCKS = 1, VX_CFG_NUM_MUFU_LANES = 1 (one lane was as fast as two on FlashAttention), VX_CFG_MUFU_LATENCY = 7.
  • Datapath: table lookup plus polynomial; all tables in one 512 x 72 ROM (one RAMB36).
  • Accuracy (exhaustive over all FP32 inputs): rcp 0.83 ulp, rsqrt 1.26 ulp, ex2 0.96 ulp, lg2 1.09 ulp, sinpi/cospi 2^-22.3 absolute, tanh 2^-11.6 relative (PTX .approx level); exact cases preserved.
  • FPGA (VX_mufu_unit_top, xcu55c, 16 threads): 375 MHz, 1,227 LUT, 1,153 FF, 57 LUTRAM, 1 BRAM, 3 DSP. New fpga_gate build and baseline.
  • Gating: VX_CFG_EXT_MUFU_ENABLE (off by default); capability bit VX_ISA_EXT_MUFU (MISA_EXT bit 12); counters in MPM class 17.
  • Models: RTL hw/rtl/mufu/; SimX sim/simx/mufu_unit.*; shared bit-exact golden model sw/common/mufu.*; table generator hw/scripts/gen_mufu_tables.py.

Zfhmin (standard RISC-V)

  • flh, fsh, fmv.x.h, fmv.h.x, fcvt.h.s, fcvt.s.h, plus fcvt.h.d/fcvt.d.h on RV64. All rounding modes, IEEE flags, NaN-boxing. Gate VX_CFG_EXT_ZFHMIN_ENABLE (off by default); capability bit 13; _zfhmin added to -march.
  • Implemented in SimX and all four FPU backends (DPI, STD, DSP, FPNEW). Known limitation: the pinned cvfpu (FPNEW) converts infinity to max-finite under non-away rounding modes and misses NV for a signaling NaN; documented in docs/designs/floating_point_unit.md.
  • FPGA cost (RV32 FPU, DSP backend): +531 LUT, +148 FF, no DSP/BRAM change, meets 300 MHz.

Fixes included

  • CSR writes used lane 0's operand even when lane 0 was inactive (VX_csr_unit.sv); a CSR write in divergent code wrote a stale value. Now the first active lane; SimX matches.
  • fcvt.s.d narrowing (STD/DSP): overflow now respects the rounding mode; a rounding carry into overflow raises OF; underflow uses after-rounding tininess; a subnormal rounding up to the smallest normal is no longer flushed to 0.
  • Perf baselines regenerated with the pinned v3.0.1 toolchain. The new config keys changed every baseline's hash; cycles and instruction counts are identical to master.

Verification

  • MUFU: hw/unittest/mufu_unit (RTL vs golden model), tests/regression/mufu on simx/rtlsim/xrt, RV32/RV64, simx/rtlsim parity cases.
  • Zfhmin: tests/regression/zfhmin, bit-exact with flags against softfloat across all rounding modes (widening exhaustive), on simx and rtlsim with the DPI, STD and DSP FPUs; fcvt_unit unit test extended with fp16; parity within 0.07%.
  • With both features off: ISA rv32uf/rv64ud pass on DPI and STD; every FPU backend builds on RV32/RV64.

The MUFU and Zfhmin config keys change the resolved config hash, which
marked every perf_gate baseline stale. Cycles are unchanged within 1.2%
except sgemm-nt4 (+3.0%) and sgemv (nt4 +34.6%, nt16 +5.2%); sgemv
already measures the new values on master (316432 instrs / 1175175
cycles at nt4), so its old baseline predates a master change.
The previous refresh was measured with a local toolchain that compiles
kernels differently from vortex-toolchain-prebuilt v3.0.1. Measured
with the pinned toolchain, every cycle and instruction count equals
master's; only the config hashes change.
@tinebp

tinebp commented Oct 22, 2026

Copy link
Copy Markdown
Collaborator

My feedback:

1- make sure your AI agent always reads AGENTS.md, docs/coding_guidelines_cpp.md, docs/coding_guidelines_verilog.md and the SimX channel rule in docs/designs/simobject.md before writing code or executing a code review before commit!!!!!

2- MUFU should not use a dedicated execution unit - very expensive!!! - move MUFU into SFU like all other hardware extensions - will slightly reduce its performance, but it is ok

Below is Claude Opus 5.5 review:

Bugs (first pass, none verified)
CSR write differs from RTL when a warp spans several packets (csr_unit.cpp:427). The RTL does one write per packet; SimX does a single write for the whole warp.
FCVT.H.S on RV64 skips the NaN-box check for an F32 source in the synthesizable converters (VX_fcvt_unit.sv:103). SimX and the DPI model check it, so results differ.
vx_sqrtf_fast returns +inf for subnormal inputs (vx_mufu.h:75).
With Zfhmin on an F-only core, the decoder accepts double-precision conversions (FCVT.S.D, FCVT.D.S, FCVT.H.D) (VX_decode.sv:509). They used to be rejected.
SimX's new LH-to-float boxing has no Zfhmin guard (lsu_unit.cpp:271). The RTL keeps the old behaviour when Zfhmin is off.
Composite fast-math ops are never checked when repeat > 1 (main.cpp:245), and both parity cases run with -r256.
The SimX trace prints MUFU.RCP.S where the RTL prints MUFU.RCP (decode.cpp:415).
The RV32 boxing rules in dpi_float.cpp are now inconsistent: 16-bit values are boxed but 8-bit ones are not. Four copies of the NaN-box helpers now exist across SimX and DPI.
Project-rule violations (AGENTS.md)
A known SimX/RTL timing gap is routed around (mufu.yaml:52): "Not 16 threads: there SimX runs ~5% fast". AGENTS §4 says never to absorb a divergence; it must be modelled. Dropping the failing shape is the same thing, and 16 threads with one lane is the default fpga_gate shape.
The new mufu DUT is added only to the Xilinx catalog. hw/syn/yosys/dut/catalog.mk has no entry and there is no yosys baseline; §4 says to keep both catalogs in step. That catalog already lacks several Xilinx DUTs, so this is low severity, but it is still a gap.
ci/baselines/synthesis/xilinx/mufu.json is golden data. It must come from a human running fpga_gate.py --update-baseline. Nothing in the PR shows where it came from.
A config knob that can't be changed (VX_config.toml:161): VX_CFG_MUFU_LATENCY is exposed, but VX_mufu_pe.sv:13 asserts LATENCY == 7. Setting any other value just fails the build.
No guard that Zfhmin requires F. The toml comment says it does, but VX_define.vh has no `error check for it, unlike MUFU.
Unrelated change bundled in (catalog.mk:65): the fpu_PKG fix isn't part of MUFU or Zfhmin.
Comment rules (C++ §10, Verilog §9: no layer cross-references, no history)
SimX and shared host code refers to RTL internals:
csr_unit.cpp:421 "as in the RTL"
mufu_unit.cpp:34 "The RTL pipeline has no tag store"
mufu_unit.cpp:62 "lane-gather stage"
mufu.cpp:28 "mirrors the RTL stage-1 register"
mufu.h:18 "(hw/rtl/mufu)"
Development history in a comment: VX_config.toml:158 "a wider unit did not speed up fused FlashAttention".
An invented rule: gen_mufu_tables.py:28 "no comments, by project convention for this unit". No such convention exists.
Verilog style
Module params packed on one line (§12): VX_fcvt_unit.sv:128 VX_fp_classifier #(.EXP_BITS(F16_EXP), .MAN_BITS(F16_MAN)).
Branches without begin/end (§3): new and changed branches in the dst_r block, VX_fcvt_unit.sv:555.
Misaligned port connections (§12): VX_mufu_unit.sv:31 .dispatch_if(dispatch_if), and lines 83–84 .pe_data_out(...) / .pe_shared_out(...).
Hand-rolled logic where the library already has a module (§11):
VX_mufu_rom.sv:29 builds its own ROM from an initial block and a Xilinx-only rom_style attribute. VX_sp_ram already supports this (INIT_ENABLE, INIT_FILE, OUT_REG) and works with the ASIC flow too.
VX_csr_unit.sv:75 picks the first active lane with a hand-written loop instead of the library's priority encoder.
A decoded field nothing uses: mufu_args_t.fmt and IntrMufuArgs.fmt (VX_decode.sv:907, types.h:614). It is always 0 (decode requires funct3 == 0) and the unit marks op_args unused. AGENTS §6 says to delete unused code.
C++ style
Mandatory braces missing (§2), across nearly all new C++:
SimX: mufu_unit.cpp:24 and line 46, csr_unit.cpp:417 and line 423, decode.cpp:223 and lines 224, 265–266, 772, 788, decode.cpp:1185
Shared model: mufu.cpp:58 and roughly 20 more one-line if … return statements
Tests: about 50 instances across hw/unittest/{mufu_unit,fcvt_unit}/main.cpp and tests/regression/{mufu,zfhmin}/*.cpp
} else followed by #endif and then if (…) (fpu_unit.cpp:298, lines 327 and 345). An else without braces, split across a preprocessor block, is exactly the re-scoping hazard §2 bans.
Tabs in a new file (§1): tests/regression/mufu/kernel.cpp is indented with tabs. The tabs in fpu_unit.cpp and lsu_unit.cpp match code already there.
Four-space indent in a public header: sw/kernel/include/vx_mufu.h uses four spaces where the C++ guide says two.

jel221 and others added 4 commits October 22, 2026 15:10
- MUFU is now an SFU processing element (SFU op 0xF): the SFU's lanes are
  serialized onto VX_CFG_NUM_MUFU_LANES datapath lanes (default 1). The
  dedicated execution unit, its dispatch/commit paths and MPM class are
  removed. SimX models it as a helper owned by SfuUnit.
- VX_MUFU_LATENCY is a fixed constant in VX_types.toml, not a config knob.
- Table ROM via a new hw/rtl/libs/VX_rom (contents as a parameter), since
  VX_sp_ram's init file is compiled out on the ASIC paths.
- Fixes: SimX CSR writes per SFU packet; F32 NaN-box check on fp32 sources
  of the STD/DSP converter with FLEN=64; vx_sqrtf_fast returns a signed zero
  for subnormal input and +inf for +inf; F-only decode rejects FCVT forms
  involving D; SimX LH-to-float raw bits only under Zfhmin; MUFU test checks
  every op for any -r; SimX trace prints MUFU.<fn>; NaN-boxing helpers shared
  in sw/common/nan_box.h, consistent on RV32.
- Zfhmin requires F (build-time error).
- CI: 16-thread parity case; mufu DUT added to the Yosys catalog with an
  asic_gate baseline; fpga_gate mufu baseline re-recorded for the new DUT
  (fpga_gate.py --update-baseline, Vivado 2024.2); perf baselines
  re-recorded for the new config hash with the pinned toolchain (cycle
  counts unchanged).
- Style: braces, 2-space indent, one parameter per line, begin/end, library
  priority encoder for the first active CSR lane, no cross-layer comments.
The SFU's response switch already buffers each PE's results, so the MUFU
serializer's own two-slot output buffer was a second copy of a full
16-lane result. Removing it drops MUFU's result latency by one cycle
(SimX matches) with no throughput loss, including with one MUFU lane per
SFU lane.

Re-recorded mufu baselines (fpga_gate/asic_gate --update-baseline):
Xilinx 2030 LUT / 1105 FF (was 2624 / 2278), Yosys 2073 um^2 (was 2701).
The xilinx DUT flow compiles every file on the include path, so the fpu
DUT's BASE_INC brings in hw/rtl/VX_cluster.sv, which imports VX_tlb_pkg.
fpu_PKG never listed the package, and synthesis stops with:

  ERROR: [Synth 8-36] 'VX_tlb_pkg' is not declared [.../VX_cluster.sv:1]

The mufu DUT lists it for the same reason.
@tinebp
tinebp merged commit b447578 into master Oct 23, 2026
2 checks passed
@tinebp
tinebp deleted the feature/mufu branch October 23, 2026 21:18
tinebp added a commit that referenced this pull request Oct 26, 2026
Brings in master's Rv64 fixes (#427), the single-packet dispatch fix
(#428), MUFU + Zfhmin (#432) and the RTU performance counters with
the perf-counter fixes (#435).

Conflicts keep the branch's structure where it reorganized the code:
- Cache bank and MSHR: the branch already carries #427's AMO write-back
  queue and commit-busy checks; its back-to-back fill path (no
  fill_inflight stall) and lazy-fetch queue stay.
- SFU: master's MUFU PE moves into VX_sfu_block, next to the RTU PE,
  and SimX's SFU dispatches MUFU ops per block through send_out.
- Host performance-counter reads: the branch's VX_csr_mpm already
  serves every counter master's VX_csr_data block reads.
- AFU/xrtsim memory ports, device caps (socket size), hardware
  profiles, changelog and command-processor docs: the branch's.
- SimX memory and local-memory perf counters now run from device
  reset, as master fixed them and as the RTL counts.
- The regression list gains mufu and zfhmin; the Xilinx DUT catalog
  gains mufu.

The perf baselines keep the branch's recorded values; they are
re-recorded on the merged tree by perf_gate --update-baselines.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants