diff --git a/README.md b/README.md index 7fc55cf0..473c31f2 100644 --- a/README.md +++ b/README.md @@ -357,15 +357,15 @@ controller calibrates, so software never observes an uninitialized main memory. | Resource | Used | Available | Util% | |----------|-----:|----------:|------:| -| CLB LUTs | 158,254 | 1,029,600 | 15.4% | -| LUT as Logic | 137,072 | 1,029,600 | 13.3% | +| CLB LUTs | 157,437 | 1,029,600 | 15.3% | +| LUT as Logic | 136,254 | 1,029,600 | 13.2% | | LUT as Distributed RAM | 19,914 | — | — | -| LUT as Shift Register | 1,268 | — | — | -| CLB Registers | 121,955 | 2,059,200 | 5.9% | +| LUT as Shift Register | 1,269 | — | — | +| CLB Registers | 119,092 | 2,059,200 | 5.8% | | Block RAM Tile | 246 | 2,112 | 11.7% | | URAM | 64 | 352 | 18.2% | | DSPs | 31 | 1,320 | 2.4% | -| CARRY8 | 4,924 | 128,700 | 3.8% | +| CARRY8 | 4,685 | 128,700 | 3.6% | | F7 Muxes | 1,206 | 514,800 | 0.2% | | F8 Muxes | 548 | 257,400 | 0.2% | | Bonded IOB | 132 | 364 | 36.3% | @@ -376,11 +376,11 @@ controller calibrates, so software never observes an uninitialized main memory. | Resource | Used | Available | Util% | |----------|-----:|----------:|------:| -| Slice LUTs | 139,934 | 203,800 | 68.7% | -| LUT as Logic | 118,255 | 203,800 | 58.0% | -| LUT as Distributed RAM | 20,752 | — | — | -| LUT as Shift Register | 927 | — | — | -| Slice Registers | 95,899 | 407,600 | 23.5% | +| Slice LUTs | 141,383 | 203,800 | 69.4% | +| LUT as Logic | 119,598 | 203,800 | 58.7% | +| LUT as Distributed RAM | 20,860 | — | — | +| LUT as Shift Register | 925 | — | — | +| Slice Registers | 96,160 | 407,600 | 23.6% | | Block RAM Tile | 225 | 445 | 50.6% | | DSPs | 32 | 840 | 3.8% | | F7 Muxes | 1,096 | 101,900 | 1.1% | diff --git a/fpga/hw_regression.py b/fpga/hw_regression.py index 4dc8ef8e..253ad0b1 100755 --- a/fpga/hw_regression.py +++ b/fpga/hw_regression.py @@ -121,8 +121,8 @@ # (977 CoreMark at 300 MHz, 3.26 CoreMark/MHz), the same figure the README # headline cites. Re-arm from the next hardware run. BASELINE_SCORES: dict[str, dict[str, float | None]] = { - "x3": {"coremark": 977.13, "coremark_pro": 111.14}, - "genesys2": {"coremark": 433.65, "coremark_pro": 39.0}, + "x3": {"coremark": 977.13, "coremark_pro": 131.22}, + "genesys2": {"coremark": 436.17, "coremark_pro": 45.32}, } # Default allowed drop below baseline (percent). FROST is cycle-deterministic diff --git a/hw/rtl/cpu_and_mem/cpu/if_stage/pc_controller.sv b/hw/rtl/cpu_and_mem/cpu/if_stage/pc_controller.sv index c489a018..138bd07d 100644 --- a/hw/rtl/cpu_and_mem/cpu/if_stage/pc_controller.sv +++ b/hw/rtl/cpu_and_mem/cpu/if_stage/pc_controller.sv @@ -618,10 +618,38 @@ module pc_controller #( end end + // The target-handoff consume must ride the same !fetch_stall enable as the + // pc_reg flop it hands off to. An ungated consume during a stall discards + // the pending target while pc_reg is frozen: pc_reg then advances + // SEQUENTIALLY past the predicted-taken branch while fetch follows the + // target, and the aligner serves target-path bytes under sequential + // pc_reg PCs. Downstream decode faithfully manufactures phantom + // instructions from that pairing (a non-branch can dispatch as a + // taken branch, "mispredict", and redirect the machine to a garbage + // address). The crossing arm needs no gate: it consumes implicitly via + // stale_pending_prediction only after pc_reg really advances. assign clear_pending_prediction_state = - redirect_kill_pending_q || pending_prediction_target_handoff || + redirect_kill_pending_q || (pending_prediction_target_handoff && !fetch_stall) || stale_pending_prediction; +`ifndef SYNTHESIS + // The un-stalled handoff consume assumes the target arm actually wins the + // next_pc_reg mux. The redirect arms are fine (they kill the pending state + // themselves), but the three non-redirect arms above the target arm would + // consume without applying — the same desync this consume gate fixes, via + // a different door. Keep that assumption observable. + always_comb begin + if (pending_prediction_target_handoff && !fetch_stall && !i_reset && !$isunknown( + {i_window_cannot_serve, i_slot2_prediction_used_for_pc, + o_pending_prediction_target_holdoff} + )) begin + p_handoff_consume_implies_apply : + assert (!i_window_cannot_serve && !i_slot2_prediction_used_for_pc && + !o_pending_prediction_target_holdoff); + end + end +`endif + // Express the valid bit as clear/enable/set control rather than a full // next-state mux on D. The priority is unchanged, but Vivado can map the // clear and hold portions onto flop control pins and keep the prediction arm @@ -823,4 +851,6 @@ module pc_controller #( end `endif + + endmodule : pc_controller diff --git a/sw/apps/coremark_pro/Makefile b/sw/apps/coremark_pro/Makefile index 47fca21b..9118c286 100644 --- a/sw/apps/coremark_pro/Makefile +++ b/sw/apps/coremark_pro/Makefile @@ -107,9 +107,19 @@ ABI := $(FROST_FP_ABI) # FROST port does the same). FPGA_CPU_CLK_FREQ ?= 30000000 -# Base flags shared by every translation unit (matches the FROST project flags -# and the upstream util/make/gccrv32.mak "FROST sizing" toolchain file). -BASE_FLAGS := -march=$(ARCH) -mabi=$(ABI) -O2 \ +# Optimization level for every CoreMark-PRO translation unit and the link. +# -O3 matches the rest of the FROST software tree (sw/common/common.mk +# OPT_LEVEL default, the plain-CoreMark port). EEMBC's shipped GCC toolchain +# files (util/make/gcc*.mak) all use -O2, but that is reference-toolchain +# convention, not a rule: the coremark-pro README "Run Rules" allow any +# toolchain/build options provided every workload is compiled and linked with +# the SAME flags, disclosed with published scores. Uniformity holds by +# construction here (this feeds BASE_FLAGS, shared by every TU and the link). +# Overridable for A/B runs: `make OPT_LEVEL=-O2` or OPT_LEVEL=-O2 in the env. +OPT_LEVEL ?= -O3 + +# Base flags shared by every translation unit and the final link. +BASE_FLAGS := -march=$(ARCH) -mabi=$(ABI) $(OPT_LEVEL) \ -nostdlib -nostartfiles -ffreestanding \ -ffunction-sections -fdata-sections -fno-strict-aliasing \ -DCOREMARK_PRO_TRACE=$(COREMARK_PRO_TRACE) @@ -431,8 +441,10 @@ MITH_DEFINES := -DHOST_EXAMPLE_CODE=1 \ # GCC 15 defaults to C23, where implicit declarations / int<->ptr conversions # are hard errors. The MITH AL and several benchmark kernels use POSIX # prototypes / K&R idioms they expect the system to supply; build them as gnu11 -# and downgrade those to warnings (then silence with -w), as the upstream -# gccrv32.mak does. GCC 14+ additionally promotes -Wincompatible-pointer-types +# and downgrade those to warnings (then silence with -w; EEMBC's own gcc*.mak +# files build this era of code as gnu99, and the user guide's errata appendix +# lists the surviving warnings as expected and safely ignorable). +# GCC 14+ additionally promotes -Wincompatible-pointer-types # / -Wint-conversion to *errors* that -w does NOT downgrade, so demote them # explicitly. MITH_COMPAT := -std=gnu11 -w -fsigned-char \ diff --git a/sw/apps/software_registry.py b/sw/apps/software_registry.py index fb6b87ef..77705fcb 100644 --- a/sw/apps/software_registry.py +++ b/sw/apps/software_registry.py @@ -67,49 +67,52 @@ def hardware_validation_run_args(self) -> str: app_name="coremark_pro_core", workload="core", description="CoreMark-PRO core workload", - # One iteration measured 24.231s on X3 / 54.520s on genesys2. + # -O3: one iteration measured 24.927s on X3 / 56.087s on genesys2. hardware_iterations={"x3": 1, "genesys2": 1}, ), CoremarkProProgram( app_name="coremark_pro_cjpeg", workload="cjpeg-rose7-preset", description="CoreMark-PRO JPEG compression workload", - # X3: 49 iters measured 10.242s (41 fell short at 8.571s). - # genesys2: 22 iters measured 10.347s (18 fell short at 8.467s). - hardware_iterations={"x3": 49, "genesys2": 22}, + # -O3: X3 5.176 iter/s (71 iters measured 13.717s) -> 54 ~= 10.4s. + # genesys2 2.300 iter/s (32 iters measured 13.911s) -> 24 ~= 10.4s. + hardware_iterations={"x3": 54, "genesys2": 24}, ), CoremarkProProgram( app_name="coremark_pro_linear_alg", workload="linear_alg-mid-100x100-sp", description="CoreMark-PRO LINPACK single-precision workload", - # X3: 24 iters measured 10.248s (12 fell short at 5.124s). - # genesys2: 11 iters measured 10.568s (4 fell short at 3.843s). - hardware_iterations={"x3": 24, "genesys2": 11}, + # -O3: X3 3.091 iter/s (37 iters measured 11.970s) -> 32 ~= 10.4s. + # genesys2 1.374 iter/s (17 iters measured 12.375s) -> 14 ~= 10.2s. + hardware_iterations={"x3": 32, "genesys2": 14}, ), CoremarkProProgram( app_name="coremark_pro_loops", workload="loops-all-mid-10k-sp", description="CoreMark-PRO Livermore loops single-precision workload", # ~6 MiB heap, satisfied by the DDR-backed cached region (heap ~1 GiB). - # X3: 2 iterations measured 17.284s (1 fell short at 8.635s). - # genesys2: one iteration measured 24.732s. + # -O3: X3 2 iterations measured 16.440s (1 falls short at ~8.2s). + # genesys2: one iteration measured 23.742s. hardware_iterations={"x3": 2, "genesys2": 1}, ), CoremarkProProgram( app_name="coremark_pro_nnet", workload="nnet_test", description="CoreMark-PRO neural net workload", - # One iteration measured 16.251s on X3 / 36.564s on genesys2. - hardware_iterations={"x3": 1, "genesys2": 1}, + # -O3: X3 2 iterations measured 19.591s -- one iteration would run + # ~9.8s, under the floor (FP64-heavy; the 64-bit data tier sped this + # workload ~1.66x). genesys2: one iteration measured 22.051s. + hardware_iterations={"x3": 2, "genesys2": 1}, ), CoremarkProProgram( app_name="coremark_pro_parser", workload="parser-125k", description="CoreMark-PRO XML parser workload", - # Parser runtime is heap-size sensitive (per-iteration isn't constant). - # X3: 18 iters measured 10.358s (17 fell short at 9.821s). - # genesys2: 4 iters measured 11.066s (3 fell short at 9.039s). - hardware_iterations={"x3": 18, "genesys2": 4}, + # Parser runtime is heap-size sensitive (per-iteration isn't constant), + # so both counts keep extra margin above the usual ~10.4s target. + # -O3: X3 1.786 iter/s (26 iters measured 14.555s) -> 19 ~= 10.6s. + # genesys2 0.398 iter/s (6 iters measured 15.064s) -> 5 ~= 12.6s. + hardware_iterations={"x3": 19, "genesys2": 5}, ), CoremarkProProgram( app_name="coremark_pro_radix2", @@ -117,26 +120,28 @@ def hardware_validation_run_args(self) -> str: description="CoreMark-PRO radix-2 FFT workload", # The ~800 KiB of constant FFT data is placed in the cached region # (.ddr_rodata via the unified linker) and delivered through the - # sw_ddr.mem image. X3: 63 iters measured 10.165s (61 fell short at - # 9.842s). genesys2: 11 iters measured 10.201s. - hardware_iterations={"x3": 63, "genesys2": 11}, + # sw_ddr.mem image. -O3: X3 10.475 iter/s (98 iters measured 9.356s, + # under the floor -- the 64-bit data tier + -O3 sped this 1.69x) -> + # 110 ~= 10.5s. genesys2 1.423 iter/s (17 iters measured 11.945s) + # -> 15 ~= 10.5s. + hardware_iterations={"x3": 110, "genesys2": 15}, ), CoremarkProProgram( app_name="coremark_pro_sha", workload="sha-test", description="CoreMark-PRO SHA-256 workload", - # X3: 103 iters measured 10.104s (75 fell short at 7.357s). - # genesys2: 44 iters measured 10.176s (33 fell short at 7.632s). - hardware_iterations={"x3": 103, "genesys2": 44}, + # -O3: X3 10.516 iter/s (150 iters measured 14.264s) -> 110 ~= 10.5s. + # genesys2 4.461 iter/s (64 iters measured 14.347s) -> 46 ~= 10.3s. + hardware_iterations={"x3": 110, "genesys2": 46}, ), CoremarkProProgram( app_name="coremark_pro_zip", workload="zip-test", description="CoreMark-PRO zlib workload", - # ~3.3 MiB heap, satisfied by the DDR-backed cached region. Measured at - # -v0: X3 21 iters took 10.470s (18 fell short at 8.975s). genesys2 - # 9 iters took 11.077s (7 fell short at 8.618s). - hardware_iterations={"x3": 21, "genesys2": 9}, + # ~3.3 MiB heap, satisfied by the DDR-backed cached region. + # -O3: X3 2.083 iter/s (30 iters measured 14.401s) -> 22 ~= 10.6s. + # genesys2 0.840 iter/s (12 iters measured 14.288s) -> 9 ~= 10.7s. + hardware_iterations={"x3": 22, "genesys2": 9}, ), )