Skip to content

Instantly share code, notes, and snippets.

@soronpo
Last active August 5, 2026 11:54
Show Gist options
  • Select an option

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

Select an option

Save soronpo/f76eeee5f505e5885d933eaf74c93ee9 to your computer and use it in GitHub Desktop.
DFHDL #449 - stale elaboration cache repro (17 files + script)
import dfhdl.*
class abs(
val DATA_WIDTH: Int <> CONST = 8
) extends EDDesign:
val data_in1, data_in2 = Bits(DATA_WIDTH + 1) <> IN
val is_upper_bin = Bit <> OUT
val data_out1, data_out2 = Bits(DATA_WIDTH) <> OUT
val input_sign1, input_sign2 = Bit <> VAR
// assign the input MSBs as sign bits
input_sign1 <> data_in1(DATA_WIDTH)
input_sign2 <> data_in2(DATA_WIDTH)
// the orientation is in bins >4 when signs are different
is_upper_bin <> (input_sign1 != input_sign2)
data_out1 <> input_sign1.sel(
(~data_in1 + 1).lsbits(DATA_WIDTH),
data_in1.lsbits(DATA_WIDTH)
)
data_out2 <> input_sign2.sel(
(~data_in2 + 1).lsbits(DATA_WIDTH),
data_in2.lsbits(DATA_WIDTH)
)
end abs
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
val KERNEL_WIDTH: Int <> CONST = 3 * 3 * 8
val kernel_valid = Bit <> VAR
val kernel = Bits(KERNEL_WIDTH) <> VAR
val Gx, Gy = Bits(DATA_WIDTH + 1) <> VAR
val Gx_abs, Gy_abs = Bits(DATA_WIDTH) <> VAR
val k_valid, k_ready, k_border = Bit <> VAR
val is_upper_bin = Bit <> VAR
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 <> k_ready
pixel_ready <> image_line_buff.p_ready
k_border <> image_line_buff.k_border
k_valid <> image_line_buff.k_valid
// lin_buff's width is 3 * 3 * DATA_WIDTH; the gold's KERNEL_WIDTH hardcodes
// 3 * 3 * 8, so the two are equal only at the default DATA_WIDTH.
kernel <> image_line_buff.kernel.resize(KERNEL_WIDTH)
kernel_valid <> (k_valid && !k_border)
val u_hog_gradient = new hog_gradient(KERNEL_WIDTH = KERNEL_WIDTH)
u_hog_gradient.clk <> clk
u_hog_gradient.rst <> rst
u_hog_gradient.k_valid <> kernel_valid
u_hog_gradient.out_ready <> bin_ready
u_hog_gradient.kernel <> kernel
k_ready <> u_hog_gradient.k_ready
bin_valid <> u_hog_gradient.out_valid
// hog_gradient's Gx/Gy are a hardcoded 9 bits (as in the gold), while these
// wires are DATA_WIDTH+1
Gx <> u_hog_gradient.Gx.resize(DATA_WIDTH + 1)
Gy <> u_hog_gradient.Gy.resize(DATA_WIDTH + 1)
val u_abs = new abs(DATA_WIDTH = DATA_WIDTH)
u_abs.data_in1 <> Gx
u_abs.data_in2 <> Gy
is_upper_bin <> u_abs.is_upper_bin
Gx_abs <> u_abs.data_out1
Gy_abs <> u_abs.data_out2
val u_hog_magnitude = new hog_magnitude(DATA_WIDTH = DATA_WIDTH)
u_hog_magnitude.gx <> Gx_abs.uint
u_hog_magnitude.gy <> Gy_abs.uint
magnitude <> u_hog_magnitude.magnitude
val u_hog_orientation = new hog_orientation(DATA_WIDTH = DATA_WIDTH)
u_hog_orientation.gx <> Gx_abs.uint
u_hog_orientation.gy <> Gy_abs.uint
u_hog_orientation.is_upper_bin <> is_upper_bin
bin <> u_hog_orientation.bin_out
end binning
import dfhdl.*
class cell_histogram(
val DATA_WIDTH: Int <> CONST = 8, // magnitude data width
val IMAGE_WIDTH: Int <> CONST = 640, // default 480p, width in pixels
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 // changed from 9 to 10!
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
/** one full histogram output */
val full_histogram = Bits(HISTOGRAM_WIDTH) <> OUT
val BINS: Int <> CONST = 9 + 1 // extra bin for the sum
val PARTIAL_HISTOGRAM_WIDTH: Int <> CONST = INPUT_BIN_WIDTH * BINS
val CELL_PARTIAL_HISTOGRAM_WIDTH: Int <> CONST = PARTIAL_HISTOGRAM_WIDTH * 8
val CELLS_PER_ROW: Int <> CONST = IMAGE_WIDTH / 8
val row_histogram = Bits(PARTIAL_HISTOGRAM_WIDTH) <> VAR
val partial_histogram = Bits(CELL_PARTIAL_HISTOGRAM_WIDTH) <> VAR
val p_ready, p_valid, k_border = Bit <> VAR
val u_row_histogram = new row_histogram(
DATA_WIDTH = DATA_WIDTH,
BIN_WIDTH = INPUT_BIN_WIDTH
)
u_row_histogram.clk <> clk
u_row_histogram.rst <> rst
u_row_histogram.in_valid <> in_valid
u_row_histogram.out_ready <> p_ready
u_row_histogram.magnitude <> magnitude
u_row_histogram.bin_index <> bin_index
p_valid <> u_row_histogram.out_valid
in_ready <> u_row_histogram.in_ready
row_histogram <> u_row_histogram.row_histogram
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 <> p_valid
cell_buff.pixel <> row_histogram
cell_buff.k_ready <> out_ready
p_ready <> cell_buff.p_ready
k_border <> cell_buff.k_border
out_valid <> cell_buff.k_valid
partial_histogram <> cell_buff.kernel
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 <> partial_histogram
full_histogram <> histogram_adder.full_histogram
end cell_histogram
import dfhdl.*
/** Author : Mahmoud, date : 17/03/2023
*
* description: A custom fifo only used for line buffer implementation.
* Indicates illegal kernels (border cases), not meant to be read until it is
* full.
*/
class custom_fifo(
val DATA_WIDTH: Int <> CONST = 8,
val FIFO_DEPTH: Int <> CONST = 854,
val KERNEL_WIDTH: Int <> CONST = 3,
// boolean, is this fifo the first one in the line buffer?
val FIRST_LINE: Int <> CONST = 0
) extends EDDesign:
val clk, rst = Bit <> IN
val w_data = Bits(DATA_WIDTH) <> IN
val w_valid = Bit <> IN
val r_ready = Bit <> IN
val r_data = Bits(DATA_WIDTH) <> OUT
val r_valid = Bit <> OUT
val w_ready = Bit <> OUT
val fifo_full = Bit <> OUT
/** indicate the kernel is at the border and no operation is to be performed */
val border_flag = Bit <> OUT
val ADDR_WIDTH = clog2(FIFO_DEPTH)
// internal signals
val w_addr = UInt(ADDR_WIDTH) <> VAR
val r_addr = UInt(ADDR_WIDTH) <> VAR
val read_mem = Bit <> VAR
val read_offset = UInt(ADDR_WIDTH) <> VAR
val fifo_write = Bit <> VAR // memory port A write enable
// read state machine
enum FifoState extends Encoded:
case S_DISABLE // waiting for the fifo to be full
case S_READ // fifo is full and read is enabled
case S_PAUSE // backpressure due to in_valid or in_ready deassertion
import FifoState.*
val current_state, next_state = FifoState <> VAR
// border detection
val BORDER_COUNTER_MAX = KERNEL_WIDTH - 2
val BORDER_COUNTER_SIZE = clog2(BORDER_COUNTER_MAX)
val border_active = Bit <> VAR
val border_skip = Bit <> VAR // indicates skipping border condition
// counter is bigger than it needs to be... to avoid a declaration of [-1:0]
val border_cnt = UInt(BORDER_COUNTER_SIZE + 1) <> VAR
val border_start_addr = UInt(ADDR_WIDTH) <> VAR
// write address logic
// increment whenever data is written, w_addr is a mod(fifo_depth) counter.
process(clk.rising, rst.rising):
if (rst) w_addr :== 0
else if (w_valid && w_ready && w_addr == FIFO_DEPTH - 1) w_addr :== 0
else if (w_valid && w_ready) w_addr :== w_addr + 1
// fifo_full indicator
process(clk.rising, rst.rising):
if (rst) fifo_full :== 0
else if (w_valid && w_ready && w_addr == FIFO_DEPTH - 1) fifo_full :== 1
fifo_write <> (w_valid && w_ready)
process(all):
next_state := current_state
current_state match
case S_DISABLE =>
if (fifo_full && w_valid) next_state := S_READ
case S_READ =>
if (!w_valid || !w_ready) next_state := S_PAUSE
case S_PAUSE =>
if (w_valid && w_ready) next_state := S_READ
case _ => next_state := current_state
end match
process(clk.rising, rst.rising):
if (rst) current_state :== S_DISABLE
else current_state :== next_state
// expected to increment once only at the start of the read operation
process(clk.rising, rst.rising):
if (rst) read_offset :== 0
else if (current_state == S_DISABLE && next_state == S_READ)
read_offset :== read_offset + 1
w_ready <> r_ready
r_valid <> (next_state == S_READ)
// read_mem procedure
process(all):
read_mem := 1
if (current_state == S_READ && next_state == S_PAUSE) read_mem := 0
if (current_state == S_PAUSE && next_state != S_READ) read_mem := 0
// Verilog evaluates these sums at 32-bit width (FIFO_DEPTH is an unsized
// literal), so they cannot overflow before the comparison/subtraction.
// Carry adds reproduce that; a plain `+` would wrap at ADDR_WIDTH.
process(all):
val sum = w_addr +^ read_offset
val sum1 = (w_addr +^ read_offset) +^ 1
if (sum >= FIFO_DEPTH) r_addr := (sum - FIFO_DEPTH).resize(ADDR_WIDTH)
else r_addr := sum.resize(ADDR_WIDTH)
if (current_state == S_DISABLE && next_state == S_READ)
if (sum1 >= FIFO_DEPTH) r_addr := (sum1 - FIFO_DEPTH).resize(ADDR_WIDTH)
else r_addr := sum1.resize(ADDR_WIDTH)
else if (current_state == S_DISABLE)
r_addr := 0
process(clk.rising, rst.rising):
if (rst) border_start_addr :== KERNEL_WIDTH
else if (border_active && border_cnt == BORDER_COUNTER_MAX && w_valid && w_ready)
// `border_start_addr +^ KERNEL_WIDTH` would yield SInt[33] because an
// `Int <> CONST` does not adapt in a carry op (DFHDL#445). Binding it as
// an explicitly-width unsigned decimal keeps this UInt[ADDR_WIDTH+1].
// NB: the unsized `d"$KERNEL_WIDTH"` does NOT work; the width is required.
val bsum = border_start_addr +^ d"${ADDR_WIDTH}'$KERNEL_WIDTH"
if (bsum >= FIFO_DEPTH)
border_start_addr :== (bsum - FIFO_DEPTH).resize(ADDR_WIDTH)
else border_start_addr :== bsum.resize(ADDR_WIDTH)
// infers a latch in some instances, hence the else block
else border_start_addr :== border_start_addr
process(clk.rising, rst.rising):
if (rst) border_skip :== 0
else if (w_addr == border_start_addr && current_state != S_DISABLE && w_valid && w_ready)
border_skip :== ~border_skip
process(clk.rising, rst.rising):
if (rst) border_active :== 0
else if (border_cnt == BORDER_COUNTER_MAX && w_valid && w_ready) border_active :== 0
else if (w_addr == border_start_addr && border_skip && w_valid && w_ready)
border_active :== 1
process(clk.rising, rst.rising):
if (rst) border_cnt :== 0
else if (border_cnt == BORDER_COUNTER_MAX && w_valid && w_ready) border_cnt :== 0
else if (border_active && w_valid && w_ready) border_cnt :== border_cnt + 1
border_flag <> border_active
// instantiate a dual-port BRAM
val mem = new true_dual_port(DATA_WIDTH = DATA_WIDTH, ADDR_WIDTH = ADDR_WIDTH)
mem.clk <> clk
mem.data_a <> w_data
// the gold leaves these input ports empty; DFHDL requires every child input
// to be driven, so they are tied off
mem.data_b <> all(0)
mem.addr_a <> w_addr.bits
mem.addr_b <> r_addr.bits
mem.we_a <> fifo_write
mem.we_b <> 0
mem.rd_a <> 0
mem.rd_b <> read_mem
mem.q_a <> OPEN
mem.q_b <> r_data
end custom_fifo
import dfhdl.*
class detection_window(
val IMAGE_WIDTH: Int <> CONST = 640,
val INPUT_WIDTH: Int <> CONST = 36, // normalized block size in bits
val BLOCKS_PER_WINDOW: Int <> CONST = 32 // 4 block columns and 8 rows
) 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
// ascribed `Int <> CONST` so they survive as `localparam`s, matching the
// gold; a bare `val X = 64 / 16` is a Scala Int and is inlined away
val BUFFER_WIDTH: Int <> CONST = IMAGE_WIDTH / 64
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
val k_valid, k_border = Bit <> VAR
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
k_border <> block_line_buffer.k_border
k_valid <> block_line_buffer.k_valid
// Both sides are 1152 bits, but via different symbolic expressions
// (INPUT_WIDTH * BLOCKS_PER_WINDOW here, BLOCK_WIDTH * BLOCK_HEIGHT *
// BUFFER_WIDTH in lin_buff), which the elaborator cannot equate. Verilog
// connects them directly since the concrete widths agree.
detection_window <> block_line_buffer.kernel.resize(OUTPUT_WIDTH)
out_valid <> (k_valid & !k_border)
end detection_window
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.*
/** takes a 3x3 kernel every cycle and outputs its x and y gradients (Gx and Gy)
*/
class hog_gradient(
val KERNEL_WIDTH: Int <> CONST = 72 // 9 pixels * 8 bits/pixels = 72 bits
) extends EDDesign:
val clk, rst = Bit <> IN
val k_valid = Bit <> IN // deasserted for border cases
val out_ready = Bit <> IN
val kernel = Bits(KERNEL_WIDTH) <> IN
val k_ready = Bit <> OUT
val out_valid = Bit <> OUT
val Gx, Gy = Bits(9) <> OUT // signed values
k_ready <> out_ready
process(clk.rising, rst.rising):
if (rst)
Gx :== all(0)
Gy :== all(0)
else if (k_valid && k_ready)
// Verilog evaluates both subtractions at the 9-bit target width, so the
// 8-bit slices are zero-extended first and the result is the two's
// complement difference.
Gx :== kernel.lsbitsAt(40, 8).resize(9) - kernel.lsbitsAt(24, 8).resize(9)
Gy :== kernel.lsbitsAt(8, 8).resize(9) - kernel.lsbitsAt(56, 8).resize(9)
process(clk.rising, rst.rising):
if (rst) out_valid :== 0
else if (k_valid) out_valid :== 1
else if (!k_valid && (out_valid && out_ready)) out_valid :== 0
end hog_gradient
import dfhdl.*
class hog_magnitude(
val DATA_WIDTH: Int <> CONST = 8
) extends EDDesign:
val gx, gy = UInt(DATA_WIDTH) <> IN
val magnitude = UInt(DATA_WIDTH) <> OUT
magnitude <> (gx >= gy).sel(gx - gy, gy - gx)
end hog_magnitude
import dfhdl.*
class hog_orientation(
val DATA_WIDTH: Int <> CONST = 8
) extends EDDesign:
val gx, gy = UInt(DATA_WIDTH) <> IN
val is_upper_bin = Bit <> IN
val bin_out = UInt(4) <> OUT
// these values are already left-shifted by 10
val TAN20: Int <> CONST = 373 // 372.706
val TAN40: Int <> CONST = 859 // 859.238
val TAN60: Int <> CONST = 1774 // 1773.620
val TAN80: Int <> CONST = 5807 // 5807.393
val gx_prod20, gx_prod40, gx_prod60, gx_prod80 = UInt(21) <> VAR
val gy_shifted = UInt(21) <> VAR
val bin = UInt(4) <> VAR
// Verilog evaluates each RHS at the 21-bit target width, so widen first:
// `gy << 10` on a UInt[8] would otherwise stay 8 bits and shift everything out.
gy_shifted <> (gy.resize(21) << 10)
gx_prod20 <> gx.resize(21) * TAN20
gx_prod40 <> gx.resize(21) * TAN40
gx_prod60 <> gx.resize(21) * TAN60
gx_prod80 <> gx.resize(21) * TAN80
process(all):
if (gy_shifted < gx_prod20) bin := 0
else if (gy_shifted < gx_prod40) bin := 1
else if (gy_shifted < gx_prod60) bin := 2
else if (gy_shifted < gx_prod80) bin := 3
else bin := 4
bin_out <> is_upper_bin.sel(8 - bin, bin)
end hog_orientation
import dfhdl.*
class kernel(
val BLOCK_WIDTH: Int <> CONST = 3,
val BLOCK_HEIGHT: Int <> CONST = 3,
val DATA_WIDTH: Int <> CONST = 8
) extends EDDesign:
val INPUT_WIDTH = DATA_WIDTH * BLOCK_HEIGHT
val OUTPUT_WIDTH = BLOCK_WIDTH * BLOCK_HEIGHT * DATA_WIDTH
val clk, rst = Bit <> IN
val in_pixels = Bits(INPUT_WIDTH) <> IN
val in_valid = Bits(BLOCK_HEIGHT) <> IN
val out_ready = Bits(BLOCK_HEIGHT) <> IN
val out_pixels = Bits(OUTPUT_WIDTH) <> OUT
val in_ready = Bits(BLOCK_HEIGHT) <> OUT
val out_valid = Bits(BLOCK_HEIGHT) <> OUT
val kernel_valid = Bit <> OUT
val KERNEL_ROW_SIZE = BLOCK_WIDTH * DATA_WIDTH
kernel_valid <> out_valid(BLOCK_HEIGHT - 1)
val shiftregs =
for (i <- 0 until BLOCK_HEIGHT) yield
val shiftreg = new kernel_shiftreg(
DATA_WIDTH = DATA_WIDTH,
BLOCK_WIDTH = BLOCK_WIDTH
)
shiftreg.clk <> clk
shiftreg.rst <> rst
shiftreg.in_data <> in_pixels.lsbitsAt(i * DATA_WIDTH, DATA_WIDTH)
shiftreg.in_valid <> in_valid(i)
shiftreg.out_ready <> out_ready(i)
in_ready(i) <> shiftreg.in_ready
out_valid(i) <> shiftreg.out_valid
shiftreg
// The child outputs drive parametric-width slices of `out_pixels`. Connecting
// those slices directly hits the false "multiple connections" error
// (DFHDL#442), so they are written as assignments inside a process. The Scala
// `.foreach` keeps the iteration at elaboration time, so `i` stays a Scala Int.
process(all):
shiftregs.zipWithIndex.foreach: (shiftreg, i) =>
out_pixels.lsbitsAt(i * KERNEL_ROW_SIZE, KERNEL_ROW_SIZE) := shiftreg.out_data
end kernel
import dfhdl.*
class kernel_shiftreg(
val DATA_WIDTH: Int <> CONST = 8,
val BLOCK_WIDTH: Int <> CONST = 3
) extends EDDesign:
val OUTPUT_WIDTH = DATA_WIDTH * BLOCK_WIDTH
val clk, rst = Bit <> IN
val in_data = Bits(DATA_WIDTH) <> IN
val in_valid = Bit <> IN
val out_ready = Bit <> IN
val out_data = Bits(OUTPUT_WIDTH) <> OUT
val in_ready = Bit <> OUT
val out_valid = Bit <> OUT
enum ShiftRegState extends Encoded:
case S_IDLE // initial (reset) state
case S_BUFFER // shift in
case S_STREAM // shift in and out
import ShiftRegState.*
val current_state, next_state = ShiftRegState <> VAR
// localparam BUFF_SIZE = $clog2(BLOCK_WIDTH)
val buff_cnt = UInt.until(BLOCK_WIDTH) <> VAR
// state transitions
process(all):
next_state := current_state
current_state match
case S_IDLE =>
if (in_ready && in_valid)
// constant condition inside a process stays a hardware conditional,
// exactly as the gold's `if (BLOCK_WIDTH == 1)` does
if (BLOCK_WIDTH == 1) next_state := S_STREAM
else next_state := S_BUFFER
case S_BUFFER =>
if (buff_cnt == BLOCK_WIDTH - 2) next_state := S_STREAM
// stay at "stream" state until reset
case _ => next_state := current_state
end match
process(clk.rising, rst.rising):
if (rst) current_state :== S_IDLE
else current_state :== next_state
process(clk.rising, rst.rising):
if (rst) buff_cnt :== 0
else if (current_state == S_BUFFER && in_valid && in_ready)
buff_cnt :== buff_cnt + 1
// OUTPUT LOGIC
// handshake outputs
out_valid <> (in_valid && current_state == S_STREAM)
in_ready <> out_ready
// 2D shifting behavior.
// A constant `if` at design (concurrent) scope resolves during elaboration
// and only the taken branch is elaborated, so no `.toScalaInt` is needed
// even though each branch is width-invalid for the other's BLOCK_WIDTH.
if (BLOCK_WIDTH == 1)
process(clk.rising, rst.rising):
if (rst) out_data :== all(0)
// this branch is only elaborated when BLOCK_WIDTH == 1, so OUTPUT_WIDTH
// (= DATA_WIDTH * BLOCK_WIDTH) equals DATA_WIDTH -- but `X * 1` is not
// simplified to `X`, so the widths do not match symbolically.
else if (in_valid && in_ready) out_data :== in_data.resize(OUTPUT_WIDTH)
else
process(clk.rising, rst.rising):
if (rst) out_data :== all(0)
else if (in_valid && in_ready)
out_data :== in_data ++ out_data.msbits(OUTPUT_WIDTH - DATA_WIDTH)
end if
end kernel_shiftreg
import dfhdl.*
/** Author: Mahmoud Abdelwase, Date: 13/03/2023
*
* description: A generic line buffer implementation
*
* parameters:
* - BUFFER_WIDTH: buffer element's width in bits.
* - BUFFER_DEPTH: buffer array length, used to determine address width.
* - BLOCK_WIDTH: number of buffer elements exposed for operations.
* - BLOCK_HEIGHT: number of buffer rows (lines).
*/
class lin_buff(
val BUFFER_WIDTH: Int <> CONST = 8,
val BUFFER_DEPTH: Int <> CONST = 854, // default support for 480p images
val BLOCK_WIDTH: Int <> CONST = 3,
val BLOCK_HEIGHT: Int <> CONST = 3
) extends EDDesign:
val OUTPUT_WIDTH = BLOCK_WIDTH * BLOCK_HEIGHT * BUFFER_WIDTH
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
// concatenated kernel data I/O size in bits
val K_DATA_WIDTH = BLOCK_HEIGHT * BUFFER_WIDTH
// kernel row size in bits
val K_ROW_WIDTH = BLOCK_WIDTH * BUFFER_WIDTH
val MID_ROWS = BLOCK_HEIGHT - 2
// connective wires (buffer interface)
val f_to_k_valid = Bit <> VAR
val f_to_k_ready = Bit <> VAR
val f_to_k_data = Bits(BUFFER_WIDTH) <> VAR
val k_to_f_valid = Bit <> VAR
val k_to_f_ready = Bit <> VAR
val k_to_f_data = Bits(BUFFER_WIDTH) <> VAR
val b_to_k_valid = Bit X MID_ROWS <> VAR
val b_to_k_ready = Bit X MID_ROWS <> VAR
val b_to_k_data = Bits(BUFFER_WIDTH) X MID_ROWS <> VAR
val k_to_b_valid = Bit X MID_ROWS <> VAR
val k_to_b_ready = Bit X MID_ROWS <> VAR
val k_to_b_data = Bits(BUFFER_WIDTH) X MID_ROWS <> VAR
// concatenated connective wires (kernel interface).
// kernel data input signals
val k_in_valid = Bits(BLOCK_HEIGHT) <> VAR
val k_in_ready = Bits(BLOCK_HEIGHT) <> VAR // output from kernel,
val k_in_data = Bits(K_DATA_WIDTH) <> VAR
// kernel data output signals
val k_out_valid = Bits(BLOCK_HEIGHT) <> VAR
val k_out_ready = Bits(BLOCK_HEIGHT) <> VAR // input to kernel.
val k_out_data = Bits(OUTPUT_WIDTH) <> VAR
p_ready <> k_in_ready(0)
kernel <> k_out_data
// instantiate the kernel block
val kernel_block = new kernel(
DATA_WIDTH = BUFFER_WIDTH,
BLOCK_WIDTH = BLOCK_WIDTH,
BLOCK_HEIGHT = BLOCK_HEIGHT
)
kernel_block.clk <> clk
kernel_block.rst <> rst
kernel_block.in_pixels <> k_in_data
kernel_block.in_valid <> k_in_valid
kernel_block.out_ready <> k_out_ready
k_out_data <> kernel_block.out_pixels
k_in_ready <> kernel_block.in_ready
k_out_valid <> kernel_block.out_valid
k_valid <> kernel_block.kernel_valid
val first_line = new custom_fifo(
DATA_WIDTH = BUFFER_WIDTH,
FIFO_DEPTH = BUFFER_DEPTH - BLOCK_WIDTH,
KERNEL_WIDTH = BLOCK_WIDTH,
FIRST_LINE = 1
)
first_line.clk <> clk
first_line.rst <> rst
first_line.w_data <> k_to_f_data
first_line.w_valid <> k_to_f_valid
first_line.r_ready <> f_to_k_ready
k_to_f_ready <> first_line.w_ready
f_to_k_data <> first_line.r_data
f_to_k_valid <> first_line.r_valid
first_line.fifo_full <> OPEN
k_border <> first_line.border_flag
val line_buffs =
for (i <- 0 until MID_ROWS) yield
val line_buff = new custom_fifo(
DATA_WIDTH = BUFFER_WIDTH,
FIFO_DEPTH = BUFFER_DEPTH - BLOCK_WIDTH,
KERNEL_WIDTH = BLOCK_WIDTH
)
line_buff.clk <> clk
line_buff.rst <> rst
line_buff.w_data <> k_to_b_data(i)
line_buff.w_valid <> k_to_b_valid(i)
line_buff.r_ready <> b_to_k_ready(i)
k_to_b_ready(i) <> line_buff.w_ready
// `b_to_k_data(i) <> line_buff.r_data` fails for i >= 1 because the
// element width is a parameter (DFHDL#447); driven in the process below.
b_to_k_valid(i) <> line_buff.r_valid
line_buff.fifo_full <> OPEN
line_buff.border_flag <> OPEN
line_buff
// All of the fabric writes live in one process: the `k_in_data`/`k_out_data`
// slices carry parameter-dependent bounds, which cannot be driven by `<>`
// (DFHDL#442). `.indices.foreach` keeps the iteration at elaboration time.
process(all):
// kernel input concatenation procedure
// inputs to the first kernel row
k_in_valid(0) := p_valid
k_in_data.lsbitsAt(0, BUFFER_WIDTH) := pixel
// inputs to second kernel row
k_in_valid(1) := f_to_k_valid
k_in_data.lsbitsAt(BUFFER_WIDTH, BUFFER_WIDTH) := f_to_k_data
// inputs to the rest of kernel rows
line_buffs.indices.foreach: j =>
k_in_valid(j + 2) := b_to_k_valid(j)
k_in_data.lsbitsAt((j + 2) * BUFFER_WIDTH, BUFFER_WIDTH) := b_to_k_data(j)
// kernel output concatenation procedure
k_out_ready(BLOCK_HEIGHT - 1) := k_ready
k_to_f_valid := k_out_valid(0)
k_out_ready(0) := k_to_f_ready
k_to_f_data := k_out_data.lsbitsAt(0, BUFFER_WIDTH)
line_buffs.indices.foreach: j =>
k_to_b_valid(j) := k_out_valid(j + 1)
k_out_ready(j + 1) := k_to_b_ready(j)
k_to_b_data(j) := k_out_data.lsbitsAt((j + 1) * K_ROW_WIDTH, BUFFER_WIDTH)
// `f_to_k_ready = k_in_ready[1]`, and `b_to_k_ready[j] = k_in_ready[j+2]`
f_to_k_ready := k_in_ready(1)
line_buffs.indices.foreach: j =>
b_to_k_ready(j) := k_in_ready(j + 2)
b_to_k_data(j) := line_buffs(j).r_data
end lin_buff
import dfhdl.*
/** takes cell histograms and produces normalized blocks */
class norm_block(
val IMAGE_WIDTH: Int <> CONST = 640,
val IMAGE_HEIGHT: Int <> CONST = 480,
val CELL_ROW_PIXELS: Int <> CONST = 8,
val CELL_COLUMN_PIXELS: Int <> CONST = 8,
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, // 10th bin is the sum
val CELLS_PER_BLOCK: Int <> CONST = 4
) extends EDDesign:
val HISTOGRAM_WIDTH: Int <> CONST = BIN_WIDTH * (BINS + 1)
val BLOCK_HIST_WIDTH: Int <> CONST = HISTOGRAM_WIDTH * CELLS_PER_BLOCK
val OUTPUT_WIDTH: Int <> CONST = BINS * CELLS_PER_BLOCK
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
val CELLS_PER_LINE: Int <> CONST = IMAGE_WIDTH / 8
val block_histograms = Bits(BLOCK_HIST_WIDTH) <> VAR
val k_valid, k_border = Bit <> VAR
// initialize the line buffer
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
k_border <> cell_line_buffer.k_border
k_valid <> cell_line_buffer.k_valid
// lin_buff's width is BLOCK_ROW_CELLS * BLOCK_COLUMN_CELLS * HISTOGRAM_WIDTH,
// which is not symbolically equal to HISTOGRAM_WIDTH * CELLS_PER_BLOCK even
// though both are 560 (the gold couples 2*2 and CELLS_PER_BLOCK by hand).
block_histograms <> cell_line_buffer.kernel.resize(BLOCK_HIST_WIDTH)
// initialize the normalization module
val u_normalization = new normalization(
BIN_WIDTH = BIN_WIDTH,
BINS = BINS,
CELLS_PER_BLOCK = CELLS_PER_BLOCK
)
u_normalization.in_valid <> k_valid
u_normalization.k_border <> k_border
u_normalization.block_histograms <> block_histograms
out_valid <> u_normalization.out_valid
normalized_block <> u_normalization.normalized_block
end norm_block
import dfhdl.*
/** a binarized normalization.
*
* takes a concatenated block representing 4 histograms, and produces the
* binarized and normalized content of the histograms
*/
class normalization(
val BIN_WIDTH: Int <> CONST = 14,
val BINS: Int <> CONST = 9, // 10th bin is the sum
val CELLS_PER_BLOCK: Int <> CONST = 4
) extends EDDesign:
val INPUT_WIDTH = BIN_WIDTH * (BINS + 1) * CELLS_PER_BLOCK
val OUTPUT_WIDTH = BINS * CELLS_PER_BLOCK // 1 bit per bin
val in_valid = Bit <> IN // gate the circuit to save power
val k_border = Bit <> IN
val block_histograms = Bits(INPUT_WIDTH) <> IN
val out_valid = Bit <> OUT
val normalized_block = Bits(OUTPUT_WIDTH) <> OUT
val sum = UInt(16) <> VAR
val shifted_sum = UInt(12) <> VAR
out_valid <> (in_valid && !k_border)
process(all):
sum := 0
for (k <- 0 until CELLS_PER_BLOCK)
sum := in_valid.sel(
sum + block_histograms
.lsbitsAt((k * (BINS + 1) + BINS) * BIN_WIDTH, BIN_WIDTH)
.resize(16),
0
)
shifted_sum <> (sum >> 4).resize(12)
for (i <- 0 until BINS; j <- 0 until CELLS_PER_BLOCK)
normalized_block(i + j * 9) <> (
block_histograms.lsbitsAt(i * BIN_WIDTH + j * 10 * BIN_WIDTH, BIN_WIDTH).uint >=
shifted_sum.resize(BIN_WIDTH)
)
end normalization
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
# Reproduce DFHDL #449 (stale elaboration cache -> internal NoSuchElementException).
#
# IMPORTANT: run this in a directory that has never been compiled before.
# Re-running in the same directory does NOT reproduce, because bloop's
# incremental state (keyed by project path, in ~/.cache/bloop) suppresses it.
# Copy the tree to a NEW path for each attempt.
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 the cache with a top that specializes shared children =="
b hog > /tmp/449_1.log 2>&1 && echo " ok" || { echo " FAILED"; tail -3 /tmp/449_1.log; exit 1; }
echo "== 2. build cell_histogram =="
b cell_histogram > /tmp/449_2.log 2>&1 && echo " ok" || { echo " FAILED"; tail -3 /tmp/449_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 =="
if b cell_histogram > /tmp/449_3.log 2>&1; then
echo " ok -- did NOT reproduce (was this directory used before?)"
else
grep -m1 NoSuchElementException /tmp/449_3.log && echo " REPRODUCED"
fi
echo "== 5. clear dfhdl-cache, rebuild the SAME source =="
find src/.scala-build -name dfhdl-cache -type d -exec rm -rf {} + 2>/dev/null
b cell_histogram > /tmp/449_4.log 2>&1 && echo " ok -- identical source elaborates fine once the cache is gone"
import dfhdl.*
class row_histogram(
val DATA_WIDTH: Int <> CONST = 8,
val BIN_WIDTH: Int <> CONST = 11,
val BINS: Int <> CONST = 10 // 10th bin for the sum of all magnitudes
) extends EDDesign:
val HISTOGRAM_WIDTH = BIN_WIDTH * BINS
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 row_histogram = Bits(HISTOGRAM_WIDTH) <> OUT
enum RowHistState extends Encoded:
case S_IDLE, S_ACCUM, S_BYPASS, S_VALID
import RowHistState.*
val current_state, next_state = RowHistState <> VAR
val bin_accum = Bits(HISTOGRAM_WIDTH) <> VAR
val bin_accum_reg = Bits(HISTOGRAM_WIDTH) <> VAR
val bin_cnt = UInt(4) <> VAR
// perform the accumulation combinationally
// [hardcoded to support 9 bins + the 10th sum bin]
// `magnitude` is widened to BIN_WIDTH explicitly: a bare `slice + magnitude`
// yields the symbolic width `BIN_WIDTH max DATA_WIDTH`, which does not resolve.
process(all):
bin_accum := bin_accum_reg // default value;
// accumulate all bin values into the 10th bin
bin_accum.lsbitsAt(9 * BIN_WIDTH, BIN_WIDTH) :=
bin_accum_reg.lsbitsAt(9 * BIN_WIDTH, BIN_WIDTH) + magnitude.resize(BIN_WIDTH)
bin_index match
case 0 =>
bin_accum.lsbitsAt(0 * BIN_WIDTH, BIN_WIDTH) :=
bin_accum_reg.lsbitsAt(0 * BIN_WIDTH, BIN_WIDTH) + magnitude.resize(BIN_WIDTH)
case 1 =>
bin_accum.lsbitsAt(1 * BIN_WIDTH, BIN_WIDTH) :=
bin_accum_reg.lsbitsAt(1 * BIN_WIDTH, BIN_WIDTH) + magnitude.resize(BIN_WIDTH)
case 2 =>
bin_accum.lsbitsAt(2 * BIN_WIDTH, BIN_WIDTH) :=
bin_accum_reg.lsbitsAt(2 * BIN_WIDTH, BIN_WIDTH) + magnitude.resize(BIN_WIDTH)
case 3 =>
bin_accum.lsbitsAt(3 * BIN_WIDTH, BIN_WIDTH) :=
bin_accum_reg.lsbitsAt(3 * BIN_WIDTH, BIN_WIDTH) + magnitude.resize(BIN_WIDTH)
case 4 =>
bin_accum.lsbitsAt(4 * BIN_WIDTH, BIN_WIDTH) :=
bin_accum_reg.lsbitsAt(4 * BIN_WIDTH, BIN_WIDTH) + magnitude.resize(BIN_WIDTH)
case 5 =>
bin_accum.lsbitsAt(5 * BIN_WIDTH, BIN_WIDTH) :=
bin_accum_reg.lsbitsAt(5 * BIN_WIDTH, BIN_WIDTH) + magnitude.resize(BIN_WIDTH)
case 6 =>
bin_accum.lsbitsAt(6 * BIN_WIDTH, BIN_WIDTH) :=
bin_accum_reg.lsbitsAt(6 * BIN_WIDTH, BIN_WIDTH) + magnitude.resize(BIN_WIDTH)
case 7 =>
bin_accum.lsbitsAt(7 * BIN_WIDTH, BIN_WIDTH) :=
bin_accum_reg.lsbitsAt(7 * BIN_WIDTH, BIN_WIDTH) + magnitude.resize(BIN_WIDTH)
case 8 =>
bin_accum.lsbitsAt(8 * BIN_WIDTH, BIN_WIDTH) :=
bin_accum_reg.lsbitsAt(8 * BIN_WIDTH, BIN_WIDTH) + magnitude.resize(BIN_WIDTH)
case _ =>
// note: this also undoes the 10th-bin accumulation above
bin_accum := bin_accum_reg
end match
// register the accumulator values, except the last one which is output directly.
process(clk.rising, rst.rising):
if (rst) bin_accum_reg :== all(0)
else if (next_state == S_IDLE) bin_accum_reg :== all(0)
else if (current_state != S_VALID) bin_accum_reg :== bin_accum
row_histogram <> (current_state != S_VALID).sel(bin_accum, bin_accum_reg)
process(all):
next_state := current_state
current_state match
case S_IDLE =>
if (in_valid && in_ready) next_state := S_ACCUM
case S_ACCUM =>
if (bin_cnt == 6 && (in_valid && in_ready)) next_state := S_BYPASS
case S_BYPASS =>
if (out_valid && out_ready) next_state := S_IDLE
else next_state := S_VALID
case S_VALID =>
if (out_valid && out_ready) next_state := S_IDLE
end match
process(clk.rising, rst.rising):
if (rst) current_state :== S_IDLE
else current_state :== next_state
out_valid <> ((current_state == S_VALID || current_state == S_BYPASS) && in_valid)
// a bare `1` as a `.sel` argument is rejected ("Unsupported value of type
// `scala.Int` for DFHDL receiver type `Bit`"), so name it as a Bit constant
val ALWAYS_READY: Bit <> CONST = 1
in_ready <> (current_state != S_VALID).sel(ALWAYS_READY, out_ready)
process(clk.rising, rst.rising):
if (rst) bin_cnt :== 0
else if (out_valid && out_ready) bin_cnt :== 0
// `current_state < S_BYPASS` : enums support only ==/!=, so compare encodings.
// NB: the documented `.uint` on an enum does not exist; go via `.bits`.
else if (in_valid && in_ready && current_state.bits.uint < S_BYPASS.bits.uint)
bin_cnt :== bin_cnt + 1
end row_histogram
import dfhdl.*
/** directly copied from...
* https://www.intel.com/content/www/us/en/docs/programmable/683323/18-1/true-dual-port-synchronous-ram.html
*/
class true_dual_port(
val DATA_WIDTH: Int <> CONST = 8,
val ADDR_WIDTH: Int <> CONST = 6
) extends EDDesign:
val data_a, data_b = Bits(DATA_WIDTH) <> IN
val addr_a, addr_b = Bits(ADDR_WIDTH) <> IN
val we_a, we_b = Bit <> IN
val clk = Bit <> IN
val rd_a, rd_b = Bit <> IN
val q_a, q_b = Bits(DATA_WIDTH) <> OUT
// Declare the RAM variable. SHARED because both port processes assign it.
val ram = Bits(DATA_WIDTH) X (2 ** ADDR_WIDTH) <> VAR.SHARED
// Port a
process(clk.rising):
if (we_a)
ram(addr_a) :== data_a
q_a :== data_a
else if (rd_a)
q_a :== ram(addr_a)
// Port b
process(clk.rising):
if (we_b)
ram(addr_b) :== data_b
q_b :== data_b
else if (rd_b)
q_b :== ram(addr_b)
end true_dual_port
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment