Add pixels-per-clock (1/2/4/8) support - #1
Merged
Conversation
Build-time PIXELS_PER_CLOCK (1/2/4/8) packs N adjacent pixels per AXI-Stream beat, lane 0 in the tdata LSBs. M1 scope: SOLID, GRID, CHECKER and the box overlay are beat-exact at PPC>1; stateful patterns still require PPC=1 and fail elaboration if enabled. PPC=1 is unchanged. Core: x advances by NPPC, width clamped to a multiple; per-lane grid/checker chains, box comparators, widened pipeline, reusable pack_pixel(); PPC mirrored RO at reg 0x30. Verify: model render_frame_beats() plus iverilog harness beat-exact across 36 configs. Verilator coverage gate still to be re-baselined.
Drop the BOX_IMG_X_STEP=0 "runtime toggle" paragraph.
Add per-lane support at PPC>1 for COLORBAR, HGRAD, VGRAD and RAMP, joining SOLID/GRID/CHECKER and the box overlay. Colorbar uses a bar-index chain plus a mode-aware palette function; hgrad/ramp use accumulator chains; vgrad is constant per beat so it replicates. The elaboration guard now only forbids NOISE and IMAGE/BOX_IMAGE at PPC>1 (M3). PPC=1 unchanged. Verify: iverilog<->model beat-exact across 36 configs x 7 patterns (252 checks), guard rejection, and PPC=1 regressions. Bump core to 0.3.0.
The per-lane pattern mux (pat_c*_bus) is only consumed by the PPC>1 pipeline lanes, so at PIXELS_PER_CLOCK=1 it was dead logic relying on synthesis DCE. Wrap it in `if (NPPC>1)` and start the lane loop at 1 (lane 0 always comes from the scalar mux), so the 1ppc netlist is provably free of the extra mux and no lane is duplicated at any PPC.
Add per-lane support at PPC>1 for the last patterns: NOISE, IMAGE and the
BOX_IMAGE overlay. NOISE uses a leap-ahead LFSR (feedback unrolled NPPC
times so lanes get consecutive states, base jumps one beat). IMAGE and
BOX_IMAGE replicate their source memory per lane with a combinational,
shift-free read (matches the reference model); the PPC=1 path keeps its
registered BRAM read and legacy 1-px shift, untouched. The elaboration
guard now only rejects an illegal PPC value -- no per-pattern restriction.
Verify: iverilog<->model beat-exact, 321 pattern-checks across ppc
{1,2,4,8} x mode {rgb,raw,yuv} x bpc {8,12,16}, IMAGE with 2x scaling and
a dedicated box-image overlay check. Smoke passes; bad-PPC guard fires.
Bump core to 0.4.0.
Add cocotb-driven PPC data-path verification Icarus-backed cocotb suite (run_ppc.py, test_ppc.py) drives the core over AXI-Lite and checks each AXIS beat against the reference model for PIXELS_PER_CLOCK 1/2/4/8 across RGB/RAW/YUV, all 8 patterns. Icarus is used since Verilator returns a sampled-once packed tdata. 12 suites pass. @
Add PPC resource sweep and refresh resource matrix run_matrix.py gains a `ppc` mode that synthesizes the all-patterns RGB-8b build over PIXELS_PER_CLOCK 1/2/4/8. README resource tables are regenerated against current RTL (they predated the line-gap feature): mode/BPC sweep, per-pattern deltas, and tiniest build all refreshed, and a new pixels-per-clock table added. Scaling is sub-linear: 8x throughput for +56% LUT / +36% FF. PPC=1 is unchanged from pre-feature RTL. @
Fix Verilator 5.020 CI lint/build failures CI uses apt Verilator 5.020, which the PPC RTL tripped three ways: - UNOPTFLAT on the per-lane chain arrays (bpc_l/cxc/hga_l/lf_l/etc.): add /* verilator split_var */ so each element is an independent net. - V3Number internal crash + out-of-range part selects on pack_pixel: rewrite packing as width-safe assigns + clamped left shifts (no zero-width replications, no dead-branch range errors). Byte-exact. - coverage_off on the mode/PPC dead-branch helpers (pack_pixel, bar_palette, pat_c*_bus, PPC readback) to restore the 100% gate. Verified on 5.020: lint, regression (100%), all_modes, check_seq. @
The stage-1 pipeline registers (pat_c*_s1, box_in_s1, box_img_*_s1, box_on_border_s1) were driven by two always blocks: the main FSM block wrote lane 0, a per-lane generate block wrote lanes 1..NPPC-1. Vivado treats each packed register as multi-driven (Synth 8-6858/8-6859), preserves the reset/GND driver for the upper lanes and drops the generate driver, zeroing lanes 1..N-1 in silicon. Simulators tolerate the non-overlapping slice writes, so this only surfaced on hardware. Collapse both into one always block with a constant-bound loop for the upper lanes. It unrolls to nothing at NPPC==1, so the 1ppc build stays byte-identical. Verified byte-exact on Arty A7-100T at PPC=4.
Enable the Arty A7-100T demo and self-test to validate pixels-per-clock > 1 on silicon: - frame_capture: serialize each TDATA_WIDTH-bit beat into ceil(W/32) little-endian 32-bit BRAM words; WPB==1 path unchanged. - demo_top: PIXELS_PER_CLOCK localparam (now 4); TDATA_WIDTH derived from it; forward the parameter to the IP. - clk_gen: parameterize CLKOUT0_DIVIDE. PPC>1 builds run the demo slower (per-lane counter chains do not close 130 MHz); correctness, not throughput, is the goal there. - run_hw_test: read back PPC, expand expected words via render_frame_beats at the beat width, validate serialized capture. Verified byte-exact across 9 patterns at PPC=4.
Note how to build/run the demo at pixels-per-clock > 1: set VTPGZ_PIXELS_PER_CLOCK in demo_top.v, rebuild, re-run. frame_capture serializes wide beats and run_hw_test checks byte-exact. PPC>1 uses a lower demo clock (per-lane chains do not close 130 MHz). PPC=4 verified byte-exact on the board.
The single-block s1 latch loop added for the multi-driver fix has zero iterations at NPPC==1, but unlike the old generate block it is not elaborated away, so its body counted as uncovered lines in the PPC=1 coverage sim (98%, 622/631). Wrap it in verilator coverage_off, matching the existing PPC>1-only pat_c*_bus logic. Coverage back to 100%; PPC>1 is covered by the cocotb data-path suite and the iverilog beat-exact gate.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds pixels-per-clock (PPC) support to the vtpgZero core: N=1/2/4/8 horizontally-adjacent pixels packed into a single AXI4-Stream beat (lane 0 = leftmost = tdata LSBs). All patterns (solid, grid, checker, colorbar, h/v-gradient, ramp, noise, image, box overlay) work at every PPC, byte-exact against the reference model.
What's included
PIXELS_PER_CLOCKbuild param; per-lane combinational pattern datapath gated behindPPC>1; single-driver stage-1 pipeline.frame_captureserializes wide beats into 32-bit words;run_hw_testreads back PPC and checks byte-exact. PPC=4 verified byte-exact across all 9 patterns on silicon.Notable fixes
pack_pixel(no zero-width replication), coverage_off on PPC>1-only dead code.Test status
Note: PPC>1 demo builds run at a lower clock (per-lane counter chains don't close 130 MHz); correctness, not throughput, is the goal there.