From cfe93acfb6946b2438ea69d9108667db6a8de8cd Mon Sep 17 00:00:00 2001 From: Francesco Conti Date: Thu, 6 Aug 2026 12:37:21 +0200 Subject: [PATCH] Refactor hci_core_{source,sink}_v2 as thin wrappers This commit refactors hci_core_{source,sink}_v2, which introduced ready/valid job queues, as thin wrappers around "traditional" hci_core_{source,sink}. Most (if not all) downstream HWPEs use non-v2 streamers, which still receive significant updates and improvements. On the other hand, v2 streamers have been left untouched and are only lightly tested. Moving the v2 streamers to thin wrappers has the primary advantage that any code duplication (and divergence) is removed. Eventually, non-v2 streamers may be deprecated and in that case their functionality may be reintegrated in the v2 streamers, but this can happen only if most downstream HWPEs migrate to v2. --- Bender.yml | 4 +- rtl/common/hci_package.sv | 7 +- rtl/core/hci_core_sink_v2.sv | 398 ++++++++--------------------- rtl/core/hci_core_source_v2.sv | 448 +++++++++------------------------ 4 files changed, 237 insertions(+), 620 deletions(-) diff --git a/Bender.yml b/Bender.yml index 518580b..cc81c8a 100644 --- a/Bender.yml +++ b/Bender.yml @@ -46,7 +46,6 @@ sources: - rtl/core/hci_core_r_id_filter.sv - rtl/core/hci_core_rob.sv - rtl/core/hci_core_source.sv - - rtl/core/hci_core_source_v2.sv - rtl/core/hci_core_split.sv - rtl/ecc/hci_ecc_dec.sv - rtl/ecc/hci_ecc_enc.sv @@ -63,10 +62,11 @@ sources: - rtl/parity/hci_copy_sink.sv # Level 3 - rtl/core/hci_core_sink.sv + - rtl/core/hci_core_source_v2.sv - rtl/ecc/hci_ecc_source.sv - - rtl/core/hci_core_sink_v2.sv - rtl/interco/hci_router.sv # Level 4 + - rtl/core/hci_core_sink_v2.sv - rtl/ecc/hci_ecc_interconnect.sv - rtl/ecc/hci_ecc_sink.sv - rtl/hci_interconnect.sv diff --git a/rtl/common/hci_package.sv b/rtl/common/hci_package.sv index 38b1130..df3c4c2 100644 --- a/rtl/common/hci_package.sv +++ b/rtl/common/hci_package.sv @@ -67,15 +67,18 @@ package hci_package; hwpe_stream_package::flags_addressgen_v4_t addressgen_flags; } hci_streamer_flags_t; + // Same as hci_streamer_ctrl_t / hci_streamer_flags_t, except that the v2 streamers accept jobs + // through a valid/ready handshake instead of the req_start/ready_start pair. typedef struct packed { logic valid; - hwpe_stream_package::ctrl_addressgen_v3_t addressgen_ctrl; + hwpe_stream_package::ctrl_addressgen_v4_t addressgen_ctrl; } hci_streamer_v2_ctrl_t; typedef struct packed { logic ready; logic done; - hwpe_stream_package::flags_addressgen_v3_t addressgen_flags; + logic no_valid_transfers; + hwpe_stream_package::flags_addressgen_v4_t addressgen_flags; } hci_streamer_v2_flags_t; typedef enum { diff --git a/rtl/core/hci_core_sink_v2.sv b/rtl/core/hci_core_sink_v2.sv index 838d56c..958bbb7 100644 --- a/rtl/core/hci_core_sink_v2.sv +++ b/rtl/core/hci_core_sink_v2.sv @@ -3,7 +3,7 @@ * Francesco Conti * Diego Gorfini * - * Copyright (C) 2014-2025 ETH Zurich, University of Bologna + * Copyright (C) 2014-2026 ETH Zurich, University of Bologna * Copyright and related rights are licensed under the Solderpad Hardware * License, Version 0.51 (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at @@ -15,47 +15,32 @@ */ /** - * The **hci_core_sink_v2** module is the high-level sink streamer - * performing a series of stores on a HCI-Core interface - * from an incoming HWPE-Stream data stream from a HWPE engine/datapath. - * The sink streamer is a composite module that makes use of many other - * fundamental IPs. + * The **hci_core_sink_v2** module acts as a job-queued wrapper around the + * **hci_core_sink** module. It extends the functionality with a queue of + * streamer jobs, while preserving its original behavior; please refer to + * **hci_core_sink** for detailed functional information on the underlying + * streamer and on all parameters not listed below. * - * Fundamentally, a sink streamer acts as a specialized DMA engine acting - * out a predefined pattern from an **hwpe_stream_addressgen_v3** to perform - * a burst of stores via a HCI-Core interface, consuming a HWPE-Stream data - * stream into the HCI-Core `data` field. - * The sink streamer is insensitive to memory latency. - * This is due to the nature of store streams, which are unidirectional - * (i.e. `addr` and `data` move in the same direction). - * - * Misaligned accesses are supported by widening the HCI-Core data width of 32 - * bits compared to the HWPE-Stream that gets consumed by the streamer. - * The stream is shifted according to the address alignment and invalid bytes - * are disabled by unsetting their `strb`. This feature can be deactivated by - * unsetting the `MISALIGNED_ACCESS` parameter; in this case, the sink will - * only work correctly if all data is aligned to a word boundary. - * - * Compared to the **hci_core_sink** module, the **hci_core_sink_v2** introduce - * a job queue to enqueue streamer jobs earlier and streamline/optimize ctrl. + * Whereas **hci_core_sink** accepts one job at a time through a + * `req_start` / `ready_start` pair, the job queue lets jobs be enqueued through + * a `valid` / `ready` handshake (`hci_streamer_v2_ctrl_t`) well before the + * streamer is able to serve them, so that a queued job can be started as soon + * as the previous one is over. The queue carries a full + * `ctrl_addressgen_v4_t`; a job is popped only once the underlying streamer + * reports it done, so the running job's configuration stays stable for its + * entire duration. * * .. tabularcolumns:: |l|l|J| * .. _hci_core_sink_v2_params: - * .. table:: **hci_core_sink_v2** design-time parameters. + * .. table:: **hci_core_sink_v2** additional design-time parameters. * - * +------------------------+-------------+------------------------------------------------------------------------------------------------------------------------+ - * | **Name** | **Default** | **Description** | - * +------------------------+-------------+------------------------------------------------------------------------------------------------------------------------+ - * | *TCDM_FIFO_DEPTH* | 2 | If >0, the module produces a HWPE-MemDecoupled interface and includes a TCDM FIFO of this depth. | - * +------------------------+-------------+------------------------------------------------------------------------------------------------------------------------+ - * | *TRANS_CNT* | 16 | Number of bits supported in the transaction counter of the address generator, which will overflow at 2^ `TRANS_CNT`. | - * +------------------------+-------------+------------------------------------------------------------------------------------------------------------------------+ - * | *MISALIGNED_ACCESS* | 1 | If set to 0, the sink will not support non-word-aligned HWPE-Mem accesses. | - * +------------------------+-------------+------------------------------------------------------------------------------------------------------------------------+ - * | *JOB_FIFO_DEPTH* | 2 | Depth of the streamer job queue. | - * +------------------------+-------------+------------------------------------------------------------------------------------------------------------------------+ - * | *JOB_FIFO_PASSTHROUGH* | 1 | If set to 1 (default), the streamer job queue is passthrough, otherwise it's a regular path-cutting FIFO. | - * +------------------------+-------------+------------------------------------------------------------------------------------------------------------------------+ + * +------------------------+-------------+------------------------------------------------------------------------------------------------------------+ + * | **Name** | **Default** | **Description** | + * +------------------------+-------------+------------------------------------------------------------------------------------------------------------+ + * | *JOB_FIFO_DEPTH* | 2 | Depth of the streamer job queue. | + * +------------------------+-------------+------------------------------------------------------------------------------------------------------------+ + * | *JOB_FIFO_PASSTHROUGH* | 1 | If set to 1 (default), the streamer job queue is passthrough, otherwise it's a regular path-cutting FIFO. | + * +------------------------+-------------+------------------------------------------------------------------------------------------------------------+ * * .. tabularcolumns:: |l|l|J| * .. _hci_core_sink_v2_ctrl: @@ -64,26 +49,29 @@ * +-------------------+------------------------+-----------------------------------------------------------------------------+ * | **Name** | **Type** | **Description** | * +-------------------+------------------------+-----------------------------------------------------------------------------+ - * | *req_start* | `logic` | When 1, the sink streamer operation is started if it is ready. | + * | *valid* | `logic` | When 1, the job described by `addressgen_ctrl` is enqueued if `ready` is 1. | * +-------------------+------------------------+-----------------------------------------------------------------------------+ - * | *addressgen_ctrl* | `ctrl_addressgen_v3_t` | Configuration of the address generator (see **hwpe_stream_addressgen_v3**). | + * | *addressgen_ctrl* | `ctrl_addressgen_v4_t` | Configuration of the address generator (see **hwpe_stream_addressgen_v4**). | * +-------------------+------------------------+-----------------------------------------------------------------------------+ * * .. tabularcolumns:: |l|l|J| * .. _hci_core_sink_v2_flags: * .. table:: **hci_core_sink_v2** output flags. * - * +--------------------+------------------------+-----------------------------------------------------------------------------------------------+ - * | **Name** | **Type** | **Description** | - * +--------------------+------------------------+-----------------------------------------------------------------------------------------------+ - * | *ready_start* | `logic` | 1 when the sink streamer is ready to start operation, from the first IDLE state cycle on. | - * +--------------------+------------------------+-----------------------------------------------------------------------------------------------+ - * | *done* | `logic` | 1 for one cycle when the streamer ends operation, in the cycle before it goes to IDLE state . | - * +--------------------+------------------------+-----------------------------------------------------------------------------------------------+ - * | *addressgen_flags* | `flags_addressgen_v3_t`| Address generator flags (see **hwpe_stream_addresgen_v3**). | - * +--------------------+------------------------+-----------------------------------------------------------------------------------------------+ + * +----------------------+-------------------------+-----------------------------------------------------------------------------------------------+ + * | **Name** | **Type** | **Description** | + * +----------------------+-------------------------+-----------------------------------------------------------------------------------------------+ + * | *ready* | `logic` | 1 when the job queue can accept a new job. | + * +----------------------+-------------------------+-----------------------------------------------------------------------------------------------+ + * | *done* | `logic` | 1 for one cycle when the streamer ends a job. | + * +----------------------+-------------------------+-----------------------------------------------------------------------------------------------+ + * | *no_valid_transfers* | `logic` | 1 while no store is in flight; used to detect an empty/idle streamer run. | + * +----------------------+-------------------------+-----------------------------------------------------------------------------------------------+ + * | *addressgen_flags* | `flags_addressgen_v4_t` | Address generator flags (see **hwpe_stream_addressgen_v4**). | + * +----------------------+-------------------------+-----------------------------------------------------------------------------------------------+ * */ + `include "hci_helpers.svh" module hci_core_sink_v2 @@ -91,11 +79,14 @@ module hci_core_sink_v2 import hci_package::*; #( // Stream interface params - parameter int unsigned TCDM_FIFO_DEPTH = 0, - parameter int unsigned TRANS_CNT = 16, - parameter int unsigned MISALIGNED_ACCESSES = 1, - parameter int unsigned JOB_FIFO_DEPTH = 2, + parameter int unsigned TCDM_FIFO_DEPTH = 0, + parameter int unsigned TRANS_CNT = 16, + parameter int unsigned MISALIGNED_ACCESSES = 1, + parameter int unsigned JOB_FIFO_DEPTH = 2, parameter int unsigned JOB_FIFO_PASSTHROUGH = 1, + parameter int unsigned ELEMENT_WIDTH = 8, // e.g., 8 bits per element + parameter int unsigned ELEMENTS_PER_BANK = 4, // number of elements in one memory bank + parameter bit [3:0] DIM_ENABLE_1H = 4'b0011, // Number of dimensions enabled in the address generator parameter hci_size_parameter_t `HCI_SIZE_PARAM(tcdm) = '0 ) ( @@ -113,50 +104,37 @@ module hci_core_sink_v2 output hci_streamer_v2_flags_t flags_o ); - localparam int unsigned DATA_WIDTH = `HCI_SIZE_GET_DW(tcdm); - localparam int unsigned EHW = `HCI_SIZE_GET_EHW(tcdm); - - hci_streamer_state_t cs, ns; - flags_fifo_t addr_fifo_flags, job_fifo_flags; + flags_fifo_t job_fifo_flags; - logic address_gen_en; - logic address_gen_clr; - logic job_pop_ready, presample; - - logic tcdm_inflight; - - hwpe_stream_intf_stream #( - .DATA_WIDTH ( 36 ) - ) addr_push ( - .clk ( clk_i ) - ); + hci_streamer_ctrl_t sink_ctrl; + hci_streamer_flags_t sink_flags; - hwpe_stream_intf_stream #( - .DATA_WIDTH ( 36 ) - ) addr_pop ( - .clk ( clk_i ) - ); + logic job_pop_ready; + logic req_start; + /* + * Job queue + */ hwpe_stream_intf_stream #( - .DATA_WIDTH ( $bits(ctrl_addressgen_v3_t) ) + .DATA_WIDTH ( $bits(ctrl_addressgen_v4_t) ) ) job_push ( .clk ( clk_i ) ); hwpe_stream_intf_stream #( - .DATA_WIDTH ( $bits(ctrl_addressgen_v3_t) ) + .DATA_WIDTH ( $bits(ctrl_addressgen_v4_t) ) ) job_pop ( .clk ( clk_i ) ); assign job_push.data = ctrl_i.addressgen_ctrl; assign job_push.valid = ctrl_i.valid; - assign flags_o.ready = job_push.ready; assign job_push.strb = '1; + assign flags_o.ready = job_push.ready; if (JOB_FIFO_PASSTHROUGH == 1) begin : job_fifo_passthrough_gen hwpe_stream_fifo_passthrough #( - .DATA_WIDTH ( $bits(ctrl_addressgen_v3_t) ), + .DATA_WIDTH ( $bits(ctrl_addressgen_v4_t) ), .FIFO_DEPTH ( JOB_FIFO_DEPTH ) ) i_fifo_job ( .clk_i ( clk_i ), @@ -169,7 +147,7 @@ module hci_core_sink_v2 end else begin : job_fifo_nopassthrough_gen hwpe_stream_fifo #( - .DATA_WIDTH ( $bits(ctrl_addressgen_v3_t) ), + .DATA_WIDTH ( $bits(ctrl_addressgen_v4_t) ), .FIFO_DEPTH ( JOB_FIFO_DEPTH ) ) i_fifo_job ( .clk_i ( clk_i ), @@ -183,236 +161,76 @@ module hci_core_sink_v2 assign job_pop.ready = job_pop_ready; - localparam hci_size_parameter_t `HCI_SIZE_PARAM(tcdm_target) = '{ - DW: DATA_WIDTH, - AW: DEFAULT_AW, - BW: DEFAULT_BW, - UW: DEFAULT_UW, - IW: DEFAULT_IW, - EW: DEFAULT_EW, - EHW: DEFAULT_EHW - }; - `HCI_INTF(tcdm_target, clk_i); - - hwpe_stream_addressgen_v3 i_addressgen ( - .clk_i ( clk_i ), - .rst_ni ( rst_ni ), - .enable_i ( address_gen_en ), - .clear_i ( address_gen_clr ), - .presample_i ( presample ), - .addr_o ( addr_push ), - .ctrl_i ( job_pop.data ), - .flags_o ( flags_o.addressgen_flags ) - ); - - hwpe_stream_fifo #( - .DATA_WIDTH ( 36 ), - .FIFO_DEPTH ( 2 ) - ) i_fifo_addr ( - .clk_i ( clk_i ), - .rst_ni ( rst_ni ), - .clear_i ( clear_i ), - .flags_o ( addr_fifo_flags ), - .push_i ( addr_push ), - .pop_o ( addr_pop ) - ); - - logic address_cnt_en, address_cnt_clr; - logic [TRANS_CNT-1:0] address_cnt_d, address_cnt_q; - - logic [DATA_WIDTH-1:0] stream_data_misaligned; - logic [DATA_WIDTH/8-1:0] stream_strb_misaligned; - logic [DATA_WIDTH-1:0] stream_data_aligned; - logic [DATA_WIDTH/8-1:0] stream_strb_aligned; - - assign stream_data_misaligned = stream.data; - assign stream_strb_misaligned = stream.strb; - - if (MISALIGNED_ACCESSES==1 ) begin : misaligned_access_gen - always_comb - begin - stream_data_aligned = '0; - stream_strb_aligned = '0; - case(addr_pop.data[1:0]) - 2'b00: begin - stream_data_aligned[DATA_WIDTH-32-1:0] = stream_data_misaligned[DATA_WIDTH-32-1:0]; - stream_strb_aligned[(DATA_WIDTH-32)/8-1:0] = stream_strb_misaligned[(DATA_WIDTH-32)/8-1:0]; - end - 2'b01: begin - stream_data_aligned[DATA_WIDTH-24-1:8] = stream_data_misaligned[DATA_WIDTH-32-1:0]; - stream_strb_aligned[(DATA_WIDTH-24)/8-1:1] = stream_strb_misaligned[(DATA_WIDTH-32)/8-1:0]; - end - 2'b10: begin - stream_data_aligned[DATA_WIDTH-16-1:16] = stream_data_misaligned[DATA_WIDTH-32-1:0]; - stream_strb_aligned[(DATA_WIDTH-16)/8-1:2] = stream_strb_misaligned[(DATA_WIDTH-32)/8-1:0]; - end - 2'b11: begin - stream_data_aligned[DATA_WIDTH-8-1:24] = stream_data_misaligned[DATA_WIDTH-32-1:0]; - stream_strb_aligned[(DATA_WIDTH-8)/8-1:3] = stream_strb_misaligned[(DATA_WIDTH-32)/8-1:0]; - end - endcase - end - end - else begin - assign stream_data_aligned[DATA_WIDTH-1:0] = stream_data_misaligned[DATA_WIDTH-1:0]; - assign stream_strb_aligned[DATA_WIDTH/8-1:0] = stream_strb_misaligned[DATA_WIDTH/8-1:0]; - end - - // hci port binding - assign tcdm_target.req = (cs != STREAMER_IDLE) ? stream.valid & addr_pop.valid : '0; - assign tcdm_target.add = (cs != STREAMER_IDLE) ? {addr_pop.data[31:2],2'b0} : '0; - assign tcdm_target.wen = '0; - assign tcdm_target.be = (cs != STREAMER_IDLE) ? stream_strb_aligned : '0; - assign tcdm_target.data = (cs != STREAMER_IDLE) ? stream_data_aligned : '0; - assign tcdm_target.r_ready = '1; - assign stream.ready = ~stream.valid | (tcdm_target.gnt & addr_pop.valid); - assign addr_pop.ready = stream.valid & stream.ready; - - // unimplemented user bits = 0 - assign tcdm_target.user = '0; + /* + * Job dispatch FSM + * The head of the job queue is presented to the underlying streamer for the whole duration of + * the job, and popped only when the streamer reports it done; this keeps the running job's + * tot_len (used by the streamer to detect completion) stable. + */ + typedef enum logic { WRAP_IDLE, WRAP_RUNNING } wrap_state_t; + wrap_state_t wrap_cs, wrap_ns; - // unimplemented id bits = 0 - assign tcdm_target.id = '0; - - // FIXME unimplemented ECC bits - assign tcdm_target.ecc = '0; - - generate - - if(TCDM_FIFO_DEPTH != 0) begin: tcdm_fifos_gen - - hci_core_fifo #( - .FIFO_DEPTH ( TCDM_FIFO_DEPTH ), - .`HCI_SIZE_PARAM(tcdm_initiator) ( `HCI_SIZE_PARAM(tcdm) ) - ) i_tcdm_fifo ( - .clk_i ( clk_i ), - .rst_ni ( rst_ni ), - .clear_i ( clear_i ), - .tcdm_target ( tcdm_target ), - .tcdm_initiator ( tcdm ), - .flags_o ( ) - ); - - end - else begin: no_tcdm_fifos_gen - - hci_core_assign i_tcdm_assign ( - .tcdm_target ( tcdm_target ), - .tcdm_initiator ( tcdm ) - ); - - end - - endgenerate - - assign tcdm_inflight = tcdm.req; - - always_ff @(posedge clk_i, negedge rst_ni) - begin : fsm_seq + always_ff @(posedge clk_i or negedge rst_ni) + begin : wrap_fsm_seq if(rst_ni == 1'b0) begin - cs <= STREAMER_IDLE; + wrap_cs <= WRAP_IDLE; end else if(clear_i == 1'b1) begin - cs <= STREAMER_IDLE; + wrap_cs <= WRAP_IDLE; end else if(enable_i) begin - cs <= ns; + wrap_cs <= wrap_ns; end end always_comb - begin : fsm_comb - ns = cs; - job_pop_ready = 1'b0; - flags_o.done = 1'b0; - address_gen_en = 1'b0; - presample = 1'b0; - address_gen_clr = clear_i; - address_cnt_clr = 1'b0; - case(cs) - STREAMER_IDLE : begin - if(job_pop.valid) begin - ns = STREAMER_PRESAMPLE; - address_gen_en = 1'b1; - presample = 1'b1; + begin : wrap_fsm_comb + wrap_ns = wrap_cs; + job_pop_ready = 1'b0; + req_start = 1'b0; + case(wrap_cs) + WRAP_IDLE : begin + if(job_pop.valid & sink_flags.ready_start) begin + wrap_ns = WRAP_RUNNING; + req_start = 1'b1; end end - STREAMER_PRESAMPLE : begin - ns = STREAMER_WORKING; - address_gen_en = 1'b1; - if(flags_o.addressgen_flags.done) begin - ns = STREAMER_DONE; - job_pop_ready = 1'b1; - end - end - STREAMER_WORKING : begin - address_gen_en = 1'b1; - if(flags_o.addressgen_flags.done) begin - ns = STREAMER_DONE; + WRAP_RUNNING : begin + if(sink_flags.done) begin + wrap_ns = WRAP_IDLE; job_pop_ready = 1'b1; end end - STREAMER_DONE : begin - address_gen_en = 1'b1; - if(address_cnt_q==ctrl_i.addressgen_ctrl.tot_len) begin - flags_o.done = 1'b1; - address_gen_en = 1'b0; - address_gen_clr = 1'b1; - address_cnt_clr = 1'b1; - if(job_pop.valid) begin - ns = STREAMER_PRESAMPLE; - end - else begin - ns = STREAMER_IDLE; - end - end - end endcase end - assign address_cnt_en = addr_pop.valid & addr_pop.ready; - - always_ff @(posedge clk_i or negedge rst_ni) - begin - if(~rst_ni) - address_cnt_q <= '0; - else if(clear_i | address_cnt_clr) - address_cnt_q <= '0; - else if(enable_i & address_cnt_en) - address_cnt_q <= address_cnt_d; - end - assign address_cnt_d = address_cnt_q + 1; - -/* - * ECC Handshake signals - */ - if(EHW > 0) begin : ecc_handshake_gen - assign tcdm_target.ereq = '{default:{tcdm_target.req}}; - assign tcdm_target.r_eready = '{default:{tcdm_target.r_ready}}; - end - else begin : no_ecc_handshake_gen - assign tcdm_target.ereq = '0; - assign tcdm_target.r_eready = '1; // assign all gnt's to 1 - end + assign sink_ctrl = '{ + req_start : req_start, + addressgen_ctrl : job_pop.data + }; -/* - * Interface size asserts - */ -`ifndef SYNTHESIS -`ifndef VERILATOR -`ifndef VCS - if(MISALIGNED_ACCESSES == 0) begin - initial - dw : assert(stream.DATA_WIDTH == tcdm.DW); - end - else begin - initial - dw : assert(stream.DATA_WIDTH+32 == tcdm.DW); - end - - `HCI_SIZE_CHECK_ASSERTS(tcdm); -`endif -`endif -`endif + assign flags_o.done = sink_flags.done; + assign flags_o.no_valid_transfers = sink_flags.no_valid_transfers; + assign flags_o.addressgen_flags = sink_flags.addressgen_flags; + + hci_core_sink #( + .TCDM_FIFO_DEPTH ( TCDM_FIFO_DEPTH ), + .TRANS_CNT ( TRANS_CNT ), + .MISALIGNED_ACCESSES ( MISALIGNED_ACCESSES ), + .ELEMENT_WIDTH ( ELEMENT_WIDTH ), + .ELEMENTS_PER_BANK ( ELEMENTS_PER_BANK ), + .DIM_ENABLE_1H ( DIM_ENABLE_1H ), + .`HCI_SIZE_PARAM(tcdm) ( `HCI_SIZE_PARAM(tcdm) ) + ) i_hci_core_sink ( + .clk_i ( clk_i ), + .rst_ni ( rst_ni ), + .test_mode_i ( test_mode_i ), + .clear_i ( clear_i ), + .enable_i ( enable_i ), + .tcdm ( tcdm ), + .stream ( stream ), + .ctrl_i ( sink_ctrl ), + .flags_o ( sink_flags ) + ); endmodule // hci_core_sink_v2 diff --git a/rtl/core/hci_core_source_v2.sv b/rtl/core/hci_core_source_v2.sv index 46ed47d..767f2d0 100644 --- a/rtl/core/hci_core_source_v2.sv +++ b/rtl/core/hci_core_source_v2.sv @@ -4,7 +4,7 @@ * Arpan Suravi Prasad * Diego Gorfini * - * Copyright (C) 2014-2025 ETH Zurich, University of Bologna + * Copyright (C) 2014-2026 ETH Zurich, University of Bologna * Copyright and related rights are licensed under the Solderpad Hardware * License, Version 0.51 (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at @@ -16,80 +16,60 @@ */ /** - * The **hci_core_source_v2** module is the high-level source streamer - * performing a series of loads on a HCI-Core interface - * and producing a HWPE-Stream data stream to feed a HWPE engine/datapath. - * The source streamer is a composite module that makes use of many other - * fundamental IPs. + * The **hci_core_source_v2** module acts as a job-queued wrapper around the + * **hci_core_source** module. It extends the functionality with a queue of + * streamer jobs, while preserving its original behavior; please refer to + * **hci_core_source** for detailed functional information on the underlying + * streamer and on all parameters not listed below. * - * Fundamentally, a source streamer acts as a specialized DMA engine acting - * out a predefined pattern from an **hwpe_stream_addressgen_v3** to perform - * a burst of loads via a HCI-Core interface, producing a HWPE-Stream - * data stream from the HCI-Core `r_data` field. - * By default, the HCI-Core streamer supports delayed accesses using a HCI-Core - * interface. - * - * Misaligned accesses are supported by widening the HCI-Core data width of 32 - * bits compared to the HWPE-Stream that gets produced by the streamer. - * Unused bytes are simply ignored. This feature can be deactivated by unsetting - * the `MISALIGNED_ACCESS` parameter; in this case, the sink will - * only work correctly if all data is aligned to a word boundary. - * - * In principle, the source streamer is insensitive to latency. - * However, when configured to support misaligned memory accesses, the address FIFO - * depth sets the maximum supported latency. - * This parameter can be controlled by the `ADDR_MIS_DEPTH` parameter (default 8). - * - * Compared to the **hci_core_source** module, the **hci_core_source_v2** introduce - * a job queue to enqueue streamer jobs earlier and streamline/optimize ctrl. + * Whereas **hci_core_source** accepts one job at a time through a + * `req_start` / `ready_start` pair, the job queue lets jobs be enqueued through + * a `valid` / `ready` handshake (`hci_streamer_v2_ctrl_t`) well before the + * streamer is able to serve them, so that a queued job can be started as soon + * as the previous one is over. The queue carries a full + * `ctrl_addressgen_v4_t`; a job is popped only once the underlying streamer + * reports it done, so the running job's configuration stays stable for its + * entire duration. * * .. tabularcolumns:: |l|l|J| * .. _hci_core_source_v2_params: - * .. table:: **hci_core_source_v2** design-time parameters. + * .. table:: **hci_core_source_v2** additional design-time parameters. * - * +------------------------+-------------+--------------------------------------------------------------------------------------------------------------------------+ - * | **Name** | **Default** | **Description** | - * +------------------------+-------------+--------------------------------------------------------------------------------------------------------------------------+ - * | *LATCH_FIFO* | 0 | If 1, use latches instead of flip-flops (requires special constraints in synthesis). | - * +------------------------+-------------+--------------------------------------------------------------------------------------------------------------------------+ - * | *TRANS_CNT* | 16 | Number of bits supported in the transaction counter of the address generator, which will overflow at 2^ `TRANS_CNT`. | - * +------------------------+-------------+--------------------------------------------------------------------------------------------------------------------------+ - * | *ADDR_MIS_DEPTH* | 8 | Depth of the misaligned address FIFO. This **must** be equal to the max-latency between the HCI-Core `gnt` and `r_valid`.| - * +------------------------+-------------+--------------------------------------------------------------------------------------------------------------------------+ - * | *MISALIGNED_ACCESS* | 1 | If set to 0, the source will not support non-word-aligned HCI-Core accesses. | - * +------------------------+-------------+--------------------------------------------------------------------------------------------------------------------------+ - * | *PASSTHROUGH_FIFO* | 0 | If set to 1, the address FIFO will be capable of fall-through operation (i.e., skipping the FIFO latency entirely). | - * +------------------------+-------------+--------------------------------------------------------------------------------------------------------------------------+ - * | *JOB_FIFO_DEPTH* | 2 | Depth of the streamer job queue. | - * +------------------------+-------------+--------------------------------------------------------------------------------------------------------------------------+ - * | *JOB_FIFO_PASSTHROUGH* | 1 | If set to 1 (default), the streamer job queue is passthrough, otherwise it's a regular path-cutting FIFO. | - * +------------------------+-------------+--------------------------------------------------------------------------------------------------------------------------+ + * +------------------------+-------------+------------------------------------------------------------------------------------------------------------+ + * | **Name** | **Default** | **Description** | + * +------------------------+-------------+------------------------------------------------------------------------------------------------------------+ + * | *JOB_FIFO_DEPTH* | 2 | Depth of the streamer job queue. | + * +------------------------+-------------+------------------------------------------------------------------------------------------------------------+ + * | *JOB_FIFO_PASSTHROUGH* | 1 | If set to 1 (default), the streamer job queue is passthrough, otherwise it's a regular path-cutting FIFO. | + * +------------------------+-------------+------------------------------------------------------------------------------------------------------------+ * * .. tabularcolumns:: |l|l|J| * .. _hci_core_source_v2_ctrl: * .. table:: **hci_core_source_v2** input control signals. * - * +-------------------+------------------------+----------------------------------------------------------------------------+ - * | **Name** | **Type** | **Description** | - * +-------------------+------------------------+----------------------------------------------------------------------------+ - * | *req_start* | `logic` | When 1, the source streamer operation is started if it is ready. | - * +-------------------+------------------------+----------------------------------------------------------------------------+ - * | *addressgen_ctrl* | `ctrl_addressgen_v3_t` | Configuration of the address generator (see **hwpe_stream_addresgen_v3**). | - * +-------------------+------------------------+----------------------------------------------------------------------------+ + * +-------------------+------------------------+-----------------------------------------------------------------------------+ + * | **Name** | **Type** | **Description** | + * +-------------------+------------------------+-----------------------------------------------------------------------------+ + * | *valid* | `logic` | When 1, the job described by `addressgen_ctrl` is enqueued if `ready` is 1. | + * +-------------------+------------------------+-----------------------------------------------------------------------------+ + * | *addressgen_ctrl* | `ctrl_addressgen_v4_t` | Configuration of the address generator (see **hwpe_stream_addressgen_v4**). | + * +-------------------+------------------------+-----------------------------------------------------------------------------+ * * .. tabularcolumns:: |l|l|J| * .. _hci_core_source_v2_flags: * .. table:: **hci_core_source_v2** output flags. * - * +--------------------+------------------------+-----------------------------------------------------------------------------------------------+ - * | **Name** | **Type** | **Description** | - * +--------------------+------------------------+-----------------------------------------------------------------------------------------------+ - * | *ready_start* | `logic` | 1 when the source streamer is ready to start operation, from the first IDLE state cycle on. | - * +--------------------+------------------------+-----------------------------------------------------------------------------------------------+ - * | *done* | `logic` | 1 for one cycle when the streamer ends operation, in the cycle before it goes to IDLE state . | - * +--------------------+------------------------+-----------------------------------------------------------------------------------------------+ - * | *addressgen_flags* | `flags_addressgen_v3_t`| Address generator flags (see **hwpe_stream_addresgen_v3**). | - * +--------------------+------------------------+-----------------------------------------------------------------------------------------------+ + * +----------------------+-------------------------+-----------------------------------------------------------------------------------------------+ + * | **Name** | **Type** | **Description** | + * +----------------------+-------------------------+-----------------------------------------------------------------------------------------------+ + * | *ready* | `logic` | 1 when the job queue can accept a new job. | + * +----------------------+-------------------------+-----------------------------------------------------------------------------------------------+ + * | *done* | `logic` | 1 for one cycle when the streamer ends a job. | + * +----------------------+-------------------------+-----------------------------------------------------------------------------------------------+ + * | *no_valid_transfers* | `logic` | Unused in the source streamer (see **hci_core_source**). | + * +----------------------+-------------------------+-----------------------------------------------------------------------------------------------+ + * | *addressgen_flags* | `flags_addressgen_v4_t` | Address generator flags (see **hwpe_stream_addressgen_v4**). | + * +----------------------+-------------------------+-----------------------------------------------------------------------------------------------+ * */ @@ -100,13 +80,17 @@ module hci_core_source_v2 import hci_package::*; #( // Stream interface params - parameter int unsigned LATCH_FIFO = 0, - parameter int unsigned TRANS_CNT = 16, - parameter int unsigned ADDR_MIS_DEPTH = 8, // Beware: this must be >= the maximum latency between TCDM gnt and TCDM r_valid!!! - parameter int unsigned MISALIGNED_ACCESSES = 1, - parameter int unsigned JOB_FIFO_DEPTH = 2, + parameter int unsigned LATCH_FIFO = 0, + parameter int unsigned TRANS_CNT = 16, + parameter int unsigned ADDR_MIS_DEPTH = 8, // Beware: this must be >= the maximum latency between TCDM gnt and TCDM r_valid!!! + parameter int unsigned MISALIGNED_ACCESSES = 1, + parameter int unsigned JOB_FIFO_DEPTH = 2, parameter int unsigned JOB_FIFO_PASSTHROUGH = 1, - parameter int unsigned PASSTHROUGH_FIFO = 0, + parameter int unsigned PASSTHROUGH_FIFO = 0, + parameter int unsigned RESP_FIFO_DEPTH = 0, + parameter int unsigned ELEMENT_WIDTH = 8, // e.g., 8 bits per element + parameter int unsigned ELEMENTS_PER_BANK = 4, // number of elements in one memory bank + parameter bit [3:0] DIM_ENABLE_1H = 4'b0011, // Number of dimensions enabled in the address generator parameter hci_size_parameter_t `HCI_SIZE_PARAM(tcdm) = '0 ) ( @@ -124,48 +108,37 @@ module hci_core_source_v2 output hci_streamer_v2_flags_t flags_o ); - localparam int unsigned DATA_WIDTH = `HCI_SIZE_GET_DW(tcdm); - localparam int unsigned EHW = `HCI_SIZE_GET_EHW(tcdm); + flags_fifo_t job_fifo_flags; - hci_streamer_state_t cs, ns; - flags_fifo_t addr_fifo_flags, job_fifo_flags; + hci_streamer_ctrl_t source_ctrl; + hci_streamer_flags_t source_flags; - logic job_pop_ready, presample; - logic address_gen_en; - logic address_gen_clr; - - hwpe_stream_intf_stream #( - .DATA_WIDTH ( 32 ) - ) addr_push ( - .clk ( clk_i ) - ); - - hwpe_stream_intf_stream #( - .DATA_WIDTH ( 32 ) - ) addr_pop ( - .clk ( clk_i ) - ); + logic job_pop_ready; + logic req_start; + /* + * Job queue + */ hwpe_stream_intf_stream #( - .DATA_WIDTH ( $bits(ctrl_addressgen_v3_t) ) + .DATA_WIDTH ( $bits(ctrl_addressgen_v4_t) ) ) job_push ( .clk ( clk_i ) ); hwpe_stream_intf_stream #( - .DATA_WIDTH ( $bits(ctrl_addressgen_v3_t) ) + .DATA_WIDTH ( $bits(ctrl_addressgen_v4_t) ) ) job_pop ( .clk ( clk_i ) ); - assign job_push.data = ctrl_i.addressgen_ctrl; - assign job_push.valid = ctrl_i.valid; - assign flags_o.ready = job_push.ready; - assign job_push.strb = '1; - + assign job_push.data = ctrl_i.addressgen_ctrl; + assign job_push.valid = ctrl_i.valid; + assign job_push.strb = '1; + assign flags_o.ready = job_push.ready; + if (JOB_FIFO_PASSTHROUGH == 1) begin : job_fifo_passthrough_gen hwpe_stream_fifo_passthrough #( - .DATA_WIDTH ( $bits(ctrl_addressgen_v3_t) ), + .DATA_WIDTH ( $bits(ctrl_addressgen_v4_t) ), .FIFO_DEPTH ( JOB_FIFO_DEPTH ) ) i_fifo_job ( .clk_i ( clk_i ), @@ -178,7 +151,7 @@ module hci_core_source_v2 end else begin : job_fifo_nopassthrough_gen hwpe_stream_fifo #( - .DATA_WIDTH ( $bits(ctrl_addressgen_v3_t) ), + .DATA_WIDTH ( $bits(ctrl_addressgen_v4_t) ), .FIFO_DEPTH ( JOB_FIFO_DEPTH ) ) i_fifo_job ( .clk_i ( clk_i ), @@ -192,256 +165,79 @@ module hci_core_source_v2 assign job_pop.ready = job_pop_ready; - // generate addresses - hwpe_stream_addressgen_v3 i_addressgen ( - .clk_i ( clk_i ), - .rst_ni ( rst_ni ), - .enable_i ( address_gen_en ), - .clear_i ( address_gen_clr ), - .presample_i ( presample ), - .addr_o ( addr_push ), - .ctrl_i ( job_pop.data ), - .flags_o ( flags_o.addressgen_flags ) - ); - - if (PASSTHROUGH_FIFO) begin : passthrough_gen - hwpe_stream_fifo_passthrough #( - .DATA_WIDTH ( 36 ), - .FIFO_DEPTH ( 2 ) - ) i_fifo_addr ( - .clk_i ( clk_i ), - .rst_ni ( rst_ni ), - .clear_i ( clear_i ), - .flags_o ( addr_fifo_flags ), - .push_i ( addr_push ), - .pop_o ( addr_pop ) - ); - end - else begin : nopassthrough_gen - hwpe_stream_fifo #( - .DATA_WIDTH ( 36 ), - .FIFO_DEPTH ( 2 ) - ) i_fifo_addr ( - .clk_i ( clk_i ), - .rst_ni ( rst_ni ), - .clear_i ( clear_i ), - .flags_o ( addr_fifo_flags ), - .push_i ( addr_push ), - .pop_o ( addr_pop ) - ); - end - - logic stream_valid_q; - logic [DATA_WIDTH-1:0] stream_data_q; - logic [1:0] addr_misaligned_q; - logic addr_misaligned_valid; - logic [DATA_WIDTH-1:0] stream_data_misaligned; - logic [DATA_WIDTH-1:0] stream_data_aligned; - - logic stream_cnt_en, stream_cnt_clr; - logic [TRANS_CNT-1:0] stream_cnt_d, stream_cnt_q; - - // this is simply exploiting the fact that we can make a wider data access than strictly necessary! - assign stream_data_misaligned = tcdm.r_valid ? tcdm.r_data : stream_data_q; // is this strictly necessary to keep the HWPE-Stream protocol? or can be avoided with a FIFO q? - - if (MISALIGNED_ACCESSES==1 ) begin : misaligned_access_gen - always_comb - begin - stream_data_aligned = '0; - case(addr_misaligned_q) - 2'b00: begin - stream_data_aligned[DATA_WIDTH-1:0] = stream_data_misaligned[DATA_WIDTH-1:0]; - end - 2'b01: begin - stream_data_aligned[DATA_WIDTH-32-1:0] = stream_data_misaligned[DATA_WIDTH-24-1:8]; - end - 2'b10: begin - stream_data_aligned[DATA_WIDTH-32-1:0] = stream_data_misaligned[DATA_WIDTH-16-1:16]; - end - 2'b11: begin - stream_data_aligned[DATA_WIDTH-32-1:0] = stream_data_misaligned[DATA_WIDTH-8-1:24]; - end - endcase - end - end - else begin - assign stream_data_aligned[DATA_WIDTH-1:0] = stream_data_misaligned[DATA_WIDTH-1:0]; - end - - assign tcdm.r_ready = stream.ready; - assign tcdm.req = (cs != STREAMER_IDLE) ? addr_pop.valid & stream.ready : '0; - assign tcdm.add = (cs != STREAMER_IDLE) ? {addr_pop.data[31:2],2'b0} : '0; - assign tcdm.wen = 1'b1; - assign tcdm.be = 4'h0; - assign tcdm.data = '0; - assign tcdm.user = '0; - assign tcdm.id = '0; - assign tcdm.ecc = '0; - assign stream.strb = '1; - assign stream.data = stream_data_aligned; - assign stream.valid = enable_i & (tcdm.r_valid | stream_valid_q); // is this strictly necessary to keep the HWPE-Stream protocol? or can be avoided with a FIFO q? - assign addr_pop.ready = (cs != STREAMER_IDLE) ? addr_pop.valid & stream.ready & tcdm.gnt : 1'b0; - - hwpe_stream_intf_stream #( - .DATA_WIDTH ( 8 ) // only 2 significant - ) addr_misaligned_push ( - .clk ( clk_i ) - ); - hwpe_stream_intf_stream #( - .DATA_WIDTH ( 8 ) // only 2 significant - ) addr_misaligned_pop ( - .clk ( clk_i ) - ); - assign addr_misaligned_push.data = {6'b0, addr_pop.data[1:0]}; - assign addr_misaligned_push.strb = '1; - assign addr_misaligned_push.valid = enable_i & tcdm.req & tcdm.gnt; // check full handshake (req&gnt): hci_core_fifo plugs gnt to a ready, therefore gnt's may be asserted without a related req. BEWARE: pop considered always ready!!! - assign addr_misaligned_pop.ready = (tcdm.r_valid | stream_valid_q) & stream.ready; - assign addr_misaligned_q = addr_misaligned_pop.data[1:0]; - - hwpe_stream_fifo #( - .DATA_WIDTH ( 8 ), // only [1:0] significant - .FIFO_DEPTH ( ADDR_MIS_DEPTH ) - ) i_addr_misaligned_fifo ( - .clk_i ( clk_i ), - .rst_ni ( rst_ni ), - .clear_i ( clear_i ), - .flags_o ( ), - .push_i ( addr_misaligned_push ), - .pop_o ( addr_misaligned_pop ) - ); + /* + * Job dispatch FSM + * The head of the job queue is presented to the underlying streamer for the whole duration of + * the job, and popped only when the streamer reports it done; this keeps the running job's + * tot_len (used by the streamer to detect completion) stable. + */ + typedef enum logic { WRAP_IDLE, WRAP_RUNNING } wrap_state_t; + wrap_state_t wrap_cs, wrap_ns; always_ff @(posedge clk_i or negedge rst_ni) - begin - if(~rst_ni) - stream_valid_q <= 1'b0; - else if(clear_i) - stream_valid_q <= 1'b0; - else if(enable_i) begin - if(tcdm.r_valid & stream.ready) - stream_valid_q <= 1'b0; - else if(tcdm.r_valid) - stream_valid_q <= 1'b1; - else if(stream_valid_q & stream.ready) - stream_valid_q <= 1'b0; - end - end - - always_ff @(posedge clk_i or negedge rst_ni) - begin - if(~rst_ni) - stream_data_q <= '0; - else if(clear_i) - stream_data_q <= '0; - else if(enable_i & tcdm.r_valid) - stream_data_q <= tcdm.r_data; - end - - always_ff @(posedge clk_i, negedge rst_ni) - begin : fsm_seq + begin : wrap_fsm_seq if(rst_ni == 1'b0) begin - cs <= STREAMER_IDLE; + wrap_cs <= WRAP_IDLE; end else if(clear_i == 1'b1) begin - cs <= STREAMER_IDLE; + wrap_cs <= WRAP_IDLE; end else if(enable_i) begin - cs <= ns; + wrap_cs <= wrap_ns; end end always_comb - begin : fsm_comb - ns = cs; - job_pop_ready = 1'b0; - flags_o.done = 1'b0; - presample = 1'b0; - address_gen_en = 1'b0; - address_gen_clr = clear_i; - stream_cnt_clr = 1'b0; - case(cs) - STREAMER_IDLE : begin - if(job_pop.valid) begin - ns = STREAMER_PRESAMPLE; - address_gen_en = 1'b1; - presample = 1'b1; + begin : wrap_fsm_comb + wrap_ns = wrap_cs; + job_pop_ready = 1'b0; + req_start = 1'b0; + case(wrap_cs) + WRAP_IDLE : begin + if(job_pop.valid & source_flags.ready_start) begin + wrap_ns = WRAP_RUNNING; + req_start = 1'b1; end end - STREAMER_PRESAMPLE : begin - ns = STREAMER_WORKING; - address_gen_en = 1'b1; - if(flags_o.addressgen_flags.done) begin - ns = STREAMER_DONE; + WRAP_RUNNING : begin + if(source_flags.done) begin + wrap_ns = WRAP_IDLE; job_pop_ready = 1'b1; end - end - STREAMER_WORKING : begin - address_gen_en = 1'b1; - if(flags_o.addressgen_flags.done) begin - ns = STREAMER_DONE; - job_pop_ready = 1'b1; - end - end - STREAMER_DONE : begin - address_gen_en = 1'b1; - if((addr_fifo_flags.empty==1'b1) && (stream_cnt_q==job_pop.data.tot_len)) begin - flags_o.done = 1'b1; - address_gen_en = 1'b0; - address_gen_clr = 1'b1; - stream_cnt_clr = 1'b1; - if(job_pop.valid) begin - ns = STREAMER_PRESAMPLE; - end - else begin - ns = STREAMER_IDLE; - end - end end endcase end - assign stream_cnt_en = stream.valid & stream.ready; - - always_ff @(posedge clk_i or negedge rst_ni) - begin - if(~rst_ni) - stream_cnt_q <= '0; - else if(clear_i | stream_cnt_clr) - stream_cnt_q <= '0; - else if(enable_i & stream_cnt_en) - stream_cnt_q <= stream_cnt_d; - end - assign stream_cnt_d = stream_cnt_q + 1; - -/* - * ECC Handshake signals - */ - if(EHW > 0) begin : ecc_handshake_gen - assign tcdm.ereq = '{default: {tcdm.req}}; - assign tcdm.r_eready = '{default: {tcdm.r_ready}}; - end - else begin : no_ecc_handshake_gen - assign tcdm.ereq = '0; - assign tcdm.r_eready = '1; // assign all gnt's to 1 - end - -/* - * Interface size asserts - */ -`ifndef SYNTHESIS -`ifndef VERILATOR -`ifndef VCS - if(MISALIGNED_ACCESSES == 0) begin - initial - dw : assert(stream.DATA_WIDTH == tcdm.DW); - end - else begin - initial - dw : assert(stream.DATA_WIDTH <= tcdm.DW); - end - - `HCI_SIZE_CHECK_ASSERTS(tcdm); -`endif -`endif -`endif + assign source_ctrl = '{ + req_start : req_start, + addressgen_ctrl : job_pop.data + }; + + assign flags_o.done = source_flags.done; + assign flags_o.no_valid_transfers = source_flags.no_valid_transfers; + assign flags_o.addressgen_flags = source_flags.addressgen_flags; + + hci_core_source #( + .LATCH_FIFO ( LATCH_FIFO ), + .TRANS_CNT ( TRANS_CNT ), + .ADDR_MIS_DEPTH ( ADDR_MIS_DEPTH ), + .MISALIGNED_ACCESSES ( MISALIGNED_ACCESSES ), + .PASSTHROUGH_FIFO ( PASSTHROUGH_FIFO ), + .RESP_FIFO_DEPTH ( RESP_FIFO_DEPTH ), + .ELEMENT_WIDTH ( ELEMENT_WIDTH ), + .ELEMENTS_PER_BANK ( ELEMENTS_PER_BANK ), + .DIM_ENABLE_1H ( DIM_ENABLE_1H ), + .`HCI_SIZE_PARAM(tcdm) ( `HCI_SIZE_PARAM(tcdm) ) + ) i_hci_core_source ( + .clk_i ( clk_i ), + .rst_ni ( rst_ni ), + .test_mode_i ( test_mode_i ), + .clear_i ( clear_i ), + .enable_i ( enable_i ), + .tcdm ( tcdm ), + .stream ( stream ), + .ctrl_i ( source_ctrl ), + .flags_o ( source_flags ) + ); endmodule // hci_core_source_v2