JPEG Streaming baseline JPEG compressor
A fixed-function image-compression accelerator on the chassis stream interface: grayscale pixels stream in under a valid/ready handshake, a standards-conformant baseline JPEG byte stream streams out. This is PPABench's DSP / streaming track — a real datapath (DCT, quantization, entropy coding) graded on rate-distortion and PPA, distinct from the instruction-stream MCU tracks.
Overview
The JPEG benchmark asks an agent to design a baseline (sequential DCT, Huffman) JPEG encoder
that hardens into the same locked user_project_wrapper as every other PPABench task, but
exercises the stream interface rather than the CPU's QSPI-flash / UART path. There is no
firmware to boot and no instruction stream to retire: image data is pushed in word-by-word over a
source-ready / destination-ready (srdy/drdy) handshake, and the compressed
JFIF byte stream is pulled out over the symmetric output handshake. The design is a pure hardware
pipeline.
This is deliberately a different region of the design space from the microcontroller tracks. MCU A fixes the RV32I ISA and rewards microarchitecture (caches, pipelining) on a deterministic instruction stream; MCU B opens the ISA and grades verify-by-result over random AES inputs. JPEG instead rewards a streaming fixed-function accelerator: throughput per cycle, energy per pixel, and a rate-distortion operating point — the kind of block (codec, ISP, NPU tile) that dominates the area of real SoCs.
The pipeline
A conformant baseline JPEG encoder is a fixed sequence of transforms. Each stage consumes the previous stage's output and applies its own back-pressure upstream, so a stall anywhere (most often the variable-length entropy coder draining slower than the front end fills) propagates cleanly back to the pixel input without dropping or duplicating a sample.
Stage by stage
- 8×8 block buffering. Pixels arrive in raster order but JPEG operates on 8×8 blocks, so the front end buffers enough rows to emit complete blocks (and level-shifts each sample by −128 to center it). The block buffer is the natural place to absorb input bursts and assert drdy low when the downstream pipe is busy.
- 2-D forward DCT. The separable 8×8 DCT-II is computed as a 1-D DCT across rows, a transpose, then a 1-D DCT down columns. A fast factorization (Loeffler / Chen-class, ~11 multiplies per 1-D pass) keeps the multiplier count and critical path low; fixed-point precision must be high enough that rounding error does not push the decoded PSNR below target.
- Quantization. Each of the 64 frequency coefficients is divided by the corresponding entry of the standard JPEG luma quantization table, scaled by a quality factor / QP supplied at runtime through Wishbone CFG. This is the single knob that trades file size against PSNR and defines the rate-distortion point the design is graded at.
- Zig-zag scan. The quantized 8×8 block is read out in the canonical zig-zag order so that low-frequency (typically non-zero) coefficients precede high-frequency (typically zeroed) ones, producing the long zero runs the entropy stage compresses.
- Run-length + Huffman entropy coding. The DC coefficient is differentially coded against the previous block's DC; the 63 AC coefficients are encoded as (run-of-zeros, size) symbols. Both are mapped through the baseline JPEG Huffman tables into a variable-length bitstream. This stage is the throughput bottleneck and the primary source of back-pressure: its output rate is data-dependent.
- JFIF byte stream. The packer wraps the entropy-coded scan in a valid JFIF container — SOI, APP0, the quantization (DQT) and Huffman (DHT) tables actually used, the frame header (SOF0), the scan header (SOS), the entropy data with 0xFF→0xFF 0x00 byte-stuffing, and EOI — and presents it byte-by-byte on the output handshake.
Interface & protocol
The design plugs into the locked chassis pinout. JPEG uses the chassis stream pins plus the standard control/config/status registers:
- Pixel input port —
pixel_in[7:0]with srdy (source has a valid pixel) and drdy (the design can accept it). Grayscale, 8 bits/sample, raster order. - Byte output port —
byte_out[7:0]with the symmetric srdy (a valid JFIF byte is available) and drdy (the sink will take it). The harness is the sink and varies its drdy to apply back-pressure. - CFG (Wishbone) — carries the quality factor / QP and image dimensions for the run; this is how the quantization scale is set without re-synthesizing the design.
- CTRL.start — kicks off a frame; STATUS.done signals the encoder has flushed the final EOI byte. As on the other tracks, the graded cycle count is taken externally from CTRL.start to STATUS.done, never from a DUT-internal register.
This pinout and the handshake semantics are part of the wrapper's locked contract: submissions may not change the boundary, only what happens inside it.
Grading — three axes
A submission is scored on three independent axes. All three must clear their bars for a pass; ranking among passing designs is by efficiency (energy and area at the achieved throughput and quality).
1 · Functional / quality
The emitted bitstream must be a valid JPEG that round-trips through a standard reference decoder (the harness decodes the captured byte stream with an off-the-shelf JPEG library). The decoded image is then compared against the source, and its PSNR must meet the per-run target. A bitstream that the reference decoder rejects — or that decodes but lands below the PSNR bar — fails functional grading outright. This couples correctness to a real decoder rather than to a bit-pattern the design could be tuned to emit.
2 · Rate
At the target quality the design's output is measured as bits-per-pixel and equivalently a compression ratio against the raw 8-bit input. Quality and rate together define a single rate-distortion operating point: a design is credited for hitting the required PSNR at a competitive bitrate, not for cranking quality at the cost of file size or vice-versa.
3 · PPA + throughput
The hardened design carries the full PPA suite — area, power, fmax, DRC-clean, and LVS-match — exactly like the MCU tracks. On top of that, the stream track adds two throughput metrics measured over the externally-timed run: sustained pixels/cycle (how much of the image the pipeline absorbs per clock under back-pressure) and energy/pixel in pJ, the energy efficiency of the accelerator on real image data.
Anti-cheat
Because the JPEG task has a compact, inspectable output, its anti-cheat is structural rather than incidental:
- Random / held-out images per run. The test images are supplied fresh per run and are not published. A design cannot precompute or hardcode a byte stream, because it has never seen the pixels it will be asked to compress.
- Must decode in a reference decoder. Grading runs the captured bytes through a standard JPEG decoder. Emitting plausible-looking garbage that merely resembles a JPEG fails: the container, table, and entropy structure all have to be correct enough for an independent decoder to reconstruct the image, and the reconstruction has to clear the PSNR bar.
- Externally-timed throughput. Pixels/cycle and energy/pixel are derived from the wrapper-boundary cycle count (start→done), so a design cannot under-report cycles from inside the DUT.
Why JPEG
The MCU tracks measure a processor running an instruction stream. JPEG measures something the processor tracks structurally cannot: a streaming datapath with a transform core (the 2-D DCT), a quantization stage, and a variable-rate entropy coder, graded on a rate-distortion trade-off and on throughput and energy efficiency over image data. That is exactly the shape of the fixed-function accelerators — codecs, ISPs, crypto engines, NN tiles — that occupy most of the silicon in a modern SoC. Including it keeps PPABench from rewarding only general-purpose-CPU design and lets the benchmark probe whether an agent can build a real, pipelined, back-pressure-correct accelerator.
Tracked metrics
The JPEG track reports the following. The reference row is marked pending until a clean chassis run closes.
JPEG KPIs — definitions
metric — definition · reference baseline pending| Metric | Definition | Reference |
|---|---|---|
psnr_db | Peak signal-to-noise ratio (dB) of the reference-decoded image vs. the source; the distortion axis. | pending |
bpp | Output bits per input pixel at the target quality; the rate axis. | pending |
compression_ratio | Raw input size ÷ compressed output size (equivalently 8 ÷ bpp for 8-bit input). | pending |
pixels_per_cycle | Sustained input pixels accepted per clock under back-pressure (externally timed). | pending |
energy_per_pixel_pj | Energy in pJ to compress one pixel = total_power × cycles / f ÷ pixels. | pending |
area_um2 | Routed cell area on sky130 HD (µm²). | pending |
total_power_mw | Total power (dynamic + leakage) at the operating clock (mW). | pending |
fmax_mhz | Maximum clock from post-route STA (MHz); WNS ≥ 0 required to pass. | pending |
What a strong submission looks like
The headroom on this track is in the datapath, not in firmware. A competitive design tends to:
- Pipeline the DCT with a fast factorization (e.g. Loeffler) so the transform core sustains one block on a short critical path instead of a long multiply-heavy combinational chain — this is usually what sets fmax.
- Process blocks in parallel where area budget allows — multiple DCT lanes or a deeper pipeline — to push pixels/cycle up without raising energy/pixel.
- Build an efficient entropy coder that keeps up with the front end so the variable-rate Huffman stage is not a throughput floor, with enough output buffering to ride out byte-stuffing bursts.
- Tune fixed-point precision in the DCT and quantizer to land the PSNR target with the least hardware — over-precision wastes area and power, under-precision fails quality.
- Trade area against throughput and quality deliberately: the winning point is the lowest energy/pixel and area that still clears the PSNR and bitrate bars at the run's quality setting.
PPABench v0.1 · JPEG streaming track (spec; reference run pending) · sky130 HD · grading: harden (OpenROAD/Magic/netgen) + reference-decoder round-trip + PSNR/bitrate + external timing · github.com/balbekov