Skip to content

Instantly share code, notes, and snippets.

@soronpo
Created August 5, 2026 12:22
Show Gist options
  • Select an option

  • Save soronpo/ed02da10b82ed44ee8a21f04cfc0a4ed to your computer and use it in GitHub Desktop.

Select an option

Save soronpo/ed02da10b82ed44ee8a21f04cfc0a4ed to your computer and use it in GitHub Desktop.
DFHDL #449 minimized - 8 files, 262 lines
import dfhdl.*
// unreferenced by anything; present in the compilation unit only
class abs(val DATA_WIDTH: Int <> CONST = 8) extends EDDesign:
val i = Bits(DATA_WIDTH) <> IN
val o = Bits(DATA_WIDTH) <> OUT
o <> i
import dfhdl.*
class binning(
val DATA_WIDTH: Int <> CONST = 8,
val IMAGE_WIDTH: Int <> CONST = 640,
val IMAGE_HEIGHT: Int <> CONST = 480
) extends EDDesign:
val clk, rst = Bit <> IN
val pixel_valid = Bit <> IN
val bin_ready = Bit <> IN
val pixel = Bits(DATA_WIDTH) <> IN
val bin_valid = Bit <> OUT
val pixel_ready = Bit <> OUT
val magnitude = UInt(DATA_WIDTH) <> OUT
val bin = UInt(4) <> OUT
bin_valid <> 0
magnitude <> 0
bin <> 0
// the only thing that matters here: lin_buff at a 4th parameter set
val image_line_buff = new lin_buff(
BUFFER_WIDTH = DATA_WIDTH, BUFFER_DEPTH = IMAGE_WIDTH,
BLOCK_WIDTH = 3, BLOCK_HEIGHT = 3)
image_line_buff.clk <> clk
image_line_buff.rst <> rst
image_line_buff.p_valid <> pixel_valid
image_line_buff.pixel <> pixel
image_line_buff.k_ready <> bin_ready
pixel_ready <> image_line_buff.p_ready
image_line_buff.k_border <> OPEN
image_line_buff.k_valid <> OPEN
image_line_buff.kernel <> OPEN
import dfhdl.*
class cell_histogram(
val DATA_WIDTH: Int <> CONST = 8,
val IMAGE_WIDTH: Int <> CONST = 640,
val INPUT_BIN_WIDTH: Int <> CONST = 11,
val OUTPUT_BIN_WIDTH: Int <> CONST = 14
) extends EDDesign:
val HISTOGRAM_WIDTH: Int <> CONST = OUTPUT_BIN_WIDTH * 10
val BINS: Int <> CONST = 9 + 1
val PARTIAL_HISTOGRAM_WIDTH: Int <> CONST = INPUT_BIN_WIDTH * BINS
val CELL_PARTIAL_HISTOGRAM_WIDTH: Int <> CONST = PARTIAL_HISTOGRAM_WIDTH * 8
// IMAGE_WIDTH (the edited parameter) reaches lin_buff through here
val CELLS_PER_ROW: Int <> CONST = IMAGE_WIDTH / 8
val clk, rst = Bit <> IN
val in_valid = Bit <> IN
val out_ready = Bit <> IN
val magnitude = UInt(DATA_WIDTH) <> IN
val bin_index = UInt(4) <> IN
val out_valid = Bit <> OUT
val in_ready = Bit <> OUT
val full_histogram = Bits(HISTOGRAM_WIDTH) <> OUT
out_valid <> 0
in_ready <> 0
val cell_buff = new lin_buff(
BUFFER_WIDTH = PARTIAL_HISTOGRAM_WIDTH, BUFFER_DEPTH = CELLS_PER_ROW,
BLOCK_WIDTH = 1, BLOCK_HEIGHT = 8)
cell_buff.clk <> clk; cell_buff.rst <> rst
cell_buff.p_valid <> in_valid; cell_buff.pixel <> all(0); cell_buff.k_ready <> out_ready
cell_buff.p_ready <> OPEN; cell_buff.k_border <> OPEN; cell_buff.k_valid <> OPEN
cell_buff.kernel <> OPEN
val histogram_adder = new partial_histogram_add(
INPUT_BIN_WIDTH = INPUT_BIN_WIDTH, OUTPUT_BIN_WIDTH = OUTPUT_BIN_WIDTH, BINS = BINS)
histogram_adder.partial_histogram <> all(0)
full_histogram <> histogram_adder.full_histogram
import dfhdl.*
class detection_window(
val IMAGE_WIDTH: Int <> CONST = 640,
val INPUT_WIDTH: Int <> CONST = 36,
val BLOCKS_PER_WINDOW: Int <> CONST = 32
) extends EDDesign:
val OUTPUT_WIDTH = INPUT_WIDTH * BLOCKS_PER_WINDOW
val clk, rst = Bit <> IN
val in_valid = Bit <> IN
val out_ready = Bit <> IN
val normalized_block = Bits(INPUT_WIDTH) <> IN
val out_valid = Bit <> OUT
val in_ready = Bit <> OUT
val detection_window = Bits(OUTPUT_WIDTH) <> OUT
val IMAGE_ROW_BLOCKS: Int <> CONST = IMAGE_WIDTH / 16
val WINDOW_ROW_BLOCKS: Int <> CONST = 64 / 16
val WINDOW_COLUMN_BLOCKS: Int <> CONST = 128 / 16
out_valid <> 0
val block_line_buffer = new lin_buff(
BUFFER_WIDTH = INPUT_WIDTH, BUFFER_DEPTH = IMAGE_ROW_BLOCKS,
BLOCK_WIDTH = WINDOW_ROW_BLOCKS, BLOCK_HEIGHT = WINDOW_COLUMN_BLOCKS)
block_line_buffer.clk <> clk
block_line_buffer.rst <> rst
block_line_buffer.p_valid <> in_valid
block_line_buffer.pixel <> normalized_block
block_line_buffer.k_ready <> out_ready
in_ready <> block_line_buffer.p_ready
block_line_buffer.k_border <> OPEN
block_line_buffer.k_valid <> OPEN
detection_window <> block_line_buffer.kernel.resize(OUTPUT_WIDTH)
import dfhdl.*
class hog(
val DATA_WIDTH: Int <> CONST = 8,
val IMAGE_WIDTH: Int <> CONST = 640,
val IMAGE_HEIGHT: Int <> CONST = 480,
val WINDOW_WIDTH: Int <> CONST = 32 * 36
) extends EDDesign:
val clk, rst = Bit <> IN
val pixel_valid = Bit <> IN
val window_ready = Bit <> IN
val pixel = Bits(DATA_WIDTH) <> IN
val window_valid = Bit <> OUT
val pixel_ready = Bit <> OUT
val detection_window = Bits(WINDOW_WIDTH) <> OUT
val HISTOGRAM_WIDTH: Int <> CONST = 10 * 14 // BINS * BIN_WIDTH
val NORM_BLOCK_WIDTH: Int <> CONST = 36
val magnitude = UInt(DATA_WIDTH) <> VAR
val bin = UInt(4) <> VAR
val cell_histogram = Bits(HISTOGRAM_WIDTH) <> VAR
val normalized_block = Bits(NORM_BLOCK_WIDTH) <> VAR
val bin_ready, bin_valid = Bit <> VAR
val cell_valid, cell_ready = Bit <> VAR
val block_valid, block_ready = Bit <> VAR
val u_binning = new binning(
DATA_WIDTH = DATA_WIDTH,
IMAGE_WIDTH = IMAGE_WIDTH,
IMAGE_HEIGHT = IMAGE_HEIGHT
)
u_binning.clk <> clk
u_binning.rst <> rst
u_binning.pixel_valid <> pixel_valid
u_binning.bin_ready <> bin_ready
u_binning.pixel <> pixel
bin_valid <> u_binning.bin_valid
pixel_ready <> u_binning.pixel_ready
magnitude <> u_binning.magnitude
bin <> u_binning.bin
val u_cell_histogram = new cell_histogram(
DATA_WIDTH = DATA_WIDTH,
IMAGE_WIDTH = IMAGE_WIDTH,
INPUT_BIN_WIDTH = 11,
OUTPUT_BIN_WIDTH = 14
)
u_cell_histogram.clk <> clk
u_cell_histogram.rst <> rst
u_cell_histogram.in_valid <> bin_valid
u_cell_histogram.out_ready <> cell_ready
u_cell_histogram.magnitude <> magnitude
u_cell_histogram.bin_index <> bin
cell_valid <> u_cell_histogram.out_valid
bin_ready <> u_cell_histogram.in_ready
cell_histogram <> u_cell_histogram.full_histogram
// the remaining parameters are left at their defaults, as in the gold
val u_norm_block = new norm_block(
IMAGE_WIDTH = IMAGE_WIDTH,
IMAGE_HEIGHT = IMAGE_HEIGHT
)
u_norm_block.clk <> clk
u_norm_block.rst <> rst
u_norm_block.in_valid <> cell_valid
u_norm_block.out_ready <> block_ready
u_norm_block.cell_histogram <> cell_histogram
block_valid <> u_norm_block.out_valid
cell_ready <> u_norm_block.in_ready
normalized_block <> u_norm_block.normalized_block
val u_detection_window = new detection_window(IMAGE_WIDTH = IMAGE_WIDTH)
u_detection_window.clk <> clk
u_detection_window.rst <> rst
u_detection_window.in_valid <> block_valid
u_detection_window.out_ready <> window_ready
u_detection_window.normalized_block <> normalized_block
window_valid <> u_detection_window.out_valid
block_ready <> u_detection_window.in_ready
// WINDOW_WIDTH is its own parameter here (32 * 36) while the child derives
// INPUT_WIDTH * BLOCKS_PER_WINDOW; both are 1152 but nothing ties them.
detection_window <> u_detection_window.detection_window.resize(WINDOW_WIDTH)
end hog
import dfhdl.*
class lin_buff(
val BUFFER_WIDTH: Int <> CONST = 8,
val BUFFER_DEPTH: Int <> CONST = 854,
val BLOCK_WIDTH: Int <> CONST = 3,
val BLOCK_HEIGHT: Int <> CONST = 3
) extends EDDesign:
val OUTPUT_WIDTH = BLOCK_WIDTH * BLOCK_HEIGHT * BUFFER_WIDTH
val MID_ROWS = BLOCK_HEIGHT - 2
val clk, rst = Bit <> IN
val p_valid = Bit <> IN
val pixel = Bits(BUFFER_WIDTH) <> IN
val k_ready = Bit <> IN
val p_ready = Bit <> OUT
val k_border = Bit <> OUT
val k_valid = Bit <> OUT
val kernel = Bits(OUTPUT_WIDTH) <> OUT
p_ready <> 0
k_border <> 0
k_valid <> 0
kernel <> all(0)
// structure-bearing loop retained; the child design is gone
for (i <- 0 until MID_ROWS)
val w = Bits(BUFFER_WIDTH) <> VAR
w <> pixel
import dfhdl.*
class norm_block(
val IMAGE_WIDTH: Int <> CONST = 640,
val IMAGE_HEIGHT: Int <> CONST = 480,
val BLOCK_ROW_CELLS: Int <> CONST = 2,
val BLOCK_COLUMN_CELLS: Int <> CONST = 2,
val BIN_WIDTH: Int <> CONST = 14,
val BINS: Int <> CONST = 9,
val CELLS_PER_BLOCK: Int <> CONST = 4
) extends EDDesign:
val HISTOGRAM_WIDTH: Int <> CONST = BIN_WIDTH * (BINS + 1)
val OUTPUT_WIDTH: Int <> CONST = BINS * CELLS_PER_BLOCK
val CELLS_PER_LINE: Int <> CONST = IMAGE_WIDTH / 8
val clk, rst = Bit <> IN
val in_valid = Bit <> IN
val out_ready = Bit <> IN
val cell_histogram = Bits(HISTOGRAM_WIDTH) <> IN
val out_valid = Bit <> OUT
val in_ready = Bit <> OUT
val normalized_block = Bits(OUTPUT_WIDTH) <> OUT
out_valid <> 0
normalized_block <> all(0)
val cell_line_buffer = new lin_buff(
BUFFER_WIDTH = HISTOGRAM_WIDTH, BUFFER_DEPTH = CELLS_PER_LINE,
BLOCK_WIDTH = BLOCK_ROW_CELLS, BLOCK_HEIGHT = BLOCK_COLUMN_CELLS)
cell_line_buffer.clk <> clk
cell_line_buffer.rst <> rst
cell_line_buffer.p_valid <> in_valid
cell_line_buffer.pixel <> cell_histogram
cell_line_buffer.k_ready <> out_ready
in_ready <> cell_line_buffer.p_ready
cell_line_buffer.k_border <> OPEN
cell_line_buffer.k_valid <> OPEN
cell_line_buffer.kernel <> OPEN
import dfhdl.*
class partial_histogram_add(
val INPUT_BIN_WIDTH: Int <> CONST = 11,
val OUTPUT_BIN_WIDTH: Int <> CONST = 14,
val BINS: Int <> CONST = 9, // number of bins in each histogram
val CELL_ROWS: Int <> CONST = 8 // number of rows per cell, number of partial histograms
) extends EDDesign:
val INPUT_WIDTH = INPUT_BIN_WIDTH * BINS * CELL_ROWS
val OUTPUT_WIDTH = OUTPUT_BIN_WIDTH * BINS
/** 8 partial histograms as 1 vector */
val partial_histogram = Bits(INPUT_WIDTH) <> IN
/** one full histogram output */
val full_histogram = Bits(OUTPUT_WIDTH) <> OUT
// The gold hardcodes the per-row byte offsets (99 == INPUT_BIN_WIDTH * BINS)
// rather than deriving them, so they are kept literal here too.
val ROW_OFFSETS = List(0, 99, 198, 297, 396, 495, 594, 693)
// Multi-bit slices of an output port are not connectable more than once
// (`<>` reports "multiple connections write to the same port"), so the
// per-bin writes are assignments inside a process instead.
process(all):
for (i <- 0 until BINS)
// the ith bin of the output is the sum of
// the ith bins of each row (partial histogram)
// Verilog evaluates the 8-term sum at the 14-bit target width, so each
// 11-bit addend is zero-extended first.
full_histogram.lsbitsAt(i * OUTPUT_BIN_WIDTH, OUTPUT_BIN_WIDTH) :=
ROW_OFFSETS
.map(off =>
partial_histogram
.lsbitsAt(i * INPUT_BIN_WIDTH + off, INPUT_BIN_WIDTH)
.resize(OUTPUT_BIN_WIDTH)
)
.reduce(_ + _)
end partial_histogram_add
#!/bin/bash
# DFHDL #449 minimized repro. MUST run in a directory that has never been
# compiled before -- copy this tree to a NEW path for each attempt (bloop's
# incremental state, keyed by project path, suppresses the crash on re-runs).
set -u
V=$(cat ~/.dfhdl_version)
b(){ scala run src/ --scala 3.8.4 --dep "io.github.dfianthdl::dfhdl::$V" \
--compiler-plugin "io.github.dfianthdl:::dfhdl-plugin::$V" -O -deprecation -M "$1" -- commit 2>&1; }
echo "1. prime with hog"; b hog >/tmp/1.log 2>&1 && echo " ok" || { tail -3 /tmp/1.log; exit 1; }
echo "2. build cell_histogram"; b cell_histogram >/tmp/2.log 2>&1 && echo " ok" || { tail -3 /tmp/2.log; exit 1; }
echo "3. change one parameter default"
sed -i 's/IMAGE_WIDTH: Int <> CONST = 640/IMAGE_WIDTH: Int <> CONST = 64/' src/cell_histogram.scala
echo "4. rebuild cell_histogram"
b cell_histogram >/tmp/3.log 2>&1 && echo " ok (did NOT reproduce - was this path used before?)" \
|| { grep -m1 NoSuchElementException /tmp/3.log; echo " REPRODUCED"; }
echo "5. clear dfhdl-cache, rebuild identical source"
find src/.scala-build -name dfhdl-cache -type d -exec rm -rf {} + 2>/dev/null
b cell_histogram >/tmp/4.log 2>&1 && echo " ok - same source elaborates fine once the cache is gone"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment