Skip to content

mxdotp: LLM-assisted datapath restructuring around narrower pipeline cuts - #34

Open
Zeltrxn2333 wants to merge 4 commits into
pulp-platform:mxcorefrom
Zeltrxn2333:mxdotp-datapath-restructure
Open

mxdotp: LLM-assisted datapath restructuring around narrower pipeline cuts#34
Zeltrxn2333 wants to merge 4 commits into
pulp-platform:mxcorefrom
Zeltrxn2333:mxdotp-datapath-restructure

Conversation

@Zeltrxn2333

Copy link
Copy Markdown

Summary

This is an LLM-assisted change: the restructuring below was proposed and
iterated by an LLM-driven optimisation flow, then reviewed by hand.

This PR restructures the internal datapath of fpnew_mxdotp_multi (files
src/fpnew_mxdotp_multi.sv, src/mxdotp/fpnew_mxdotp_multi_modules.sv) so that
the pipeline registers sit at narrower cut points and the per-stage logic depth
is shorter. It is a functionally transparent change:

  • module interface, parameters (FpSrcFmtConfig, IntSrcFmtConfig,
    NumPipeRegs, PipeConfig, LaneWidth, VectorSize, ...), handshake and
    latency are unchanged;
  • the pipeline-register allocation (NUM_INP_REGS, NUM_IM_REGS,
    NUM_MID_REGS, NUM_MO_EARLY_REGS, NUM_MO_LATE_REGS, NUM_OUT_REGS) and
    the PipeConfig semantics are byte-identical to eb505fe;
  • results are bit-identical to eb505fe for every source format and pipeline
    configuration, at every VectorSize for which the FP6 remainder interface
    is well defined (8*VectorSize mod 6 == 4, e.g. 8, 32; at VectorSize = 16
    FP6/FP6ALT read past the 2-bit operands_*_fp6_rem_i port on eb505fe
    and on this branch alike).

fpnew_mxdotp_multi_pkg.sv and everything outside mxdotp are untouched.

The PR is four commits: (1) the datapath restructuring, (2) a width-generic
rewrite of one stage in the new normalisation window, (3) removal of the
submodules that are no longer instantiated, (4) a one-line fix of a
pre-existing FP6 lane-extraction bug for VectorSize != 8.

Scope: tuned for NumPipeRegs = 3

The restructuring was done for one pipeline configuration only:
NumPipeRegs = 3, PipeConfig = INSIDE, i.e. register banks at IM, MID and
MO_LATE (NUM_IM_REGS = NUM_MID_REGS = NUM_MO_LATE_REGS = 1, all others 0).
"Stage n" below means the combinational logic between consecutive banks in
that configuration:

  • stage 1: inputs → IM bank (classification, multipliers, hoisted exponent /
    shift-amount arithmetic);
  • stage 2: IM → MID bank (alignment shifters, fused SoP + accumulator add);
  • stage 3: MID → MO_LATE bank (two's complement, LZC, normalisation window);
  • output: MO_LATE bank → outputs (rounding, status, special-value assembly).

Every other NumPipeRegs / PipeConfig still elaborates and produces the
same results as eb505fe (the bank positions are untouched), but no effort
was made to balance the logic for those configurations; in particular the
combinational (NumPipeRegs = 0) depth is the sum of the three stages either
way.

What changed

1. Re-cut pipeline banks

The bank positions (NUM_*_REGS, PipeConfig) are unchanged; what each bank
holds is not. Widths quoted for VectorSize = 8, FP32 accumulator.

bank eb505fe this PR
IM (INP-MID) bank aligned products (8×67 + FP6/FP4 lanes ≈ 644 b), accumulator operand + fp_info_t, scale raw products + per-lane complete shift amount (258 b), pre-negated accumulator mantissa + its shift amount, exponent_major
MID bank 95-bit sum of products, accumulator operand + fp_info_t, scale (accumulator aligned and added behind the bank) 119-bit fused SoP+accumulator result, its zero flag, the two raw accumulator verdicts, exponent_major
MO_EARLY / MO_LATE / OUT banks 95-bit sum_product word carried along for the zero test, 32-bit special result + 5-bit status 1-bit sum_product_is_zero, 4-bit special verdict {result_is_special, nv, is_inf, inf_sign} re-expanded at the output (fpnew_mxdotp_special_assemble)

So the alignment barrel shifters and the fused adder now sit in the same stage
(between INP-MID and MID), while stage 1 is the multipliers plus the narrow
exponent/shift-amount arithmetic hoisted in front of them.

2. Stage 1: hoist everything that does not depend on the products

  • fpnew_mxdotp_product_exponent (new, replaces the early half of
    fpnew_mxdotp_product_shifter): computes the per-lane alignment shift amount
    in stage 1 with the constant offset (SOP_SHIFT = 28 for FP8, 4 for FP6)
    folded into the exponent adder, so stage 2 no longer has an adder in front of
    the barrel shifter. For the FP8 instance the INT8 and FP8 alignment arms are
    made disjoint: on INT8 the shift amount is forced out of range
    (AmtWidth = ExpWidth+1 bits, all ones ≥ OutputWidth), so the 67-bit × 8-lane
    format multiplexer in the aligner collapses into an OR that is free on the
    low ANCHOR bits.
  • fpnew_mxdotp_product_align (new, the late half of the old shifter): just
    the variable barrel shift, behind the INP-MID bank.
  • fpnew_mxdotp_accumulator_prep (new, split off fpnew_mxdotp_accumulator_shift
    at its narrow waist): the 24-bit conditional negate of the accumulator mantissa
    and the 4-term accumulator shift-amount sum move into stage 1; the INP-MID bank
    carries signed_mantissa + shift amount instead of the info word.
  • exponent_major (= scale + constant offset) is computed once by the stage-1
    scale adder and carried through the banks instead of the raw scale, so the
    10-bit constant adder in fpnew_mxdotp_norm_finalize disappears.
  • Classifier wiring: each format's classifier input is an elaboration-time
    choice of the operand packing that holds when src_fmt == fmt (its output is
    only ever read through [src_fmt]), removing the src_fmt multiplexer in
    front of the classifier bank.
  • fpnew_mxdotp_signed_vector_multiplier: the FP8 sign is taken out of
    the front of the multiplier array. Instead of negating mantissa_a before
    the multiply, mantissa_a is one's-complemented (one XOR) and mantissa_b
    is added as one extra partial-product row, using (-a)*b == (~a)*b + b.
    product_signed is bit-identical.

3. Stage 2: fused sum-of-products + accumulator

  • fpnew_mxdotp_fused_sop_accumulator (new, replaces the
    adder_tree / format_adder / add_accumulator_sop chain): one signed
    reduction of the FP8 lanes, the FP6/FP4 lane sums and the aligned accumulator.
  • The SoP-is-zero test is taken on the low SoPFixedWidth bits (70 at
    VectorSize = 8) instead of the full 95-bit container; the exact SoP magnitude
    is bounded below 2**SoPFixedWidth, so the two tests agree (argument in the
    source comment).
  • The two accumulator verdicts (result_is_accumulator operands) are registered
    raw and combined behind the MID bank.

4. Stage 3: normalisation

  • fpnew_mxdotp_norm_window (new, replaces fpnew_mxdotp_norm_shift): the
    normalisation left shift is three coarse binary stages (shamt[6:4]) followed
    by a single 16-way select for the fine amount (shamt[3:0]), with the sticky
    of the dropped bits carried on 7/3/1 bits instead of full-width windows.
  • fpnew_mxdotp_lzc119 (new): a radix-4 leading-zero counter, bit-identical
    to lzc #(.MODE(1)) from common_cells on every input including all-zero.
  • fpnew_mxdotp_twos_compl hands over the one's complement magnitude; the
    pending +1 is re-applied in norm_finalize and its effect on the LZC
    (lzc_dec) is folded into exponent_major. The correction is dropped from
    norm_shamt itself, where the window provably cannot observe it.
  • The two early output selects (special / accumulator pass-through) are merged
    so the rounder tail sees one 2:1 level.

5. Output

  • Overflow / underflow after rounding are derived from the pre-round operands,
    taking the status cone off the rounding incrementer.

Modules

New: fpnew_mxdotp_special_assemble, fpnew_mxdotp_product_exponent,
fpnew_mxdotp_product_align, fpnew_mxdotp_accumulator_prep,
fpnew_mxdotp_fused_sop_accumulator, fpnew_mxdotp_norm_window,
fpnew_mxdotp_lzc119.

Removed (third commit, no remaining instances): fpnew_mxdotp_product_shifter,
fpnew_mxdotp_adder_tree, fpnew_mxdotp_format_adder,
fpnew_mxdotp_add_accumulator_sop, fpnew_mxdotp_norm_shift.

VectorSize != 8 fix (second commit)

The first cut of fpnew_mxdotp_norm_window built its shift-by-64 stage as
{sum_magnitude[W0-65:0], 32'b0}, which is only the top-87-bit window of
X << 64 when LZC_SUM_WIDTH == 119 (i.e. VectorSize == 8). At
VectorSize = 32 (LZC_SUM_WIDTH = 121) it silently dropped two leading bits
for normalisation shifts ≥ 64. The second commit writes the stage as a constant
shift so it is correct for any width, and adds an elaboration-time $fatal
guard for the 64 < LZC_SUM_WIDTH <= 128 range the window assumes
(VectorSize <= 4096). At VectorSize = 8 the two forms select the same wires.

FP6 lane extraction for VectorSize != 8 (fourth commit)

Pre-existing on eb505fe: fpnew_mxdotp_multi.sv reads the FP6 remainder lanes
from flat_operands_{a,b}_q at a hard-coded offset of 48 (= 8·6), so
FP6/FP6ALT results were wrong for any VectorSize other than 8 (e.g. 32). The
offset is now VectorSize*6. VectorSize = 8 is unchanged. This does not
change the FP6 remainder interface itself, so VectorSize values whose
8*VectorSize mod 6 != 4 (e.g. 16) remain unsupported for FP6/FP6ALT as before.

Aofeng Shen added 4 commits September 2, 2026 12:42
Move the per-lane alignment shift amounts, the accumulator negate/shift
amount and the scale offset into stage 1, fuse the sum-of-products with
the accumulator add in stage 2, and replace the normalisation shifter and
LZC with a windowed shifter and a radix-4 counter. Pipeline register
positions, PipeConfig semantics, interface, latency and results are
unchanged. Builds on the LaneWidth/VectorSize parameterisation: all widths
are passed per module (SoPFixedWidth), the package carries none of them.
fpnew_mxdotp_norm_window built its first coarse stage as
{sum_magnitude[W0-65:0], 32'b0}, which is the top-W1 window of (X << 64)
only when LZC_SUM_WIDTH == 119 (VectorSize == 8). For VectorSize = 32
(LZC_SUM_WIDTH = 121) the concatenation is two bits too wide and the
assignment silently dropped the two leading bits whenever the
normalisation shift was >= 64.

Write the stage as a constant shift of the full word and take the top
W1 bits, which is correct for any width, and add an elaboration-time
guard for the 64 < LZC_SUM_WIDTH <= 128 range the window assumes.
At VectorSize = 8 the two forms select the same wires.
fpnew_mxdotp_product_shifter, fpnew_mxdotp_adder_tree,
fpnew_mxdotp_format_adder, fpnew_mxdotp_add_accumulator_sop and
fpnew_mxdotp_norm_shift were superseded by product_exponent/product_align,
fused_sop_accumulator and norm_window and have no remaining instances.
The FP6 remainder lanes were read from flat_operands_{a,b}_q at a
hard-coded offset of 48 (= 8 * 6), so FP6/FP6ALT results were wrong for
any VectorSize other than 8. Use VectorSize*6 instead.
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.

1 participant