RV32I + DOT4 / EVIDENCE

DOT4 is 5.2× faster after its clock cost.

A five-stage RV32I core with one custom instruction, DOT4, that multiplies four signed bytes and sums them. Five figures and one table, each built from the JSON reports the regression writes.

Cycle counts come from RTL simulation. Fmax and area are post-route estimates for iCE40 HX8K; the core has not run on a physical board. The Spike comparison uses riscv-arch-test 3.10.0; it is not certification. Hover or tab to any mark for its exact value.

Figure 1

DOT4 needs 5.8× fewer cycles than the best RV32I kernel at n = 256

DOT4 needs 5.8× fewer cycles than the best RV32I kernel at n = 256Shift-and-add, LW: 897 cycles at n = 16, 3,789 cycles at n = 64, 14,589 cycles at n = 256. Shift-and-add, LB: 865 cycles at n = 16, 3,661 cycles at n = 64, 14,077 cycles at n = 256. Quarter-square table: 223 cycles at n = 16, 859 cycles at n = 64, 3,403 cycles at n = 256. DOT4: 45 cycles at n = 16, 153 cycles at n = 64, 585 cycles at n = 2561001,00010,000Cycles, log scale1664256Vector length n (int8 elements per vector), log scale5.8× fewercyclesShift-and-add, LW14,589 cyclesShift-and-add, LB14,077 cyclesQuarter-square table3,403 cyclesDOT4585 cycles
DOT4 needs 5.8× fewer cycles than the best RV32I kernel at n = 256Shift-and-add, LW: 897 cycles at n = 16, 3,789 cycles at n = 64, 14,589 cycles at n = 256. Shift-and-add, LB: 865 cycles at n = 16, 3,661 cycles at n = 64, 14,077 cycles at n = 256. Quarter-square table: 223 cycles at n = 16, 859 cycles at n = 64, 3,403 cycles at n = 256. DOT4: 45 cycles at n = 16, 153 cycles at n = 64, 585 cycles at n = 2561001,00010,000Cycles, log scale1664256Vector length n, log scaleShift-add LW14,589 cyclesShift-add LB14,077 cyclesQuarter-square3,403 cyclesDOT4585 cycles5.8× fewer thanquarter-square
Compare the vertical gaps: on a log axis an equal gap is an equal ratio, and the gaps stay nearly constant as n grows. Cycle counts from Icarus Verilog version 12.0 simulation of the RTL, uniform random int8 data (seed 2026), counted from reset release through the final EBREAK. Shift-and-add cost depends on the data: with extreme values at n = 16 the LW version takes 1,185 cycles. Both axes are log scale because cycles span 2.5 decades.
Data table
Kerneln = 16n = 64n = 256
Shift-and-add, LW8973,78914,589
Shift-and-add, LB8653,66114,077
Quarter-square table2238593,403
DOT445153585

Figure 2

After its clock cost, DOT4 is 5.2× faster than the best RV32I kernel at n = 256

After its clock cost, DOT4 is 5.2× faster than the best RV32I kernel at n = 256Quarter-square table: n = 16, 4.96× in cycles, 4.41× after clock cost; n = 64, 5.61× in cycles, 5.00× after clock cost; n = 256, 5.82× in cycles, 5.18× after clock cost. Shift-and-add, LW: n = 16, 19.93× in cycles, 17.75× after clock cost; n = 64, 24.77× in cycles, 22.05× after clock cost; n = 256, 24.94× in cycles, 22.20× after clock cost. Shift-and-add, LB: n = 16, 19.22× in cycles, 17.11× after clock cost; n = 64, 23.93× in cycles, 21.30× after clock cost; n = 256, 24.06× in cycles, 21.42× after clock costcycles onlyafter clock cost (median)range over route seedsQuarter-square tablen = 164.4×n = 645.0×n = 2565.2×Shift-and-add, LWn = 1617.7×n = 6422.0×n = 25622.2×Shift-and-add, LBn = 1617.1×n = 6421.3×n = 25621.4×0×5×10×15×20×25×Why the dots move left: the DOT4 core routes at a median 35.94 MHz, the core without DOT4 at 40.37 MHz.Each DOT4-core cycle is 12.3% longer, so every cycle ratio is multiplied by 35.94 / 40.37 = 0.890.The range pairs the slowest DOT4 seed (34.51 MHz) with the fastest DOT4=0 seed (43.25 MHz), and the reverse.
After its clock cost, DOT4 is 5.2× faster than the best RV32I kernel at n = 256Quarter-square table: n = 16, 4.96× in cycles, 4.41× after clock cost; n = 64, 5.61× in cycles, 5.00× after clock cost; n = 256, 5.82× in cycles, 5.18× after clock cost. Shift-and-add, LW: n = 16, 19.93× in cycles, 17.75× after clock cost; n = 64, 24.77× in cycles, 22.05× after clock cost; n = 256, 24.94× in cycles, 22.20× after clock cost. Shift-and-add, LB: n = 16, 19.22× in cycles, 17.11× after clock cost; n = 64, 23.93× in cycles, 21.30× after clock cost; n = 256, 24.06× in cycles, 21.42× after clock costcycles onlyafter clock cost (median)Quarter-square tablen = 164.4×n = 645.0×n = 2565.2×Shift-and-add, LWn = 1617.7×n = 6422.0×n = 25622.2×Shift-and-add, LBn = 1617.1×n = 6421.3×n = 25621.4×0×5×10×15×20×25×Why the dots move left:DOT4 core median Fmax 35.94 MHz,core without DOT4 40.37 MHz.Every cycle ratio is multiplied by 0.890.
Cycles alone overstate the gain, because the DOT4 multiplier lengthens the critical path. Each cycle count is divided by its own core's median post-route Fmax, so the filled dot is wall time on the estimated clock. Against shift-and-add the n = 256 figure is 22.2×. The quarter-square kernel also reads a 2 KiB table that DOT4 does not need. These are post-route estimates; the core has not run on hardware.
Data table
BaselinenCycles onlyAfter clock costSeed range
Quarter-square table164.96×4.41×3.95× to 4.83×
Quarter-square table645.61×5.00×4.48× to 5.48×
Quarter-square table2565.82×5.18×4.64× to 5.67×
Shift-and-add, LW1619.93×17.75×15.90× to 19.45×
Shift-and-add, LW6424.77×22.05×19.76× to 24.16×
Shift-and-add, LW25624.94×22.20×19.89× to 24.33×
Shift-and-add, LB1619.22×17.11×15.33× to 18.75×
Shift-and-add, LB6423.93×21.30×19.09× to 23.34×
Shift-and-add, LB25624.06×21.42×19.20× to 23.48×

Figure 3

DOT4 lowers median Fmax by 4.43 MHz (11%); the route seed alone moves it up to 4.6 MHz

DOT4 lowers median Fmax by 4.43 MHz (11%); the route seed alone moves it up to 4.6 MHzDOT4 off (chparam DOT4=0): seed 1 40.25, seed 2 40.10, seed 3 43.25, seed 4 40.65, seed 5 40.37 MHz; median 40.37. DOT4 on (chparam DOT4=1): seed 1 35.59, seed 2 34.51, seed 3 36.42, seed 4 35.94, seed 5 39.12 MHz; median 35.94. Shipped netlist (make timing, DOT4=1): seed 1 37.03, seed 2 36.05, seed 3 34.63, seed 4 35.85, seed 5 37.60 MHz; median 36.05DOT4 offchparam DOT4=0median 40.37DOT4 onchparam DOT4=1median 35.94Shipped netlistmake timing, DOT4=1median 36.05343638404244Post-route Fmax estimate, MHz. Axis starts at 33 MHz.−4.43 MHz (−11%)3 netlists x 5 nextpnr seeds. Each dot is one seed.
DOT4 lowers median Fmax by 4.43 MHz (11%); the route seed alone moves it up to 4.6 MHzDOT4 off (chparam DOT4=0): seed 1 40.25, seed 2 40.10, seed 3 43.25, seed 4 40.65, seed 5 40.37 MHz; median 40.37. DOT4 on (chparam DOT4=1): seed 1 35.59, seed 2 34.51, seed 3 36.42, seed 4 35.94, seed 5 39.12 MHz; median 35.94. Shipped netlist (make timing, DOT4=1): seed 1 37.03, seed 2 36.05, seed 3 34.63, seed 4 35.85, seed 5 37.60 MHz; median 36.05DOT4 offchparam DOT4=0median 40.37DOT4 onchparam DOT4=1median 35.94Shipped netlistmake timing, DOT4=1median 36.05343638404244Post-route Fmax estimate, MHz. Axis starts at 33 MHz.−4.43 MHz (−11%)3 netlists x 5 nextpnr seeds. Each dot is one seed.
Compare the two yellow medians. DOT4 on and off are the same flow with only the DOT4 parameter changed. The shipped netlist has the same logic as DOT4 on but synthesizes 132 more LUT4 and routes -0.11 MHz differently: that is tool noise, not design. Quote the median, 36.05 MHz for the shipped netlist, not one seed. All 15 routes clear the 12 MHz constraint. Post-route estimates on iCE40 HX8K; no board run.
Data table
NetlistSeed 1Seed 2Seed 3Seed 4Seed 5Median
DOT4 off (chparam DOT4=0)40.2540.1043.2540.6540.3740.37
DOT4 on (chparam DOT4=1)35.5934.5136.4235.9439.1235.94
Shipped netlist (make timing, DOT4=1)37.0336.0534.6335.8537.6036.05

Figure 4

DOT4 adds 904 logic cells (19% more); the shipped core fills 72.1% of the HX8K

DOT4 adds 904 logic cells (19% more); the shipped core fills 72.1% of the HX8KDOT4 off (chparam DOT4=0): 4,767 of 7,680 logic cells. DOT4 on (chparam DOT4=1): 5,671 of 7,680 logic cells. Shipped netlist (make timing, DOT4=1): 5,539 of 7,680 logic cellsDOT4 offchparam DOT4=04,767 62.1%DOT4 onchparam DOT4=15,671 73.8%+904Shipped netlistmake timing, DOT4=15,539 72.1%01,0002,0003,0004,0005,0006,0007,680iCE40 HX8K logic cells after packing. The outline is the device: 7,680.
DOT4 adds 904 logic cells (19% more); the shipped core fills 72.1% of the HX8KDOT4 off (chparam DOT4=0): 4,767 of 7,680 logic cells. DOT4 on (chparam DOT4=1): 5,671 of 7,680 logic cells. Shipped netlist (make timing, DOT4=1): 5,539 of 7,680 logic cellsDOT4 offchparam DOT4=04,767 62.1%DOT4 onchparam DOT4=15,671 73.8%+904Shipped netlistmake timing, DOT4=15,539 72.1%02,0004,0007,680iCE40 HX8K logic cells after packing.The outline is the device: 7,680.
The yellow segment is the DOT4 unit: four signed 8×8 multipliers and an adder tree built from LUTs, since the iCE40 has no hard DSP blocks. Bars start at zero; the outline is the whole device. The count covers the core and its registered timing shell, not program or data memory.
Data table
NetlistLogic cellsShare of 7,680LUT4
DOT4 off (chparam DOT4=0)4,76762.1%3,176
DOT4 on (chparam DOT4=1)5,67173.8%4,144
Shipped netlist (make timing, DOT4=1)5,53972.1%4,012

Figure 5

Generated hazard programs hit all 327 of 327 producer, consumer and distance bins

Generated hazard programs hit all 327 of 327 producer, consumer and distance bins7 producer panels of 14 consumer slots by distances 1, 2, 3, plus 21 x0 bins and 12 redirect bins. 327 hit, 0 missing.distancealu.rs1alu.rs2alu.bothbranch.rs1branch.rs2branch.bothjalr.rs1store.addrstore.datastore.bothdot4.rs1dot4.rs2dot4.bothload.addralu123load123jal123jalr123lui123auipc123dot4123distancealuloadjaljalrluiauipcdot4x0 reads123wrong path reads recentwrong path load usewrong path storetarget reads recentRedirectsbranchjaljalr
Generated hazard programs hit all 327 of 327 producer, consumer and distance bins7 producer panels of 14 consumer slots by distances 1, 2, 3, plus 21 x0 bins and 12 redirect bins. 327 hit, 0 missing.distancealu.rs1alu.rs2alu.bothbranch.rs1branch.rs2branch.bothjalr.rs1store.addrstore.datastore.bothdot4.rs1dot4.rs2dot4.bothload.addralu123load123jal123jalr123distancealu.rs1alu.rs2alu.bothbranch.rs1branch.rs2branch.bothjalr.rs1store.addrstore.datastore.bothdot4.rs1dot4.rs2dot4.bothload.addrlui123auipc123dot4123distancealuloadjaljalrluiauipcdot4x0 reads123wrong path reads recentwrong path load usewrong path storetarget reads recentRedirectsbranchjaljalr
One panel per producer. Rows are the consumer slot that reads the register; columns are how many instructions later it reads (1 to 3). A filled square is a bin some generated program hit; an empty outline would be a miss, and there are 0. The report stores the hit count and the list of missing bins, not a per-bin count, so the grid can show hit or miss but not how often. Bin names come from tests/hazards.py, whose hash matches the one recorded in the report.
Data table
PanelBins hitBins
Producer alu4242
Producer load4242
Producer jal4242
Producer jalr4242
Producer lui4242
Producer auipc4242
Producer dot44242
Writes to x02121
Redirects1212
Total327327

Figure 6

Every check passes; 73 of 73 core line points are covered

ResultCheckWhat was compared
253 passPrograms against the Python reference model11,928 retired instructions, run in Icarus and Verilator. Invariants checked on 38,603 cycles. 46/46 instruction-class bins hit.
39/39 testsSpike, riscv-arch-test 3.10.012,720 signature words equal Spike. RV32I suite; not ACT4 and not certification.
50 programsRandom programs against Spike16,908 retirements and 5,091 store bytes equal the Spike commit log.
327/327 hitHazard cross bins108 generated hazard programs. Figure 5 shows every bin.
38/38Deliberate RTL bugs caught8 by invariants, 8 by hazard programs, 7 by Spike tests, 4 by invariants, 11 by invariants.
262,144 casesDOT4 lane sweepEvery signed byte pair in each lane; checksum equal to the oracle in Verilator and Icarus. Of 1,024 custom-0 encodings only DOT4 executes; the other 1,023 trap.
100.00%Core line coverage73/73 Verilator 5.020 line points in rv32_core.sv. 0 uncovered.