Last active
August 5, 2026 11:54
-
-
Save soronpo/f76eeee5f505e5885d933eaf74c93ee9 to your computer and use it in GitHub Desktop.
DFHDL #449 - stale elaboration cache repro (17 files + script)
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
| 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 |
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
| 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 |
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
| 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 |
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
| 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 |
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
| 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 |
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
| 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 |
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
| 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 |
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
| 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 |
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
| 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 |
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
| 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 |
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
| 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 |
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
| 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 |
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
| 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 |
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
| 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 |
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
| 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 |
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
| #!/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" |
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
| 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 |
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
| 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