From 2481775ab4f6e6713bca768c2d58ebf6c0ce54b5 Mon Sep 17 00:00:00 2001 From: Philippe Sauter Date: Thu, 23 Jul 2026 17:48:37 +0200 Subject: [PATCH 1/5] Streamline HyperBus buffering and request flow Remove redundant host and isochronous buffering, adopt common_cells stream and address-decode primitives, and clarify request, atomic, configuration, and adapter control flow. --- Bender.yml | 2 +- include/hyperbus/typedef.svh | 12 +- src/backend/hyperbus_backend.sv | 20 +- src/backend/hyperbus_clock_diff_out.sv | 27 +- src/backend/hyperbus_ddr_out.sv | 18 +- src/backend/hyperbus_delay.sv | 2 +- src/backend/hyperbus_phy.sv | 59 +- src/backend/hyperbus_trx.sv | 48 +- src/hyperbus_async_bridge.sv | 20 + src/hyperbus_asynchronous.sv | 116 ++-- src/hyperbus_atomic_handler.sv | 304 ++++++---- ...perbus_axi.sv => hyperbus_axi_frontend.sv} | 235 ++++---- src/hyperbus_cfg_frontend.sv | 84 ++- src/hyperbus_cfg_regs.sv | 20 +- src/hyperbus_iso_bridge.sv | 134 ++--- src/hyperbus_isochronous.sv | 115 ++-- src/hyperbus_midend.sv | 559 +++++++++++------- src/hyperbus_pkg.sv | 27 +- src/hyperbus_read_adapter.sv | 71 ++- src/hyperbus_synchronous.sv | 103 ++-- src/hyperbus_write_adapter.sv | 160 +++-- test/axi_hyper_tb.sv | 83 ++- test/dut_if.sv | 15 +- test/fixture_hyperbus.sv | 13 +- test/hyperbus_cfg_regs_tb.sv | 5 +- test/hyperbus_tb.sv | 2 +- test/hyperbus_test_dut.sv | 6 +- 27 files changed, 1350 insertions(+), 910 deletions(-) rename src/{hyperbus_axi.sv => hyperbus_axi_frontend.sv} (60%) diff --git a/Bender.yml b/Bender.yml index c074308..4842667 100644 --- a/Bender.yml +++ b/Bender.yml @@ -55,7 +55,7 @@ sources: - src/regs/hyperbus_cfg_regblock.sv - src/hyperbus_cfg_regs.sv - src/backend/hyperbus_phy.sv - - src/hyperbus_axi.sv + - src/hyperbus_axi_frontend.sv - src/hyperbus_atomic_handler.sv - src/hyperbus_midend.sv - src/hyperbus_cfg_frontend.sv diff --git a/include/hyperbus/typedef.svh b/include/hyperbus/typedef.svh index 9474b9f..cd20bbb 100644 --- a/include/hyperbus/typedef.svh +++ b/include/hyperbus/typedef.svh @@ -5,6 +5,10 @@ `ifndef HYPERBUS_TYPEDEF_SVH_ `define HYPERBUS_TYPEDEF_SVH_ +//////////////////////////////// +// Protocol-neutral host link // +//////////////////////////////// + `define HYPERBUS_TYPEDEF_HOST_CMD_T(__name, __addr_t) \ typedef struct packed { \ logic write; \ @@ -68,7 +72,11 @@ `HYPERBUS_TYPEDEF_HOST_RSP_T(__name``_rsp_t, __name``_r_t, \ __name``_wrsp_t) -`define HYPERBUS_TYPEDEF_LINK_ALL_CT(__name, __num_phys, __num_chips) \ +//////////////////////////// +// Midend-to-backend link // +//////////////////////////// + +`define HYPERBUS_TYPEDEF_LINK_ALL_CT(__name, __num_phys) \ typedef struct packed { \ logic [(16*__num_phys)-1:0] data; \ logic last; \ @@ -84,7 +92,7 @@ } __name``_wrsp_t; \ typedef struct packed { \ hyperbus_pkg::hyper_tf_t trans; \ - logic [__num_chips-1:0] cs; \ + logic [hyperbus_pkg::HyperNumChips-1:0] cs; \ } __name``_cmd_t; \ typedef struct packed { \ __name``_cmd_t cmd; \ diff --git a/src/backend/hyperbus_backend.sv b/src/backend/hyperbus_backend.sv index f7349c8..ac3e4d2 100644 --- a/src/backend/hyperbus_backend.sv +++ b/src/backend/hyperbus_backend.sv @@ -6,7 +6,6 @@ `include "common_cells/registers.svh" module hyperbus_backend #( - parameter int unsigned NumChips = -1, parameter int unsigned NumPhys = 2, parameter int unsigned StartupCycles = 60000, parameter int unsigned SyncStages = 2, @@ -31,7 +30,7 @@ module hyperbus_backend #( input hyper_req_t req_i, output hyper_rsp_t rsp_o, - output logic [NumPhys-1:0][NumChips-1:0] hyper_cs_no, + output logic [NumPhys-1:0][hyperbus_pkg::HyperNumChips-1:0] hyper_cs_no, output logic [NumPhys-1:0] hyper_ck_o, output logic [NumPhys-1:0] hyper_ck_no, output logic [NumPhys-1:0] hyper_rwds_o, @@ -43,12 +42,15 @@ module hyperbus_backend #( output logic [NumPhys-1:0] hyper_reset_no ); + ///////////////////////////// + // Configuration and clock // + ///////////////////////////// + hyperbus_pkg::phy_cfg_t cfg_q; - logic cfg_apply_accepted; - logic phy_busy_any; - logic clk_tx; + logic cfg_apply_accepted; + logic phy_busy_any; + logic clk_tx; - `ASSERT_INIT(NumChipsValid, NumChips >= 1 && NumChips <= 8) `ASSERT_INIT(NumPhysValid, NumPhys == 1 || NumPhys == 2) `ASSERT_INIT(SyncStagesValid, SyncStages >= 2) @@ -69,6 +71,10 @@ module hyperbus_backend #( .out_o ( clk_tx ) ); + ///////////////////// + // Physical lanes // + ///////////////////// + if (NumPhys == 2) begin : gen_dual_phy hyperbus_pkg::phy_rx_t [NumPhys-1:0] phy_rx; hyperbus_pkg::phy_rx_t [NumPhys-1:0] buffered_rx; @@ -169,7 +175,6 @@ module hyperbus_backend #( ); hyperbus_phy #( - .NumChips ( NumChips ), .StartupCycles ( StartupCycles ), .NumPhys ( NumPhys ), .SyncStages ( SyncStages ) @@ -211,7 +216,6 @@ module hyperbus_backend #( end end else begin : gen_single_phy hyperbus_phy #( - .NumChips ( NumChips ), .StartupCycles ( StartupCycles ), .NumPhys ( NumPhys ), .SyncStages ( SyncStages ) diff --git a/src/backend/hyperbus_clock_diff_out.sv b/src/backend/hyperbus_clock_diff_out.sv index 67c6ae6..72ce9fc 100644 --- a/src/backend/hyperbus_clock_diff_out.sv +++ b/src/backend/hyperbus_clock_diff_out.sv @@ -9,28 +9,27 @@ (* no_ungroup *) (* no_boundary_optimization *) (* keep_hierarchy = "yes" *) -module hyperbus_clock_diff_out -( +module hyperbus_clock_diff_out ( input logic in_i, - input logic en_i, //high enable + input logic en_i, output logic out_o, output logic out_no ); - `ifdef FPGA_EMUL +`ifdef FPGA_EMUL - logic en_sync; + logic en_sync; - always_latch - begin - if (in_i == 1'b0) - en_sync <= en_i; - end + always_latch begin + if (in_i == 1'b0) begin + en_sync <= en_i; + end + end - assign out_o = in_i & en_sync; - assign out_no = ~out_o; + assign out_o = in_i & en_sync; + assign out_no = ~out_o; - `else +`else tc_clk_gating i_hyper_ck_gating ( .clk_i ( in_i ), @@ -44,6 +43,6 @@ module hyperbus_clock_diff_out .clk_o ( out_no ) ); - `endif // !`ifdef FPGA_EMUL +`endif endmodule diff --git a/src/backend/hyperbus_ddr_out.sv b/src/backend/hyperbus_ddr_out.sv index d737d15..38f57f5 100644 --- a/src/backend/hyperbus_ddr_out.sv +++ b/src/backend/hyperbus_ddr_out.sv @@ -7,7 +7,7 @@ module hyperbus_ddr_out #( parameter logic Init = 1'b0 -)( +) ( input logic clk_i, input logic rst_ni, input logic d0_i, @@ -18,13 +18,13 @@ module hyperbus_ddr_out #( logic q1; `ifdef FPGA_EMUL - always_comb - begin - if(clk_i == 1'b0) - q_o = q1; - else - q_o = q0; - end + always_comb begin + if (clk_i == 1'b0) begin + q_o = q1; + end else begin + q_o = q0; + end + end `else tc_clk_mux2 i_ddrmux ( .clk_o ( q_o ), @@ -32,7 +32,7 @@ module hyperbus_ddr_out #( .clk1_i ( q0 ), .clk_sel_i ( clk_i ) ); -`endif // !`ifdef FPGA_EMUL +`endif always_ff @(posedge clk_i or negedge rst_ni) begin if (~rst_ni) begin diff --git a/src/backend/hyperbus_delay.sv b/src/backend/hyperbus_delay.sv index 00ea849..43dffc8 100644 --- a/src/backend/hyperbus_delay.sv +++ b/src/backend/hyperbus_delay.sv @@ -20,7 +20,7 @@ module hyperbus_delay ( // Additional delay can be added for debug purposes, // the upper 3 bits are reserved for this optional additional delay configurable_delay #( - .NUM_STEPS(32) + .NUM_STEPS ( 32 ) ) i_delay ( .clk_i ( in_i ), .delay_i ( delay_i[4:0] ), diff --git a/src/backend/hyperbus_phy.sv b/src/backend/hyperbus_phy.sv index c531e67..031a830 100644 --- a/src/backend/hyperbus_phy.sv +++ b/src/backend/hyperbus_phy.sv @@ -10,12 +10,12 @@ `include "common_cells/assertions.svh" module hyperbus_phy import hyperbus_pkg::*; #( - parameter int unsigned NumChips = 2, parameter int unsigned NumPhys = -1, parameter int unsigned TimerWidth = 16, parameter int unsigned RxFifoLogDepth = 3, parameter int unsigned SyncStages = 2, - parameter int unsigned StartupCycles = 300 /*us*/ * 200 /*MHz*/ // Conservative maximum frequency estimate + // Conservative startup delay: 300 us at 200 MHz. + parameter int unsigned StartupCycles = 300 * 200 )( input logic clk_i, input logic clk_tx_i, @@ -29,7 +29,7 @@ module hyperbus_phy import hyperbus_pkg::*; #( input logic trans_valid_i, output logic trans_ready_o, input hyper_tf_t trans_i, // TODO: increase burst width! - input logic [NumChips-1:0] trans_cs_i, + input logic [HyperNumChips-1:0] trans_cs_i, // Transmitting channel input logic tx_valid_i, output logic tx_ready_o, @@ -47,7 +47,7 @@ module hyperbus_phy import hyperbus_pkg::*; #( input logic b_ready_i, output logic b_error_o, // Physical interface - output logic [NumChips-1:0] hyper_cs_no, + output logic [HyperNumChips-1:0] hyper_cs_no, output logic hyper_ck_o, output logic hyper_ck_no, output logic hyper_rwds_o, @@ -71,11 +71,14 @@ module hyperbus_phy import hyperbus_pkg::*; #( assign words_per_beat = (NumPhys == 2 && cfg_i.dual_phy) ? 2 : 1; - // PHY state + ////////////////////// + // Persistent state // + ////////////////////// + hyper_phy_state_t state_d, state_q; logic [TimerWidth-1:0] timer_d, timer_q; hyper_tf_t tf_d, tf_q; - logic [NumChips-1:0] cs_d, cs_q; + logic [HyperNumChips-1:0] cs_d, cs_q; logic add_latency_d, add_latency_q; // Whether B response is pending @@ -88,7 +91,7 @@ module hyperbus_phy import hyperbus_pkg::*; #( logic r_outstand_inc; logic r_outstand_dec; - // Auxiliar control signals + // Auxiliary control signals logic ctl_write_zero_lat; logic ctl_add_latency; logic ctl_tf_burst_last; @@ -105,7 +108,7 @@ module hyperbus_phy import hyperbus_pkg::*; #( // Command-address hyper_phy_ca_t ca; - // Transciever IO + // Transceiver I/O logic trx_clk_ena; logic trx_cs_ena; logic trx_rwds_sample; @@ -120,12 +123,11 @@ module hyperbus_phy import hyperbus_pkg::*; #( logic trx_rx_valid; logic trx_rx_ready; - // ================= - // Transciever - // ================= + ////////////////////// + // Transceiver I/O // + ////////////////////// hyperbus_trx #( - .NumChips ( NumChips ), .RxFifoLogDepth ( RxFifoLogDepth ), .SyncStages ( SyncStages ) ) i_trx ( @@ -160,9 +162,9 @@ module hyperbus_phy import hyperbus_pkg::*; #( .hyper_reset_no ); - // ============== - // Dataflow - // ============== + ////////////// + // Dataflow // + ////////////// // Command-address assign ca = hyper_phy_ca_t '{ @@ -226,9 +228,9 @@ module hyperbus_phy import hyperbus_pkg::*; #( else if (r_outstand_dec & ~r_outstand_inc) r_outstand_q <= r_outstand_q - 1; end - // ============= - // Control - // ============= + ///////////// + // Control // + ///////////// // Auxiliary control signals assign ctl_write_zero_lat = tf_q.address_space & tf_q.write; @@ -265,7 +267,8 @@ module hyperbus_phy import hyperbus_pkg::*; #( trx_tx_rwds_oe = 1'b0; trx_tx_data_oe = 1'b0; // State-dependent logic - case (state_q) + unique case (state_q) + // Hold chip select inactive until the startup delay expires. Startup: begin trx_cs_ena = 1'b0; // Timer resets to parameterized startup delay @@ -273,6 +276,7 @@ module hyperbus_phy import hyperbus_pkg::*; #( state_d = Idle; end end + // Accept a transfer only after all responses from the previous one have drained. Idle: begin trx_cs_ena = 1'b0; timer_d = timer_q; @@ -302,6 +306,7 @@ module hyperbus_phy import hyperbus_pkg::*; #( trx_tx_data_oe = 1'b1; end end + // Assert chip select early when RWDS needs extra setup time before CK starts. DelayCK: begin trx_clk_ena = 1'b0; trx_rwds_sample_ena = ~ctl_write_zero_lat; @@ -310,6 +315,7 @@ module hyperbus_phy import hyperbus_pkg::*; #( state_d = SendCA; end end + // Shift the three command/address words onto DQ. SendCA: begin // Dataflow handled outside FSM trx_clk_ena = 1'b1; @@ -326,6 +332,7 @@ module hyperbus_phy import hyperbus_pkg::*; #( end end end + // Wait one access-latency interval and sample the additional-latency request. WaitLatAccess: begin trx_clk_ena = 1'b1; trx_tx_data_oe = 1'b1; @@ -361,6 +368,7 @@ module hyperbus_phy import hyperbus_pkg::*; #( add_latency_d = 1'b0; end end + // Complete the requested second access-latency interval. WaitAddLatAccess: begin // Same as WaitLatAccess but without possibility // of adding another latency count @@ -379,6 +387,7 @@ module hyperbus_phy import hyperbus_pkg::*; #( end end end + // Capture read data until this segment completes or reaches its time limit. Read: begin // Dataflow handled outside FSM trx_rx_clk_set = 1'b1; @@ -398,6 +407,7 @@ module hyperbus_phy import hyperbus_pkg::*; #( state_d = WaitXfer; end end + // Transmit write data until this segment completes or reaches its time limit. Write: begin // Drive DQ lines in write mode trx_tx_data_oe = 1'b1; @@ -420,6 +430,7 @@ module hyperbus_phy import hyperbus_pkg::*; #( state_d = WaitXfer; end end + // Keep chip select asserted until the final generated clock edge has settled. WaitXfer: begin // Wait for FFed Clock and output to stop // May have to be prolonged for potential future devices with t_CSH > 0 @@ -428,6 +439,7 @@ module hyperbus_phy import hyperbus_pkg::*; #( state_d = WaitRWR; end end + // Enforce read/write recovery, then continue a split transfer or return idle. WaitRWR: begin trx_cs_ena = 1'b0; if (ctl_timer_rwr_done) begin @@ -445,6 +457,15 @@ module hyperbus_phy import hyperbus_pkg::*; #( end end end + // Recover safely from an invalid state through the normal startup sequence. + default: begin + state_d = Startup; + timer_d = StartupCycles; + tf_d = hyper_tf_t'{burst_type: 1'b1, default: '0}; + cs_d = '0; + add_latency_d = 1'b0; + trx_cs_ena = 1'b0; + end endcase end diff --git a/src/backend/hyperbus_trx.sv b/src/backend/hyperbus_trx.sv index 73fc128..d0b882e 100644 --- a/src/backend/hyperbus_trx.sv +++ b/src/backend/hyperbus_trx.sv @@ -9,7 +9,6 @@ `include "common_cells/assertions.svh" module hyperbus_trx #( - parameter int unsigned NumChips = 2, parameter int unsigned RxFifoLogDepth = 3, parameter int unsigned SyncStages = 2 )( @@ -20,7 +19,7 @@ module hyperbus_trx #( input logic test_mode_i, // Transceiver control: facing controller - input logic [NumChips-1:0] cs_i, + input logic [hyperbus_pkg::HyperNumChips-1:0] cs_i, input logic cs_ena_i, output logic rwds_sample_o, input logic rwds_sample_ena_i, @@ -38,7 +37,7 @@ module hyperbus_trx #( output logic rx_valid_o, input logic rx_ready_i, // Physical interface: facing HyperBus - output logic [NumChips-1:0] hyper_cs_no, + output logic [hyperbus_pkg::HyperNumChips-1:0] hyper_cs_no, output logic hyper_ck_o, output logic hyper_ck_no, output logic hyper_rwds_o, @@ -50,27 +49,14 @@ module hyperbus_trx #( output logic hyper_reset_no ); - logic tx_clk_ena_q; - logic rx_rwds_90; - - // Delayed clock enable synchronous with data - - // Intermediate RX signals for RWDS domain - logic rx_rwds_clk_ena; - logic rx_rwds_clk_orig; - logic rx_rwds_clk; - logic rx_rwds_clk_n; - logic rx_capture_rst; - logic [15:0] rx_rwds_fifo_in; - logic rx_rwds_fifo_valid; - logic rx_rwds_fifo_ready; - // Feed through async reset assign hyper_reset_no = rst_ni; - // ================= - // TX + control - // ================= + ////////////////// + // TX and control // + ////////////////// + + logic tx_clk_ena_q; // The delayed differential output clock samples output bytes centrally. // TODO: tx_clk_ena_q must arrive before clk_tx_i to avoid disturbing clock gating. @@ -125,9 +111,19 @@ module hyperbus_trx #( end end - // ======== - // RX - // ======== + ///////////////////// + // RX data capture // + ///////////////////// + + logic rx_rwds_90; + logic rx_rwds_clk_ena; + logic rx_rwds_clk_orig; + logic rx_rwds_clk; + logic rx_rwds_clk_n; + logic rx_capture_rst; + logic [15:0] rx_rwds_fifo_in; + logic rx_rwds_fifo_valid; + logic rx_rwds_fifo_ready; // Sample RWDS for extra latency determination. always_ff @(posedge clk_i or negedge rst_ni) begin : proc_ff_rwds_sample @@ -169,8 +165,8 @@ module hyperbus_trx #( .clk_o ( rx_rwds_clk_orig ) ); - // Reset RX state on async reset or on gated clock (whenever inactive) - // TODO: is this safe? Replace with tech cells? + // Reset RX state on async reset or on gated clock (whenever inactive) + // TODO: is this safe? Replace with tech cells? assign rx_capture_rst = !rst_ni || (!rx_rwds_clk_ena && !test_mode_i); // RX data is valid one cycle after each RX soft reset diff --git a/src/hyperbus_async_bridge.sv b/src/hyperbus_async_bridge.sv index 9d97ed2..e2644c2 100644 --- a/src/hyperbus_async_bridge.sv +++ b/src/hyperbus_async_bridge.sv @@ -36,6 +36,10 @@ module hyperbus_async_bridge #( input logic cfg_apply_ready_i ); + //////////////////////////// + // Configuration crossing // + //////////////////////////// + logic cfg_apply_accepted; logic cfg_apply_pending_d, cfg_apply_pending_q; @@ -81,6 +85,10 @@ module hyperbus_async_bridge #( .dst_ready_i ( cfg_apply_ready_i ) ); + ////////////////////// + // Command crossing // + ////////////////////// + cdc_2phase_clearable #( .T ( hyper_cmd_t ), .SYNC_STAGES ( CdcSyncStages ) @@ -101,6 +109,10 @@ module hyperbus_async_bridge #( .dst_ready_i ( backend_rsp_i.cmd_ready ) ); + ///////////////////////////// + // Write-response crossing // + ///////////////////////////// + cdc_2phase_clearable #( .T ( hyper_wrsp_t ), .SYNC_STAGES ( CdcSyncStages ) @@ -121,6 +133,10 @@ module hyperbus_async_bridge #( .dst_ready_i ( frontend_req_i.wrsp_ready ) ); + /////////////////////// + // TX data crossing // + /////////////////////// + cdc_fifo_gray_clearable #( .T ( hyper_tx_t ), .LOG_DEPTH ( TxFifoLogDepth ), @@ -142,6 +158,10 @@ module hyperbus_async_bridge #( .dst_ready_i ( backend_rsp_i.tx_ready ) ); + /////////////////////// + // RX data crossing // + /////////////////////// + cdc_fifo_gray_clearable #( .T ( hyper_rx_t ), .LOG_DEPTH ( RxFifoLogDepth ), diff --git a/src/hyperbus_asynchronous.sv b/src/hyperbus_asynchronous.sv index 376890a..3069b4e 100644 --- a/src/hyperbus_asynchronous.sv +++ b/src/hyperbus_asynchronous.sv @@ -6,23 +6,24 @@ `include "common_cells/assertions.svh" module hyperbus_asynchronous #( - parameter int unsigned NumChips = -1, - parameter int unsigned NumPhys = 2, - parameter int unsigned AxiAddrWidth = -1, - parameter int unsigned AxiDataWidth = -1, - parameter int unsigned AxiIdWidth = -1, - parameter int unsigned AxiUserWidth = -1, - parameter type axi_req_t = logic, - parameter type axi_rsp_t = logic, - parameter int unsigned RegDataWidth = -1, - parameter type reg_req_t = logic, - parameter type reg_rsp_t = logic, - parameter type axi_rule_t = logic, - parameter int unsigned RxFifoLogDepth = 3, - parameter int unsigned TxFifoLogDepth = 3, - parameter int unsigned PhyStartupCycles = 300 * 200, - parameter int unsigned SyncStages = 2, - parameter int unsigned CdcSyncStages = 3 + parameter int unsigned NumPhys = 2, + parameter int unsigned AxiAddrWidth = -1, + parameter int unsigned AxiDataWidth = -1, + parameter int unsigned AxiIdWidth = -1, + parameter int unsigned AxiUserWidth = -1, + parameter type axi_req_t = logic, + parameter type axi_rsp_t = logic, + parameter int unsigned RegDataWidth = -1, + parameter type reg_req_t = logic, + parameter type reg_rsp_t = logic, + parameter type axi_rule_t = logic, + parameter int unsigned HostCommandDepth = 8, + parameter int unsigned HostWriteBufferBytes = 128, + parameter int unsigned RxFifoLogDepth = 3, + parameter int unsigned TxFifoLogDepth = 3, + parameter int unsigned PhyStartupCycles = 300 * 200, + parameter int unsigned SyncStages = 2, + parameter int unsigned CdcSyncStages = 3 ) ( input logic clk_sys_i, input logic rst_sys_ni, @@ -39,7 +40,7 @@ module hyperbus_asynchronous #( input reg_req_t reg_req_i, output reg_rsp_t reg_rsp_o, - output logic [NumPhys-1:0][NumChips-1:0] hyper_cs_no, + output logic [NumPhys-1:0][hyperbus_pkg::HyperNumChips-1:0] hyper_cs_no, output logic [NumPhys-1:0] hyper_ck_o, output logic [NumPhys-1:0] hyper_ck_no, output logic [NumPhys-1:0] hyper_rwds_o, @@ -57,10 +58,20 @@ module hyperbus_asynchronous #( typedef logic [AxiDataWidth-1:0] host_data_t; typedef logic [AxiDataWidth/8-1:0] host_strb_t; `HYPERBUS_TYPEDEF_HOST_ALL_CT(host, host_addr_t, host_data_t, host_strb_t) - `HYPERBUS_TYPEDEF_LINK_ALL_CT(hyper, NumPhys, NumChips) + `HYPERBUS_TYPEDEF_LINK_ALL_CT(hyper, NumPhys) + + ///////////////////// + // Clock and reset // + ///////////////////// + + logic clk_backend; + logic rst_backend_n; + logic rst_phy_async_n; + + //////////////////////// + // Configuration path // + //////////////////////// - logic clk_backend; - logic rst_backend_n; hyperbus_pkg::phy_cfg_t frontend_cfg_apply; logic frontend_cfg_apply_valid; logic frontend_cfg_apply_ready; @@ -73,7 +84,11 @@ module hyperbus_asynchronous #( logic backend_cfg_apply_ready; hyperbus_pkg::frontend_cfg_t frontend_cfg; - axi_rule_t [NumChips-1:0] frontend_chip_rules; + axi_rule_t [hyperbus_pkg::HyperNumChips-1:0] frontend_chip_rules; + + //////////////////// + // Dataflow links // + //////////////////// host_req_t host_req; host_rsp_t host_rsp; @@ -84,8 +99,6 @@ module hyperbus_asynchronous #( hyper_req_t backend_req; hyper_rsp_t backend_rsp; - logic rst_phy_async_n; - // Clearable CDCs permit an independent PHY reset without creating phantom // transfers. Such a reset can discard a pending transfer and is therefore // only a supported software operation while the controller is idle. @@ -101,8 +114,11 @@ module hyperbus_asynchronous #( .init_no ( ) ); + //////////////////////////// + // Configuration frontend // + //////////////////////////// + hyperbus_cfg_frontend #( - .NumChips ( NumChips ), .NumPhys ( NumPhys ), .reg_req_t ( reg_req_t ), .reg_rsp_t ( reg_rsp_t ), @@ -129,6 +145,10 @@ module hyperbus_asynchronous #( .decode_error_i ( midend_decode_error ) ); + ////////////////// + // AXI frontend // + ////////////////// + hyperbus_axi_frontend #( .AxiDataWidth ( AxiDataWidth ), .AxiAddrWidth ( AxiAddrWidth ), @@ -149,23 +169,28 @@ module hyperbus_asynchronous #( .host_rsp_i ( host_rsp ) ); + //////////// + // Midend // + //////////// + hyperbus_midend #( - .HostAddrWidth ( AxiAddrWidth ), - .HostDataWidth ( AxiDataWidth ), - .NumChips ( NumChips ), - .NumPhys ( NumPhys ), - .host_cmd_t ( host_cmd_t ), - .host_w_t ( host_w_t ), - .host_r_t ( host_r_t ), - .host_wrsp_t ( host_wrsp_t ), - .host_req_t ( host_req_t ), - .host_rsp_t ( host_rsp_t ), - .hyper_rx_t ( hyper_rx_t ), - .hyper_tx_t ( hyper_tx_t ), - .hyper_cmd_t ( hyper_cmd_t ), - .hyper_req_t ( hyper_req_t ), - .hyper_rsp_t ( hyper_rsp_t ), - .rule_t ( axi_rule_t ) + .HostAddrWidth ( AxiAddrWidth ), + .HostDataWidth ( AxiDataWidth ), + .NumPhys ( NumPhys ), + .HostCommandDepth ( HostCommandDepth ), + .HostWriteBufferBytes ( HostWriteBufferBytes ), + .host_cmd_t ( host_cmd_t ), + .host_w_t ( host_w_t ), + .host_r_t ( host_r_t ), + .host_wrsp_t ( host_wrsp_t ), + .host_req_t ( host_req_t ), + .host_rsp_t ( host_rsp_t ), + .hyper_rx_t ( hyper_rx_t ), + .hyper_tx_t ( hyper_tx_t ), + .hyper_cmd_t ( hyper_cmd_t ), + .hyper_req_t ( hyper_req_t ), + .hyper_rsp_t ( hyper_rsp_t ), + .rule_t ( axi_rule_t ) ) i_midend ( .clk_i ( clk_sys_i ), .rst_ni ( rst_sys_ni ), @@ -179,6 +204,10 @@ module hyperbus_asynchronous #( .hyper_link_rsp_i ( midend_rsp ) ); + ///////////////////////// + // Asynchronous bridge // + ///////////////////////// + hyperbus_async_bridge #( .RxFifoLogDepth ( RxFifoLogDepth ), .TxFifoLogDepth ( TxFifoLogDepth ), @@ -207,8 +236,11 @@ module hyperbus_asynchronous #( .cfg_apply_ready_i ( backend_cfg_apply_ready ) ); + ///////////// + // Backend // + ///////////// + hyperbus_backend #( - .NumChips ( NumChips ), .NumPhys ( NumPhys ), .StartupCycles ( PhyStartupCycles ), .SyncStages ( SyncStages ), diff --git a/src/hyperbus_atomic_handler.sv b/src/hyperbus_atomic_handler.sv index 4daddea..b301199 100644 --- a/src/hyperbus_atomic_handler.sv +++ b/src/hyperbus_atomic_handler.sv @@ -4,6 +4,7 @@ `include "common_cells/registers.svh" +/// Execute one atomic request as an exclusive backend read-modify-write sequence. module hyperbus_atomic_handler #( parameter int unsigned HostAddrWidth = -1, parameter int unsigned HostDataWidth = -1, @@ -70,6 +71,9 @@ module hyperbus_atomic_handler #( logic atomic_return; } atomic_request_t; + ////////////////////// + // Persistent state // + ////////////////////// atomic_state_e state_d, state_q; atomic_request_t request_d, request_q; host_data_t operand_d, operand_q; @@ -78,119 +82,173 @@ module hyperbus_atomic_handler #( logic read_pending_d, read_pending_q; logic write_pending_d, write_pending_q; - host_data_t value_mask; - host_data_t old_value; - host_data_t operand_value; - host_data_t swap_value; - host_data_t result_value; - host_data_t write_data; - int unsigned byte_offset; - int unsigned operand_bytes; - int unsigned operand_bits; + //////////////////////// + // Request sequencing // + //////////////////////// + atomic_request_t request_in; + logic operation_valid; + hyperbus_pkg::hyper_resp_e initial_response, backend_write_response; + logic atomic_started, operand_accepted; + logic valid_operand_accepted, invalid_operand_completed; + logic command_accepted; + logic backend_read_accepted, backend_read_succeeded, backend_read_failed; + logic backend_write_accepted, backend_write_rsp_accepted; + logic host_read_accepted, host_write_rsp_accepted; + + assign request_in = '{ + addr: request_i.addr, + size: request_i.size, + atomic_op: request_i.atomic_op, + atomic_return: request_i.atomic_return + }; + + // A start captures the request even when validation failed. In that case the + // handler only drains its write data and returns an atomic error. + assign atomic_started = (state_q == Idle) && start_i; + assign operation_valid = response_q == hyperbus_pkg::HyperRespOkay; + assign initial_response = request_valid_i ? hyperbus_pkg::HyperRespOkay : + hyperbus_pkg::HyperRespAtomicError; + assign operand_accepted = host_w_valid_i && host_w_ready_o; + assign valid_operand_accepted = operand_accepted && operation_valid; + assign invalid_operand_completed = operand_accepted && !operation_valid && host_w_i.last; + + // These events advance the backend read-modify-write sequence. + // Read errors skip the write, write errors are reported after the write response. + assign command_accepted = command_valid_o && command_ready_i; + assign backend_read_accepted = read_valid_i && read_ready_o; + assign backend_read_succeeded = backend_read_accepted && + (read_i.resp == hyperbus_pkg::HyperRespOkay); + assign backend_read_failed = backend_read_accepted && + (read_i.resp != hyperbus_pkg::HyperRespOkay); + assign backend_write_accepted = write_valid_o && write_ready_i; + assign backend_write_rsp_accepted = write_rsp_valid_i && write_rsp_ready_o; + assign backend_write_response = write_rsp_error_i ? + hyperbus_pkg::HyperRespAccessError : hyperbus_pkg::HyperRespOkay; + + // Hold both host responses until independently accepted. + // Completion is asserted in the cycle where the final pending response is accepted. + assign host_read_accepted = host_r_valid_o && host_r_ready_i; + assign host_write_rsp_accepted = host_wrsp_valid_o && host_wrsp_ready_i; + assign completed_o = (state_q == ReturnResponse) && + (!read_pending_q || host_read_accepted) && + (!write_pending_q || host_write_rsp_accepted); assign active_o = state_q != Idle; assign command_valid_o = (state_q == ReadCommand) || (state_q == WriteCommand); - assign completed_o = (state_q == ReturnResponse) && - (!read_pending_q || (host_r_valid_o && host_r_ready_i)) && - (!write_pending_q || (host_wrsp_valid_o && host_wrsp_ready_i)); + ////////////////////// + // Backend commands // + ////////////////////// + // Atomic commands are converted into one backend read followed by one write. always_comb begin : proc_command command_o = '0; command_o.write = state_q == WriteCommand; command_o.addr = request_q.addr; command_o.beats = 1; - command_o.size = request_q.size; + command_o.size = ((request_q.atomic_op == hyperbus_pkg::HyperAtomicCompare) && + (request_q.size != '0)) ? + request_q.size - 1'b1 : request_q.size; command_o.burst = hyperbus_pkg::HyperBurstIncr; command_o.atomic_op = hyperbus_pkg::HyperAtomicNone; command_o.atomic_return = 1'b0; command_o.ordered = 1'b1; - if ((request_q.atomic_op == hyperbus_pkg::HyperAtomicCompare) && - (request_q.size != '0)) begin - command_o.size = request_q.size - 1'b1; - end end - always_comb begin : proc_alu - byte_offset = 0; + //////////////// + // Atomic ALU // + //////////////// + // The operation works on one contiguous byte-lane range within the host data word. + host_data_t value_mask, write_mask; + host_data_t alu_operand_a, alu_operand_b; + host_data_t compare_value, swap_value; + host_data_t alu_result, shifted_alu_result; + host_data_t preserved_data, write_data; + int unsigned byte_offset, operand_bytes, operand_bits; + logic alu_enable, alu_equal; + logic alu_signed_less, alu_unsigned_less; + + // Select and align the memory value and write operand before the ALU. + always_comb begin : proc_alu_operands + byte_offset = 0; operand_bytes = 1; - operand_bits = 8; - value_mask = '1; - old_value = read_data_q; - operand_value = operand_q; - swap_value = operand_q >> 8; - result_value = old_value; - write_data = read_data_q; + operand_bits = 8; + value_mask = '1; if (response_q == hyperbus_pkg::HyperRespOkay) begin byte_offset = unsigned'(request_q.addr[HostBusAddrWidth-1:0]); operand_bytes = 1 << ((request_q.atomic_op == hyperbus_pkg::HyperAtomicCompare) ? - (request_q.size - 1'b1) : request_q.size); - operand_bits = operand_bytes * 8; - if (operand_bits < HostDataWidth) begin - value_mask = value_mask >> (HostDataWidth - operand_bits); - end + request_q.size - 1'b1 : request_q.size); + operand_bits = operand_bytes * 8; + value_mask = value_mask >> (HostDataWidth - operand_bits); + end - old_value = read_data_q >> (byte_offset * 8); - operand_value = operand_q >> (byte_offset * 8); - swap_value = operand_value >> operand_bits; - result_value = old_value; + alu_operand_a = (read_data_q >> (byte_offset * 8)) & value_mask; + // Compare operations carry the compare value below the replacement value. + compare_value = (operand_q >> (byte_offset * 8)) & value_mask; + swap_value = (operand_q >> ((byte_offset * 8) + operand_bits)) & value_mask; + alu_operand_b = (request_q.atomic_op == hyperbus_pkg::HyperAtomicCompare) ? + swap_value : compare_value; + end + + assign alu_enable = (state_q == WriteData) && + (response_q == hyperbus_pkg::HyperRespOkay); + assign alu_equal = alu_operand_a == compare_value; + assign alu_unsigned_less = alu_operand_a < alu_operand_b; + assign alu_signed_less = (alu_operand_a[operand_bits-1] != + alu_operand_b[operand_bits-1]) ? + alu_operand_a[operand_bits-1] : alu_unsigned_less; + // Apply the selected operation to the aligned operands. + always_comb begin : proc_alu + alu_result = alu_operand_a; + if (alu_enable) begin unique case (request_q.atomic_op) - hyperbus_pkg::HyperAtomicSwap: result_value = operand_value; - hyperbus_pkg::HyperAtomicCompare: begin - if ((old_value & value_mask) == (operand_value & value_mask)) begin - result_value = swap_value; - end - end - hyperbus_pkg::HyperAtomicAdd: result_value = old_value + operand_value; - hyperbus_pkg::HyperAtomicClear: result_value = old_value & ~operand_value; - hyperbus_pkg::HyperAtomicXor: result_value = old_value ^ operand_value; - hyperbus_pkg::HyperAtomicSet: result_value = old_value | operand_value; - hyperbus_pkg::HyperAtomicSignedMax: begin - if ((old_value[operand_bits-1] && !operand_value[operand_bits-1]) || - ((old_value[operand_bits-1] == operand_value[operand_bits-1]) && - ((old_value & value_mask) < (operand_value & value_mask)))) begin - result_value = operand_value; - end - end - hyperbus_pkg::HyperAtomicSignedMin: begin - if ((!old_value[operand_bits-1] && operand_value[operand_bits-1]) || - ((old_value[operand_bits-1] == operand_value[operand_bits-1]) && - ((old_value & value_mask) > (operand_value & value_mask)))) begin - result_value = operand_value; - end - end - hyperbus_pkg::HyperAtomicUnsignedMax: begin - if ((old_value & value_mask) < (operand_value & value_mask)) begin - result_value = operand_value; - end - end - hyperbus_pkg::HyperAtomicUnsignedMin: begin - if ((old_value & value_mask) > (operand_value & value_mask)) begin - result_value = operand_value; - end - end + hyperbus_pkg::HyperAtomicSwap: alu_result = alu_operand_b; + hyperbus_pkg::HyperAtomicCompare: alu_result = alu_equal ? + alu_operand_b : alu_operand_a; + hyperbus_pkg::HyperAtomicAdd: alu_result = alu_operand_a + alu_operand_b; + hyperbus_pkg::HyperAtomicAnd: alu_result = alu_operand_a & alu_operand_b; + hyperbus_pkg::HyperAtomicClear: alu_result = alu_operand_a & ~alu_operand_b; + hyperbus_pkg::HyperAtomicXor: alu_result = alu_operand_a ^ alu_operand_b; + hyperbus_pkg::HyperAtomicSet: alu_result = alu_operand_a | alu_operand_b; + hyperbus_pkg::HyperAtomicSignedMax: alu_result = alu_signed_less ? + alu_operand_b : alu_operand_a; + hyperbus_pkg::HyperAtomicSignedMin: alu_result = alu_signed_less ? + alu_operand_a : alu_operand_b; + hyperbus_pkg::HyperAtomicUnsignedMax: alu_result = alu_unsigned_less ? + alu_operand_b : alu_operand_a; + hyperbus_pkg::HyperAtomicUnsignedMin: alu_result = alu_unsigned_less ? + alu_operand_a : alu_operand_b; default:; endcase - - write_data = read_data_q & ~(value_mask << (byte_offset * 8)); - write_data |= (result_value & value_mask) << (byte_offset * 8); end end + assign write_mask = value_mask << (byte_offset * 8); + assign preserved_data = read_data_q & ~write_mask; + assign shifted_alu_result = (alu_result & value_mask) << (byte_offset * 8); + assign write_data = alu_enable ? preserved_data | shifted_alu_result : read_data_q; + + //////////////////// + // Stream outputs // + //////////////////// always_comb begin : proc_outputs host_w_ready_o = state_q == WaitWriteData; + host_r_o = '0; host_r_o.data = read_data_q; host_r_o.resp = response_q; host_r_o.last = 1'b1; host_r_o.atomic_ok = response_q == hyperbus_pkg::HyperRespOkay; host_r_valid_o = (state_q == ReturnResponse) && read_pending_q; + host_wrsp_o = '0; host_wrsp_o.resp = response_q; host_wrsp_o.atomic_ok = response_q == hyperbus_pkg::HyperRespOkay; host_wrsp_valid_o = (state_q == ReturnResponse) && write_pending_q; + read_ready_o = state_q == ReadData; + write_o = '0; write_o.data = write_data; write_o.last = 1'b1; @@ -198,9 +256,15 @@ module hyperbus_atomic_handler #( write_o.strb[i] = (i >= byte_offset) && (i < byte_offset + operand_bytes); end write_valid_o = state_q == WriteData; + write_rsp_ready_o = state_q == WriteResponse; end + /////////////////////////// + // Control state machine // + /////////////////////////// + // A valid request consumes one operand, reads the old value, writes the ALU result, and + // returns both responses. Invalid requests drain their operands; backend errors return early. always_comb begin : proc_state state_d = state_q; request_d = request_q; @@ -211,63 +275,89 @@ module hyperbus_atomic_handler #( write_pending_d = write_pending_q; unique case (state_q) + // Capture one validated or rejected atomic request. Idle: begin - if (start_i) begin - request_d.addr = request_i.addr; - request_d.size = request_i.size; - request_d.atomic_op = request_i.atomic_op; - request_d.atomic_return = request_i.atomic_return; - response_d = request_valid_i ? hyperbus_pkg::HyperRespOkay : - hyperbus_pkg::HyperRespAtomicError; - state_d = WaitWriteData; + if (atomic_started) begin + request_d = request_in; + response_d = initial_response; + state_d = WaitWriteData; end end + // Buffer the operand, or drain an invalid request through its final beat. WaitWriteData: begin - if (host_w_valid_i && host_w_ready_o) begin + if (operand_accepted) begin operand_d = host_w_i.data; - if (response_q == hyperbus_pkg::HyperRespOkay) begin - state_d = ReadCommand; - end else if (host_w_i.last) begin - read_pending_d = request_q.atomic_return; - write_pending_d = 1'b1; - state_d = ReturnResponse; - end + end + if (valid_operand_accepted) begin + state_d = ReadCommand; + end + if (invalid_operand_completed) begin + read_pending_d = request_q.atomic_return; + write_pending_d = 1'b1; + state_d = ReturnResponse; end end - ReadCommand: if (command_valid_o && command_ready_i) state_d = ReadData; + // Issue the backend read for the target word. + ReadCommand: begin + if (command_accepted) begin + state_d = ReadData; + end + end + // Capture the old value and stop early on a backend read error. ReadData: begin - if (read_valid_i && read_ready_o) begin + if (backend_read_accepted) begin read_data_d = read_i.data; - if (read_i.resp == hyperbus_pkg::HyperRespOkay) begin - state_d = WriteCommand; - end else begin - response_d = read_i.resp; - read_pending_d = request_q.atomic_return; - write_pending_d = 1'b1; - state_d = ReturnResponse; - end + end + if (backend_read_succeeded) begin + state_d = WriteCommand; + end + if (backend_read_failed) begin + response_d = read_i.resp; + read_pending_d = request_q.atomic_return; + write_pending_d = 1'b1; + state_d = ReturnResponse; + end + end + // Issue the backend write for the updated word. + WriteCommand: begin + if (command_accepted) begin + state_d = WriteData; end end - WriteCommand: if (command_valid_o && command_ready_i) state_d = WriteData; - WriteData: if (write_valid_o && write_ready_i) state_d = WriteResponse; + // Transfer the ALU result and byte strobes. + WriteData: begin + if (backend_write_accepted) begin + state_d = WriteResponse; + end + end + // Collect the backend write status. WriteResponse: begin - if (write_rsp_valid_i && write_rsp_ready_o) begin - response_d = write_rsp_error_i ? hyperbus_pkg::HyperRespAccessError : - hyperbus_pkg::HyperRespOkay; + if (backend_write_rsp_accepted) begin + response_d = backend_write_response; read_pending_d = request_q.atomic_return; write_pending_d = 1'b1; - state_d = ReturnResponse; + state_d = ReturnResponse; end end + // Hold the host read and write responses until independently accepted. ReturnResponse: begin - if (host_r_valid_o && host_r_ready_i) read_pending_d = 1'b0; - if (host_wrsp_valid_o && host_wrsp_ready_i) write_pending_d = 1'b0; - if (completed_o) state_d = Idle; + if (host_read_accepted) begin + read_pending_d = 1'b0; + end + if (host_write_rsp_accepted) begin + write_pending_d = 1'b0; + end + if (completed_o) begin + state_d = Idle; + end end default: state_d = Idle; endcase end + ///////////////////// + // State registers // + ///////////////////// `FFARN(state_q, state_d, Idle, clk_i, rst_ni) `FFARN(request_q, request_d, '0, clk_i, rst_ni) `FFARN(operand_q, operand_d, '0, clk_i, rst_ni) diff --git a/src/hyperbus_axi.sv b/src/hyperbus_axi_frontend.sv similarity index 60% rename from src/hyperbus_axi.sv rename to src/hyperbus_axi_frontend.sv index 74282b5..1fcff28 100644 --- a/src/hyperbus_axi.sv +++ b/src/hyperbus_axi_frontend.sv @@ -29,8 +29,6 @@ module hyperbus_axi_frontend #( input host_rsp_t host_rsp_i ); - localparam int unsigned AxiDataBytes = AxiDataWidth / 8; - `ASSERT_INIT(AxiAddrWidthValid, AxiAddrWidth >= 1) `ASSERT_INIT(AxiDataWidthValid, AxiDataWidth >= 16 && AxiDataWidth <= 1024 && @@ -39,13 +37,6 @@ module hyperbus_axi_frontend #( `ASSERT_INIT(AxiUserWidthValid, AxiUserWidth >= 1) typedef logic [AxiAddrWidth-1:0] axi_addr_t; - typedef logic [AxiDataWidth-1:0] axi_data_t; - typedef logic [AxiIdWidth-1:0] axi_id_t; - typedef logic [AxiDataBytes-1:0] axi_strb_t; - typedef logic [AxiUserWidth-1:0] axi_user_t; - - `AXI_TYPEDEF_ALL_CT(axi_fifo, axi_fifo_req, axi_fifo_rsp, axi_addr_t, axi_id_t, - axi_data_t, axi_strb_t, axi_user_t) // IDs stay in the AXI serializer; the neutral stream is single-outstanding. typedef struct packed { @@ -59,39 +50,30 @@ module hyperbus_axi_frontend #( typedef struct packed { axi_ax_t ax_data; logic write; - } ax_channel_spill_t; + } ax_channel_t; - typedef struct packed { - axi_strb_t strb; - axi_data_t data; - axi_user_t user; - logic last; - } axi_w_chan_t; - - axi_req_t fifo_in_req; - axi_rsp_t fifo_in_rsp; - axi_req_t fifo_out_req; - axi_rsp_t fifo_out_rsp; + /////////////////////////// + // Serialized AXI stream // + /////////////////////////// + + axi_req_t ser_in_req; + axi_rsp_t ser_in_rsp; axi_req_t ser_out_req; axi_rsp_t ser_out_rsp; - axi_ax_t ser_out_req_aw; - axi_ax_t ser_out_req_ar; - axi_ax_t rr_out_req_ax; - axi_ax_t spill_rr_out_req_ax; + axi_ax_t ser_out_req_aw; + axi_ax_t ser_out_req_ar; + ax_channel_t ser_out_req_aw_channel; + ax_channel_t ser_out_req_ar_channel; + ax_channel_t arbitrated_ax; + ax_channel_t selected_ax; - ax_channel_spill_t spill_ax_channel_in; - ax_channel_spill_t spill_ax_channel_out; + logic ax_arb_valid; + logic ax_arb_ready; - axi_w_chan_t w_data_fifo; - axi_w_chan_t w_data_fifo_in; - - logic spill_ax_valid; - logic spill_ax_ready; - logic spill_rr_out_req_write; - logic rr_out_req_write; - logic w_data_valid; - logic w_data_ready; + ////////////////////// + // Drain accounting // + ////////////////////// localparam int unsigned PendingWidth = 8; typedef logic [PendingWidth-1:0] pending_cnt_t; @@ -116,15 +98,15 @@ module hyperbus_axi_frontend #( allow_w = w_partial_q || (write_balance_q > 0); end - fifo_in_req = axi_req_i; - fifo_in_req.ar_valid = axi_req_i.ar_valid && !drain_i; - fifo_in_req.aw_valid = axi_req_i.aw_valid && allow_aw; - fifo_in_req.w_valid = axi_req_i.w_valid && allow_w; + ser_in_req = axi_req_i; + ser_in_req.ar_valid = axi_req_i.ar_valid && !drain_i; + ser_in_req.aw_valid = axi_req_i.aw_valid && allow_aw; + ser_in_req.w_valid = axi_req_i.w_valid && allow_w; - axi_rsp_o = fifo_in_rsp; - axi_rsp_o.ar_ready = fifo_in_rsp.ar_ready && !drain_i; - axi_rsp_o.aw_ready = fifo_in_rsp.aw_ready && allow_aw; - axi_rsp_o.w_ready = fifo_in_rsp.w_ready && allow_w; + axi_rsp_o = ser_in_rsp; + axi_rsp_o.ar_ready = ser_in_rsp.ar_ready && !drain_i; + axi_rsp_o.aw_ready = ser_in_rsp.aw_ready && allow_aw; + axi_rsp_o.w_ready = ser_in_rsp.w_ready && allow_w; end assign axi_ar_accepted = axi_req_i.ar_valid && axi_rsp_o.ar_ready; @@ -170,37 +152,21 @@ module hyperbus_axi_frontend #( assign idle_o = (read_pending_q == '0) && (write_pending_q == '0) && (write_balance_q == '0) && !w_partial_q; - axi_fifo #( - .Depth ( 8 ), - .FallThrough ( 1'b0 ), - .aw_chan_t ( axi_fifo_aw_chan_t ), - .w_chan_t ( axi_fifo_w_chan_t ), - .b_chan_t ( axi_fifo_b_chan_t ), - .ar_chan_t ( axi_fifo_ar_chan_t ), - .r_chan_t ( axi_fifo_r_chan_t ), - .axi_req_t ( axi_req_t ), - .axi_resp_t ( axi_rsp_t ) - ) i_axi_fifo ( - .clk_i, - .rst_ni, - .test_i ( 1'b0 ), - .slv_req_i ( fifo_in_req ), - .slv_resp_o ( fifo_in_rsp ), - .mst_req_o ( fifo_out_req ), - .mst_resp_i ( fifo_out_rsp ) - ); + ///////////////////////// + // Command arbitration // + ///////////////////////// axi_serializer #( - .MaxReadTxns ( 1 ), - .MaxWriteTxns ( 1 ), + .MaxReadTxns ( 4 ), + .MaxWriteTxns ( 4 ), .AxiIdWidth ( AxiIdWidth ), .axi_req_t ( axi_req_t ), .axi_resp_t ( axi_rsp_t ) ) i_axi_serializer ( .clk_i, .rst_ni, - .slv_req_i ( fifo_out_req ), - .slv_resp_o ( fifo_out_rsp ), + .slv_req_i ( ser_in_req ), + .slv_resp_o ( ser_in_rsp ), .mst_req_o ( ser_out_req ), .mst_resp_i ( ser_out_rsp ) ); @@ -217,65 +183,80 @@ module hyperbus_axi_frontend #( assign ser_out_req_aw.size = ser_out_req.aw.size; assign ser_out_req_aw.atop = ser_out_req.aw.atop; + assign ser_out_req_ar_channel = '{ax_data: ser_out_req_ar, write: 1'b0}; + assign ser_out_req_aw_channel = '{ax_data: ser_out_req_aw, write: 1'b1}; + rr_arb_tree #( - .NumIn ( 2 ), - .DataType ( axi_ax_t ), - .AxiVldRdy ( 1 ), - .ExtPrio ( 1'b1 ) + .NumIn ( 2 ), + .DataType ( ax_channel_t ), + .AxiVldRdy ( 1'b1 ) ) i_rr_arb_tree_ax ( .clk_i, .rst_ni, - .flush_i ( 1'b0 ), - .rr_i ( '0 ), + .flush_i ( 1'b0 ), + .rr_i ( '0 ), .req_i ( {ser_out_req.aw_valid, ser_out_req.ar_valid} ), .gnt_o ( {ser_out_rsp.aw_ready, ser_out_rsp.ar_ready} ), - .data_i ( {ser_out_req_aw, ser_out_req_ar} ), - .req_o ( spill_ax_valid ), - .gnt_i ( spill_ax_ready ), - .data_o ( spill_rr_out_req_ax ), - .idx_o ( spill_rr_out_req_write ) + .data_i ( {ser_out_req_aw_channel, ser_out_req_ar_channel} ), + .req_o ( ax_arb_valid ), + .gnt_i ( ax_arb_ready ), + .data_o ( arbitrated_ax ), + .idx_o ( ) ); - assign spill_ax_channel_in.ax_data = spill_rr_out_req_ax; - assign spill_ax_channel_in.write = spill_rr_out_req_write; - - spill_register #( - .T ( ax_channel_spill_t ) - ) i_ax_spill_register ( + stream_register #( + .T ( ax_channel_t ) + ) i_ax_register ( .clk_i, .rst_ni, - .valid_i ( spill_ax_valid ), - .ready_o ( spill_ax_ready ), - .data_i ( spill_ax_channel_in ), - .valid_o ( host_req_o.cmd_valid ), - .ready_i ( host_rsp_i.cmd_ready ), - .data_o ( spill_ax_channel_out ) + .clr_i ( 1'b0 ), + .testmode_i ( 1'b0 ), + .valid_i ( ax_arb_valid ), + .ready_o ( ax_arb_ready ), + .data_i ( arbitrated_ax ), + .valid_o ( host_req_o.cmd_valid ), + .ready_i ( host_rsp_i.cmd_ready ), + .data_o ( selected_ax ) ); - assign rr_out_req_ax = spill_ax_channel_out.ax_data; - assign rr_out_req_write = spill_ax_channel_out.write; + ////////////////////////// + // Host command mapping // + ////////////////////////// - assign host_req_o.cmd.write = rr_out_req_write; - assign host_req_o.cmd.addr = rr_out_req_ax.addr; - assign host_req_o.cmd.beats = hyperbus_pkg::hyper_blen_t'(rr_out_req_ax.len) + + assign host_req_o.cmd.write = selected_ax.write; + assign host_req_o.cmd.addr = selected_ax.ax_data.addr; + assign host_req_o.cmd.beats = hyperbus_pkg::hyper_blen_t'(selected_ax.ax_data.len) + hyperbus_pkg::hyper_blen_t'(1); - assign host_req_o.cmd.size = rr_out_req_ax.size; - assign host_req_o.cmd.burst = (rr_out_req_ax.burst == axi_pkg::BURST_FIXED) ? + assign host_req_o.cmd.size = selected_ax.ax_data.size; + assign host_req_o.cmd.burst = (selected_ax.ax_data.burst == axi_pkg::BURST_FIXED) ? hyperbus_pkg::HyperBurstFixed : hyperbus_pkg::HyperBurstIncr; + + logic atomic_arithmetic_class; + logic atomic_endian_sensitive; + logic atomic_big_endian_unsupported; + + assign atomic_arithmetic_class = + (selected_ax.ax_data.atop[5:4] == axi_pkg::ATOP_ATOMICSTORE) || + (selected_ax.ax_data.atop[5:4] == axi_pkg::ATOP_ATOMICLOAD); + assign atomic_endian_sensitive = + (selected_ax.ax_data.atop[2:0] == axi_pkg::ATOP_ADD) || + (selected_ax.ax_data.atop[2:0] >= axi_pkg::ATOP_SMAX); + assign atomic_big_endian_unsupported = + selected_ax.ax_data.atop[3] && atomic_endian_sensitive; + always_comb begin : proc_atomic_decode - host_req_o.cmd.atomic_op = (rr_out_req_ax.atop == '0) ? + host_req_o.cmd.atomic_op = (selected_ax.ax_data.atop == '0) ? hyperbus_pkg::HyperAtomicNone : hyperbus_pkg::HyperAtomicInvalid; - unique case (rr_out_req_ax.atop) + unique case (selected_ax.ax_data.atop) axi_pkg::ATOP_ATOMICSWAP: host_req_o.cmd.atomic_op = hyperbus_pkg::HyperAtomicSwap; axi_pkg::ATOP_ATOMICCMP: host_req_o.cmd.atomic_op = hyperbus_pkg::HyperAtomicCompare; default: begin - if ((rr_out_req_ax.atop[5:4] == axi_pkg::ATOP_ATOMICSTORE) || - (rr_out_req_ax.atop[5:4] == axi_pkg::ATOP_ATOMICLOAD)) begin - unique case (rr_out_req_ax.atop[2:0]) + if (atomic_arithmetic_class) begin + unique case (selected_ax.ax_data.atop[2:0]) axi_pkg::ATOP_ADD: host_req_o.cmd.atomic_op = hyperbus_pkg::HyperAtomicAdd; axi_pkg::ATOP_CLR: @@ -295,46 +276,29 @@ module hyperbus_axi_frontend #( default:; endcase // AXI defines bit 3 as endianness for arithmetic atomics. - if (rr_out_req_ax.atop[3] && - ((rr_out_req_ax.atop[2:0] == axi_pkg::ATOP_ADD) || - (rr_out_req_ax.atop[2:0] >= axi_pkg::ATOP_SMAX))) begin + if (atomic_big_endian_unsupported) begin host_req_o.cmd.atomic_op = hyperbus_pkg::HyperAtomicInvalid; end end end endcase - host_req_o.cmd.atomic_return = rr_out_req_ax.atop[axi_pkg::ATOP_R_RESP]; - host_req_o.cmd.ordered = rr_out_req_ax.atop != '0; + host_req_o.cmd.atomic_return = selected_ax.ax_data.atop[axi_pkg::ATOP_R_RESP]; + host_req_o.cmd.ordered = selected_ax.ax_data.atop != '0; end - assign w_data_fifo_in.data = ser_out_req.w.data; - assign w_data_fifo_in.strb = ser_out_req.w.strb; - assign w_data_fifo_in.last = ser_out_req.w.last; - assign w_data_fifo_in.user = ser_out_req.w.user; + /////////////////////// + // Host write stream // + /////////////////////// - stream_fifo #( - .FALL_THROUGH ( 1'b0 ), - .T ( axi_w_chan_t ), - .DEPTH ( 16 ) - ) i_wchan_stream_fifo ( - .clk_i, - .rst_ni, - .flush_i ( 1'b0 ), - .testmode_i ( 1'b0 ), - .usage_o ( ), - .data_i ( w_data_fifo_in ), - .valid_i ( ser_out_req.w_valid ), - .ready_o ( ser_out_rsp.w_ready ), - .data_o ( w_data_fifo ), - .valid_o ( w_data_valid ), - .ready_i ( w_data_ready ) - ); + assign host_req_o.w.data = ser_out_req.w.data; + assign host_req_o.w.strb = ser_out_req.w.strb; + assign host_req_o.w.last = ser_out_req.w.last; + assign host_req_o.w_valid = ser_out_req.w_valid; + assign ser_out_rsp.w_ready = host_rsp_i.w_ready; - assign host_req_o.w.data = w_data_fifo.data; - assign host_req_o.w.strb = w_data_fifo.strb; - assign host_req_o.w.last = w_data_fifo.last; - assign host_req_o.w_valid = w_data_valid; - assign w_data_ready = host_rsp_i.w_ready; + ////////////////////// + // Response mapping // + ////////////////////// assign ser_out_rsp.r.data = host_rsp_i.r.data; assign ser_out_rsp.r.last = host_rsp_i.r.last; @@ -366,8 +330,9 @@ module hyperbus_axi_frontend #( assign host_req_o.wrsp_ready = ser_out_req.b_ready; `ASSERT(AxiBurstType, (host_req_o.cmd_valid && host_rsp_i.cmd_ready) |-> - ((rr_out_req_ax.burst == axi_pkg::BURST_INCR) || - ((rr_out_req_ax.burst == axi_pkg::BURST_FIXED) && (rr_out_req_ax.len == '0)))) + ((selected_ax.ax_data.burst == axi_pkg::BURST_INCR) || + ((selected_ax.ax_data.burst == axi_pkg::BURST_FIXED) && + (selected_ax.ax_data.len == '0)))) `ASSERT(ReadResponsePending, axi_r_completed |-> (read_pending_q != '0)) `ASSERT(WriteResponsePending, axi_b_accepted |-> (write_pending_q != '0)) diff --git a/src/hyperbus_cfg_frontend.sv b/src/hyperbus_cfg_frontend.sv index 1fc343c..ce7a07e 100644 --- a/src/hyperbus_cfg_frontend.sv +++ b/src/hyperbus_cfg_frontend.sv @@ -6,7 +6,6 @@ `include "common_cells/assertions.svh" module hyperbus_cfg_frontend #( - parameter int unsigned NumChips = -1, parameter int unsigned NumPhys = 2, parameter type reg_req_t = logic, parameter type reg_rsp_t = logic, @@ -35,7 +34,7 @@ module hyperbus_cfg_frontend #( input logic clock_div_apply_done_i, output hyperbus_pkg::frontend_cfg_t frontend_cfg_o, - output host_rule_t [NumChips-1:0] chip_rules_o, + output host_rule_t [hyperbus_pkg::HyperNumChips-1:0] chip_rules_o, input logic decode_error_i ); @@ -50,6 +49,10 @@ module hyperbus_cfg_frontend #( CfgPhyWaitAck } cfg_state_e; + ////////////////////// + // Persistent state // + ////////////////////// + cfg_state_e cfg_state_d; cfg_state_e cfg_state_q; hyperbus_pkg::phy_cfg_t phy_cfg; @@ -63,12 +66,18 @@ module hyperbus_cfg_frontend #( logic cfg_write_pending_d; logic cfg_write_pending_q; - `ASSERT_INIT(NumChipsValid, NumChips >= 1 && NumChips <= 8) `ASSERT_INIT(NumPhysValid, NumPhys == 1 || NumPhys == 2) `ASSERT_INIT(RegDataWidthValid, RegDataWidth == 32) - reg_req_t cfg_reg_req; - reg_rsp_t cfg_reg_rsp; + ////////////////////////// + // Configuration changes // + ////////////////////////// + + logic cfg_write_requested; + logic drain_completed; + logic cfg_write_accepted; + logic clock_apply_accepted; + logic phy_apply_accepted; assign phy_cfg_changed = phy_cfg != cfg_applied_q; assign clock_div_changed = frontend_cfg_o.phy_clock_div != clock_div_applied_q; @@ -78,6 +87,18 @@ module hyperbus_cfg_frontend #( assign cfg_apply_o = phy_cfg; assign clock_div_apply_valid_o = cfg_state_q == CfgClockSend; assign clock_div_apply_o = frontend_cfg_o.phy_clock_div; + assign cfg_write_requested = reg_req_i.valid && reg_req_i.write; + assign drain_completed = host_idle_i && !trans_active_i; + assign cfg_write_accepted = cfg_reg_req.valid && cfg_reg_rsp.ready; + assign clock_apply_accepted = clock_div_apply_valid_o && clock_div_apply_ready_i; + assign phy_apply_accepted = cfg_apply_valid_o && cfg_apply_ready_i; + + //////////////////////////// + // Register access gating // + //////////////////////////// + + reg_req_t cfg_reg_req; + reg_rsp_t cfg_reg_rsp; always_comb begin : proc_cfg_reg_gate cfg_reg_req = reg_req_i; @@ -98,6 +119,12 @@ module hyperbus_cfg_frontend #( end end + ///////////////////////////////// + // Atomic configuration update // + ///////////////////////////////// + + // Drain host traffic before committing a register write or applying changed + // clock and PHY values. Each destination acknowledges before traffic resumes. always_comb begin : proc_cfg_apply cfg_state_d = cfg_state_q; cfg_applied_d = cfg_applied_q; @@ -105,26 +132,30 @@ module hyperbus_cfg_frontend #( cfg_write_pending_d = cfg_write_pending_q; unique case (cfg_state_q) + // Detect pending register or applied-configuration changes. CfgIdle: begin if (cfg_changed) begin cfg_write_pending_d = 1'b0; cfg_state_d = CfgDrain; - end else if (reg_req_i.valid && reg_req_i.write) begin + end else if (cfg_write_requested) begin cfg_write_pending_d = 1'b1; cfg_state_d = CfgDrain; end end + // Stop admission and wait until all accepted traffic has completed. CfgDrain: begin - if (host_idle_i && !trans_active_i) begin + if (drain_completed) begin cfg_state_d = cfg_write_pending_q ? CfgCommit : CfgObserve; end end + // Commit exactly one blocked register write while traffic remains drained. CfgCommit: begin - if (cfg_reg_rsp.ready) begin + if (cfg_write_accepted) begin cfg_write_pending_d = 1'b0; cfg_state_d = CfgObserve; end end + // Re-evaluate generated configuration outputs after the write commits. CfgObserve: begin if (clock_div_changed) begin cfg_state_d = CfgClockSend; @@ -134,32 +165,32 @@ module hyperbus_cfg_frontend #( cfg_state_d = CfgIdle; end end + // Send a changed divider value to the isochronous clock generator. CfgClockSend: begin - if (clock_div_apply_ready_i) begin - if (clock_div_apply_done_i) begin - clock_div_applied_d = frontend_cfg_o.phy_clock_div; - cfg_state_d = phy_cfg_changed ? CfgPhySend : CfgIdle; - end else begin - cfg_state_d = CfgClockWaitAck; - end + if (clock_apply_accepted && clock_div_apply_done_i) begin + clock_div_applied_d = frontend_cfg_o.phy_clock_div; + cfg_state_d = phy_cfg_changed ? CfgPhySend : CfgIdle; + end else if (clock_apply_accepted) begin + cfg_state_d = CfgClockWaitAck; end end + // Hold the barrier until the divider confirms the update. CfgClockWaitAck: begin if (clock_div_apply_done_i) begin clock_div_applied_d = frontend_cfg_o.phy_clock_div; cfg_state_d = phy_cfg_changed ? CfgPhySend : CfgIdle; end end + // Send changed PHY configuration through the selected bridge. CfgPhySend: begin - if (cfg_apply_ready_i) begin - if (cfg_apply_done_i) begin - cfg_applied_d = phy_cfg; - cfg_state_d = CfgIdle; - end else begin - cfg_state_d = CfgPhyWaitAck; - end + if (phy_apply_accepted && cfg_apply_done_i) begin + cfg_applied_d = phy_cfg; + cfg_state_d = CfgIdle; + end else if (phy_apply_accepted) begin + cfg_state_d = CfgPhyWaitAck; end end + // Hold the barrier until the backend confirms the PHY update. CfgPhyWaitAck: begin if (cfg_apply_done_i) begin cfg_applied_d = phy_cfg; @@ -172,13 +203,20 @@ module hyperbus_cfg_frontend #( endcase end + ///////////////////// + // State registers // + ///////////////////// + `FFARN(cfg_state_q, cfg_state_d, CfgIdle, clk_i, rst_ni) `FFARN(cfg_applied_q, cfg_applied_d, '0, clk_i, rst_ni) `FFARN(clock_div_applied_q, clock_div_applied_d, '0, clk_i, rst_ni) `FFARN(cfg_write_pending_q, cfg_write_pending_d, 1'b0, clk_i, rst_ni) + /////////////////// + // Register bank // + /////////////////// + hyperbus_cfg_regs #( - .NumChips ( NumChips ), .NumPhys ( NumPhys ), .RegDataWidth ( RegDataWidth ), .CapabilityFeatures ( CapabilityFeatures ), diff --git a/src/hyperbus_cfg_regs.sv b/src/hyperbus_cfg_regs.sv index 3dde319..a83979b 100644 --- a/src/hyperbus_cfg_regs.sv +++ b/src/hyperbus_cfg_regs.sv @@ -7,7 +7,6 @@ `include "common_cells/assertions.svh" module hyperbus_cfg_regs #( - parameter int unsigned NumChips = -1, parameter int unsigned NumPhys = -1, parameter int unsigned RegDataWidth = -1, parameter int unsigned RegAddrWidth = 32, @@ -26,13 +25,11 @@ module hyperbus_cfg_regs #( output hyperbus_pkg::frontend_cfg_t frontend_cfg_o, output hyperbus_pkg::phy_cfg_t phy_cfg_o, - output addr_rule_t [NumChips-1:0] chip_rules_o, + output addr_rule_t [hyperbus_pkg::HyperNumChips-1:0] chip_rules_o, input logic decode_error_i ); `include "common_cells/registers.svh" - localparam int unsigned NumChipsMax = 8; - `ASSERT_INIT(NumChipsValid, NumChips >= 1 && NumChips <= NumChipsMax) `ASSERT_INIT(NumPhysValid, NumPhys == 1 || NumPhys == 2) `ASSERT_INIT(RegAddrWidthValid, RegAddrWidth >= 12) `ASSERT_INIT(RegDataWidthValid, RegDataWidth == 32) @@ -73,7 +70,7 @@ module hyperbus_cfg_regs #( hyperbus_cfg_regblock_pkg::hyperbus_cfg_regs__out_t cfg_hwif_out; hyperbus_cfg_regblock_pkg::hyperbus_cfg_regs__in_t cfg_hwif_in; - addr_rule_t [NumChipsMax-1:0] chip_rules_all; + addr_rule_t [hyperbus_pkg::HyperNumChips-1:0] chip_rules_all; cfg_addr_t cfg_addr; logic cfg_addr_in_window; @@ -187,7 +184,7 @@ module hyperbus_cfg_regs #( always_comb begin : proc_chip_rules chip_rules_all = '0; - for (int unsigned i = 0; i < NumChipsMax; i++) begin + for (int unsigned i = 0; i < hyperbus_pkg::HyperNumChips; i++) begin chip_rules_all[i].idx = unsigned'(i); end chip_rules_all[0].start_addr = {cfg_hwif_out.chip_0.range_base.value.value, 22'b0}; @@ -208,7 +205,7 @@ module hyperbus_cfg_regs #( chip_rules_all[7].end_addr = {cfg_hwif_out.chip_7.range_bound.value.value, 22'b0}; end - assign cfg_hwif_in.global_cfg.capability.num_chips.next = 8'(NumChips); + assign cfg_hwif_in.global_cfg.capability.num_chips.next = 8'(hyperbus_pkg::HyperNumChips); assign cfg_hwif_in.global_cfg.capability.num_phys.next = 8'(NumPhys); assign cfg_hwif_in.global_cfg.capability.per_chip_cfg.next = CapabilityFeatures[0]; assign cfg_hwif_in.global_cfg.capability.per_phy_cfg.next = CapabilityFeatures[1]; @@ -303,13 +300,6 @@ module hyperbus_cfg_regs #( unused_cfg_fields |= ^cfg_hwif_out.chip_7.latency_cfg.rwds_sample_delay.value; end - for (genvar i = 0; unsigned'(i) < NumChipsMax; i++) begin : gen_chip_rules - if (i < NumChips) begin : gen_active - assign chip_rules_o[i] = chip_rules_all[i]; - end else begin : gen_inactive - logic unused_chip_rule; - assign unused_chip_rule = ^chip_rules_all[i]; - end - end + assign chip_rules_o = chip_rules_all; endmodule : hyperbus_cfg_regs diff --git a/src/hyperbus_iso_bridge.sv b/src/hyperbus_iso_bridge.sv index 91db8fc..c751863 100644 --- a/src/hyperbus_iso_bridge.sv +++ b/src/hyperbus_iso_bridge.sv @@ -5,8 +5,6 @@ `include "common_cells/registers.svh" module hyperbus_iso_bridge #( - parameter int unsigned RxFifoLogDepth = 8, - parameter int unsigned TxFifoLogDepth = 3, parameter type hyper_rx_t = logic, parameter type hyper_tx_t = logic, parameter type hyper_wrsp_t = logic, @@ -34,32 +32,18 @@ module hyperbus_iso_bridge #( input logic cfg_apply_ready_i ); - hyperbus_pkg::phy_cfg_t cfg_apply_data_q; - logic cfg_apply_accepted; - logic cfg_apply_pending_d; - logic cfg_apply_pending_q; - hyper_cmd_t cmd_data_q; - logic cmd_accepted; - hyper_wrsp_t wrsp_data_q; - logic wrsp_accepted; - hyper_tx_t tx_fifo_data; - logic tx_fifo_valid; - logic tx_fifo_ready; - hyper_rx_t rx_src_fifo_data; - logic rx_src_fifo_valid; - logic rx_src_fifo_ready; - logic rx_iso_valid; - logic rx_iso_ready; - hyper_rx_t rx_iso_data; - hyper_rx_t rx_fifo_data; - logic rx_fifo_valid; - logic rx_fifo_ready; + //////////////////////////// + // Configuration crossing // + //////////////////////////// + + hyperbus_pkg::phy_cfg_t cfg_apply_data_q; + logic cfg_apply_accepted; + logic cfg_apply_pending_d; + logic cfg_apply_pending_q; assign cfg_apply_accepted = frontend_cfg_apply_valid_i && frontend_cfg_apply_ready_o; assign frontend_cfg_apply_done_o = cfg_apply_pending_q && frontend_cfg_apply_ready_o; - assign cmd_accepted = frontend_req_i.cmd_valid && frontend_rsp_o.cmd_ready; - assign wrsp_accepted = backend_rsp_i.wrsp_valid && backend_req_o.wrsp_ready; always_comb begin : proc_cfg_apply_pending cfg_apply_pending_d = cfg_apply_pending_q; @@ -73,8 +57,6 @@ module hyperbus_iso_bridge #( `FFLARN(cfg_apply_data_q, cfg_apply_i, cfg_apply_accepted, '0, clk_sys_i, rst_sys_ni) `FFARN(cfg_apply_pending_q, cfg_apply_pending_d, 1'b0, clk_sys_i, rst_sys_ni) - `FFLARN(cmd_data_q, frontend_req_i.cmd, cmd_accepted, '0, clk_sys_i, rst_sys_ni) - `FFLARN(wrsp_data_q, backend_rsp_i.wrsp, wrsp_accepted, '0, clk_phy_i, rst_phy_ni) isochronous_4phase_handshake i_iso_cfg_apply ( .src_clk_i ( clk_sys_i ), @@ -89,6 +71,16 @@ module hyperbus_iso_bridge #( assign cfg_apply_o = cfg_apply_data_q; + ////////////////////// + // Command crossing // + ////////////////////// + + hyper_cmd_t cmd_data_q; + logic cmd_accepted; + + assign cmd_accepted = frontend_req_i.cmd_valid && frontend_rsp_o.cmd_ready; + `FFLARN(cmd_data_q, frontend_req_i.cmd, cmd_accepted, '0, clk_sys_i, rst_sys_ni) + isochronous_4phase_handshake i_iso_cmd ( .src_clk_i ( clk_sys_i ), .src_rst_ni ( rst_sys_ni ), @@ -102,6 +94,16 @@ module hyperbus_iso_bridge #( assign backend_req_o.cmd = cmd_data_q; + ///////////////////////////// + // Write-response crossing // + ///////////////////////////// + + hyper_wrsp_t wrsp_data_q; + logic wrsp_accepted; + + assign wrsp_accepted = backend_rsp_i.wrsp_valid && backend_req_o.wrsp_ready; + `FFLARN(wrsp_data_q, backend_rsp_i.wrsp, wrsp_accepted, '0, clk_phy_i, rst_phy_ni) + isochronous_4phase_handshake i_iso_wrsp ( .src_clk_i ( clk_phy_i ), .src_rst_ni ( rst_phy_ni ), @@ -115,32 +117,18 @@ module hyperbus_iso_bridge #( assign frontend_rsp_o.wrsp = wrsp_data_q; - stream_fifo #( - .FALL_THROUGH ( 1'b0 ), - .DEPTH ( 1 << TxFifoLogDepth ), - .T ( hyper_tx_t ) - ) i_tx_fifo ( - .clk_i ( clk_sys_i ), - .rst_ni ( rst_sys_ni ), - .flush_i ( 1'b0 ), - .testmode_i ( 1'b0 ), - .usage_o ( ), - .data_i ( frontend_req_i.tx ), - .valid_i ( frontend_req_i.tx_valid ), - .ready_o ( frontend_rsp_o.tx_ready ), - .data_o ( tx_fifo_data ), - .valid_o ( tx_fifo_valid ), - .ready_i ( tx_fifo_ready ) - ); + /////////////////////// + // TX data crossing // + /////////////////////// isochronous_spill_register #( .T ( hyper_tx_t ) ) i_iso_tx ( .src_clk_i ( clk_sys_i ), .src_rst_ni ( rst_sys_ni ), - .src_valid_i ( tx_fifo_valid ), - .src_ready_o ( tx_fifo_ready ), - .src_data_i ( tx_fifo_data ), + .src_valid_i ( frontend_req_i.tx_valid ), + .src_ready_o ( frontend_rsp_o.tx_ready ), + .src_data_i ( frontend_req_i.tx ), .dst_clk_i ( clk_phy_i ), .dst_rst_ni ( rst_phy_ni ), .dst_valid_o ( backend_req_o.tx_valid ), @@ -148,59 +136,23 @@ module hyperbus_iso_bridge #( .dst_data_o ( backend_req_o.tx ) ); - stream_fifo #( - .FALL_THROUGH ( 1'b0 ), - .DEPTH ( 1 << RxFifoLogDepth ), - .T ( hyper_rx_t ) - ) i_rx_src_fifo ( - .clk_i ( clk_phy_i ), - .rst_ni ( rst_phy_ni ), - .flush_i ( 1'b0 ), - .testmode_i ( 1'b0 ), - .usage_o ( ), - .data_i ( backend_rsp_i.rx ), - .valid_i ( backend_rsp_i.rx_valid ), - .ready_o ( backend_req_o.rx_ready ), - .data_o ( rx_src_fifo_data ), - .valid_o ( rx_src_fifo_valid ), - .ready_i ( rx_src_fifo_ready ) - ); + /////////////////////// + // RX data crossing // + /////////////////////// isochronous_spill_register #( .T ( hyper_rx_t ) ) i_iso_rx ( .src_clk_i ( clk_phy_i ), .src_rst_ni ( rst_phy_ni ), - .src_valid_i ( rx_src_fifo_valid ), - .src_ready_o ( rx_src_fifo_ready ), - .src_data_i ( rx_src_fifo_data ), + .src_valid_i ( backend_rsp_i.rx_valid ), + .src_ready_o ( backend_req_o.rx_ready ), + .src_data_i ( backend_rsp_i.rx ), .dst_clk_i ( clk_sys_i ), .dst_rst_ni ( rst_sys_ni ), - .dst_valid_o ( rx_iso_valid ), - .dst_ready_i ( rx_iso_ready ), - .dst_data_o ( rx_iso_data ) - ); - - stream_fifo #( - .FALL_THROUGH ( 1'b0 ), - .DEPTH ( 1 << RxFifoLogDepth ), - .T ( hyper_rx_t ) - ) i_rx_fifo ( - .clk_i ( clk_sys_i ), - .rst_ni ( rst_sys_ni ), - .flush_i ( 1'b0 ), - .testmode_i ( 1'b0 ), - .usage_o ( ), - .data_i ( rx_iso_data ), - .valid_i ( rx_iso_valid ), - .ready_o ( rx_iso_ready ), - .data_o ( rx_fifo_data ), - .valid_o ( rx_fifo_valid ), - .ready_i ( rx_fifo_ready ) + .dst_valid_o ( frontend_rsp_o.rx_valid ), + .dst_ready_i ( frontend_req_i.rx_ready ), + .dst_data_o ( frontend_rsp_o.rx ) ); - assign frontend_rsp_o.rx = rx_fifo_data; - assign frontend_rsp_o.rx_valid = rx_fifo_valid; - assign rx_fifo_ready = frontend_req_i.rx_ready; - endmodule diff --git a/src/hyperbus_isochronous.sv b/src/hyperbus_isochronous.sv index fa4c7cc..050daaa 100644 --- a/src/hyperbus_isochronous.sv +++ b/src/hyperbus_isochronous.sv @@ -7,22 +7,21 @@ `include "common_cells/registers.svh" module hyperbus_isochronous #( - parameter int unsigned NumChips = -1, - parameter int unsigned NumPhys = 2, - parameter int unsigned AxiAddrWidth = -1, - parameter int unsigned AxiDataWidth = -1, - parameter int unsigned AxiIdWidth = -1, - parameter int unsigned AxiUserWidth = -1, - parameter type axi_req_t = logic, - parameter type axi_rsp_t = logic, - parameter int unsigned RegDataWidth = -1, - parameter type reg_req_t = logic, - parameter type reg_rsp_t = logic, - parameter type axi_rule_t = logic, - parameter int unsigned RxFifoLogDepth = 8, - parameter int unsigned TxFifoLogDepth = 3, - parameter int unsigned PhyStartupCycles = 300 * 200, - parameter int unsigned SyncStages = 2 + parameter int unsigned NumPhys = 2, + parameter int unsigned AxiAddrWidth = -1, + parameter int unsigned AxiDataWidth = -1, + parameter int unsigned AxiIdWidth = -1, + parameter int unsigned AxiUserWidth = -1, + parameter type axi_req_t = logic, + parameter type axi_rsp_t = logic, + parameter int unsigned RegDataWidth = -1, + parameter type reg_req_t = logic, + parameter type reg_rsp_t = logic, + parameter type axi_rule_t = logic, + parameter int unsigned HostCommandDepth = 8, + parameter int unsigned HostWriteBufferBytes = 128, + parameter int unsigned PhyStartupCycles = 300 * 200, + parameter int unsigned SyncStages = 2 ) ( input logic clk_sys_i, input logic rst_sys_ni, @@ -37,7 +36,7 @@ module hyperbus_isochronous #( input reg_req_t reg_req_i, output reg_rsp_t reg_rsp_o, - output logic [NumPhys-1:0][NumChips-1:0] hyper_cs_no, + output logic [NumPhys-1:0][hyperbus_pkg::HyperNumChips-1:0] hyper_cs_no, output logic [NumPhys-1:0] hyper_ck_o, output logic [NumPhys-1:0] hyper_ck_no, output logic [NumPhys-1:0] hyper_rwds_o, @@ -55,10 +54,19 @@ module hyperbus_isochronous #( typedef logic [AxiDataWidth-1:0] host_data_t; typedef logic [AxiDataWidth/8-1:0] host_strb_t; `HYPERBUS_TYPEDEF_HOST_ALL_CT(host, host_addr_t, host_data_t, host_strb_t) - `HYPERBUS_TYPEDEF_LINK_ALL_CT(hyper, NumPhys, NumChips) + `HYPERBUS_TYPEDEF_LINK_ALL_CT(hyper, NumPhys) + + ///////////////////// + // Clock and reset // + ///////////////////// + + logic clk_backend; + logic rst_backend_n; + + //////////////////////// + // Configuration path // + //////////////////////// - logic clk_backend; - logic rst_backend_n; hyperbus_pkg::phy_cfg_t frontend_cfg_apply; logic frontend_cfg_apply_valid; logic frontend_cfg_apply_ready; @@ -74,7 +82,11 @@ module hyperbus_isochronous #( logic backend_cfg_apply_ready; hyperbus_pkg::frontend_cfg_t frontend_cfg; - axi_rule_t [NumChips-1:0] frontend_chip_rules; + axi_rule_t [hyperbus_pkg::HyperNumChips-1:0] frontend_chip_rules; + + //////////////////// + // Dataflow links // + //////////////////// host_req_t host_req; host_rsp_t host_rsp; @@ -85,6 +97,10 @@ module hyperbus_isochronous #( hyper_req_t backend_req; hyper_rsp_t backend_rsp; + ////////////////////////// + // Backend clock divider // + ////////////////////////// + logic clock_div_req_valid; logic clock_div_update_accepted; logic clock_div_update_pending_d; @@ -147,8 +163,11 @@ module hyperbus_isochronous #( .init_no ( ) ); + //////////////////////////// + // Configuration frontend // + //////////////////////////// + hyperbus_cfg_frontend #( - .NumChips ( NumChips ), .NumPhys ( NumPhys ), .reg_req_t ( reg_req_t ), .reg_rsp_t ( reg_rsp_t ), @@ -178,6 +197,10 @@ module hyperbus_isochronous #( .decode_error_i ( midend_decode_error ) ); + ////////////////// + // AXI frontend // + ////////////////// + hyperbus_axi_frontend #( .AxiDataWidth ( AxiDataWidth ), .AxiAddrWidth ( AxiAddrWidth ), @@ -198,23 +221,28 @@ module hyperbus_isochronous #( .host_rsp_i ( host_rsp ) ); + //////////// + // Midend // + //////////// + hyperbus_midend #( - .HostAddrWidth ( AxiAddrWidth ), - .HostDataWidth ( AxiDataWidth ), - .NumChips ( NumChips ), - .NumPhys ( NumPhys ), - .host_cmd_t ( host_cmd_t ), - .host_w_t ( host_w_t ), - .host_r_t ( host_r_t ), - .host_wrsp_t ( host_wrsp_t ), - .host_req_t ( host_req_t ), - .host_rsp_t ( host_rsp_t ), - .hyper_rx_t ( hyper_rx_t ), - .hyper_tx_t ( hyper_tx_t ), - .hyper_cmd_t ( hyper_cmd_t ), - .hyper_req_t ( hyper_req_t ), - .hyper_rsp_t ( hyper_rsp_t ), - .rule_t ( axi_rule_t ) + .HostAddrWidth ( AxiAddrWidth ), + .HostDataWidth ( AxiDataWidth ), + .NumPhys ( NumPhys ), + .HostCommandDepth ( HostCommandDepth ), + .HostWriteBufferBytes ( HostWriteBufferBytes ), + .host_cmd_t ( host_cmd_t ), + .host_w_t ( host_w_t ), + .host_r_t ( host_r_t ), + .host_wrsp_t ( host_wrsp_t ), + .host_req_t ( host_req_t ), + .host_rsp_t ( host_rsp_t ), + .hyper_rx_t ( hyper_rx_t ), + .hyper_tx_t ( hyper_tx_t ), + .hyper_cmd_t ( hyper_cmd_t ), + .hyper_req_t ( hyper_req_t ), + .hyper_rsp_t ( hyper_rsp_t ), + .rule_t ( axi_rule_t ) ) i_midend ( .clk_i ( clk_sys_i ), .rst_ni ( rst_sys_ni ), @@ -228,9 +256,11 @@ module hyperbus_isochronous #( .hyper_link_rsp_i ( midend_rsp ) ); + //////////////////////// + // Isochronous bridge // + //////////////////////// + hyperbus_iso_bridge #( - .RxFifoLogDepth ( RxFifoLogDepth ), - .TxFifoLogDepth ( TxFifoLogDepth ), .hyper_rx_t ( hyper_rx_t ), .hyper_tx_t ( hyper_tx_t ), .hyper_wrsp_t ( hyper_wrsp_t ), @@ -255,8 +285,11 @@ module hyperbus_isochronous #( .cfg_apply_ready_i ( backend_cfg_apply_ready ) ); + ///////////// + // Backend // + ///////////// + hyperbus_backend #( - .NumChips ( NumChips ), .NumPhys ( NumPhys ), .StartupCycles ( PhyStartupCycles ), .SyncStages ( SyncStages ), diff --git a/src/hyperbus_midend.sv b/src/hyperbus_midend.sv index 73f78cd..8e2702f 100644 --- a/src/hyperbus_midend.sv +++ b/src/hyperbus_midend.sv @@ -6,22 +6,23 @@ `include "common_cells/assertions.svh" module hyperbus_midend #( - parameter int unsigned HostAddrWidth = -1, - parameter int unsigned HostDataWidth = -1, - parameter int unsigned NumChips = -1, - parameter int unsigned NumPhys = -1, - parameter type host_cmd_t = logic, - parameter type host_w_t = logic, - parameter type host_r_t = logic, - parameter type host_wrsp_t = logic, - parameter type host_req_t = logic, - parameter type host_rsp_t = logic, - parameter type hyper_rx_t = logic, - parameter type hyper_tx_t = logic, - parameter type hyper_cmd_t = logic, - parameter type hyper_req_t = logic, - parameter type hyper_rsp_t = logic, - parameter type rule_t = logic + parameter int unsigned HostAddrWidth = -1, + parameter int unsigned HostDataWidth = -1, + parameter int unsigned NumPhys = -1, + parameter int unsigned HostCommandDepth = 8, + parameter int unsigned HostWriteBufferBytes = 128, + parameter type host_cmd_t = logic, + parameter type host_w_t = logic, + parameter type host_r_t = logic, + parameter type host_wrsp_t = logic, + parameter type host_req_t = logic, + parameter type host_rsp_t = logic, + parameter type hyper_rx_t = logic, + parameter type hyper_tx_t = logic, + parameter type hyper_cmd_t = logic, + parameter type hyper_req_t = logic, + parameter type hyper_rsp_t = logic, + parameter type rule_t = logic ) ( input logic clk_i, input logic rst_ni, @@ -30,7 +31,7 @@ module hyperbus_midend #( output host_rsp_t host_link_rsp_o, input hyperbus_pkg::frontend_cfg_t frontend_cfg_i, - input rule_t [NumChips-1:0] chip_rules_i, + input rule_t [hyperbus_pkg::HyperNumChips-1:0] chip_rules_i, output logic trans_active_o, output logic decode_error_o, @@ -38,24 +39,44 @@ module hyperbus_midend #( input hyper_rsp_t hyper_link_rsp_i ); - localparam int unsigned HostDataBytes = HostDataWidth / 8; - localparam int unsigned HostBusAddrWidth = $clog2(HostDataBytes); - localparam int unsigned PhyDataWidth = NumPhys * 16; - localparam int unsigned ChipSelWidth = cf_math_pkg::idx_width(NumChips); + localparam int unsigned HostDataBytes = HostDataWidth / 8; + localparam int unsigned HostBusAddrWidth = $clog2(HostDataBytes); + localparam int unsigned PhyDataWidth = NumPhys * 16; + localparam int unsigned WriteFifoDepth = HostWriteBufferBytes / HostDataBytes; + localparam int unsigned ReadFifoDepth = 4; + localparam int unsigned WriteRspFifoDepth = 4; + localparam int unsigned ChipSelWidth = + cf_math_pkg::idx_width(hyperbus_pkg::HyperNumChips); - `ASSERT_INIT(NumChipsValid, NumChips >= 1 && NumChips <= 8) `ASSERT_INIT(NumPhysValid, NumPhys == 1 || NumPhys == 2) `ASSERT_INIT(HostAddrWidthValid, HostAddrWidth >= HostBusAddrWidth) `ASSERT_INIT(HostDataWidthValid, HostDataWidth >= PhyDataWidth && HostDataWidth <= 1024 && (HostDataWidth & (HostDataWidth - 1)) == 0 && (HostDataWidth % PhyDataWidth) == 0) + `ASSERT_INIT(HostCommandDepthValid, HostCommandDepth >= 1) + `ASSERT_INIT(HostWriteBufferSizeValid, + HostWriteBufferBytes >= HostDataBytes && + (HostWriteBufferBytes % HostDataBytes) == 0) typedef logic [HostAddrWidth-1:0] host_addr_t; typedef logic [HostAddrWidth:0] host_ext_addr_t; typedef logic [ChipSelWidth-1:0] chip_sel_idx_t; - // Unpack the aggregate links at the midend boundary. + typedef struct packed { + hyper_cmd_t cmd; + hyperbus_pkg::hyper_host_size_t size; + logic [HostBusAddrWidth-1:0] start_addr; + hyperbus_pkg::hyper_blen_t beats; + logic write; + logic start_adapter; + } command_stage_t; + + ///////////////////////// + // Host stream buffers // + ///////////////////////// + + // Buffer the protocol-neutral host streams at the midend boundary. host_cmd_t host_cmd_i; logic host_req_valid_i; logic host_req_ready_o; @@ -68,32 +89,92 @@ module hyperbus_midend #( host_wrsp_t host_wrsp_o; logic host_wrsp_valid_o; logic host_wrsp_ready_i; - hyper_rx_t rx_i; - logic rx_valid_i; - logic rx_ready_o; - hyper_tx_t tx_o; - logic tx_valid_o; - logic tx_ready_i; - logic wrsp_error_i; - logic wrsp_valid_i; - logic wrsp_ready_o; - hyper_cmd_t cmd_o; - logic cmd_valid_o; - logic cmd_ready_i; - - assign host_cmd_i = host_link_req_i.cmd; - assign host_req_valid_i = host_link_req_i.cmd_valid; - assign host_w_i = host_link_req_i.w; - assign host_w_valid_i = host_link_req_i.w_valid; - assign host_r_ready_i = host_link_req_i.r_ready; - assign host_wrsp_ready_i = host_link_req_i.wrsp_ready; - - assign host_link_rsp_o.cmd_ready = host_req_ready_o; - assign host_link_rsp_o.w_ready = host_w_ready_o; - assign host_link_rsp_o.r = host_r_o; - assign host_link_rsp_o.r_valid = host_r_valid_o; - assign host_link_rsp_o.wrsp = host_wrsp_o; - assign host_link_rsp_o.wrsp_valid = host_wrsp_valid_o; + + stream_fifo #( + .FALL_THROUGH ( 1'b0 ), + .DEPTH ( HostCommandDepth ), + .T ( host_cmd_t ) + ) i_host_cmd_fifo ( + .clk_i, + .rst_ni, + .flush_i ( 1'b0 ), + .testmode_i ( 1'b0 ), + .usage_o ( ), + .data_i ( host_link_req_i.cmd ), + .valid_i ( host_link_req_i.cmd_valid ), + .ready_o ( host_link_rsp_o.cmd_ready ), + .data_o ( host_cmd_i ), + .valid_o ( host_req_valid_i ), + .ready_i ( host_req_ready_o ) + ); + + stream_fifo #( + .FALL_THROUGH ( 1'b0 ), + .DEPTH ( WriteFifoDepth ), + .T ( host_w_t ) + ) i_host_w_fifo ( + .clk_i, + .rst_ni, + .flush_i ( 1'b0 ), + .testmode_i ( 1'b0 ), + .usage_o ( ), + .data_i ( host_link_req_i.w ), + .valid_i ( host_link_req_i.w_valid ), + .ready_o ( host_link_rsp_o.w_ready ), + .data_o ( host_w_i ), + .valid_o ( host_w_valid_i ), + .ready_i ( host_w_ready_o ) + ); + + stream_fifo #( + .FALL_THROUGH ( 1'b0 ), + .DEPTH ( ReadFifoDepth ), + .T ( host_r_t ) + ) i_host_r_fifo ( + .clk_i, + .rst_ni, + .flush_i ( 1'b0 ), + .testmode_i ( 1'b0 ), + .usage_o ( ), + .data_i ( host_r_o ), + .valid_i ( host_r_valid_o ), + .ready_o ( host_r_ready_i ), + .data_o ( host_link_rsp_o.r ), + .valid_o ( host_link_rsp_o.r_valid ), + .ready_i ( host_link_req_i.r_ready ) + ); + + stream_fifo #( + .FALL_THROUGH ( 1'b0 ), + .DEPTH ( WriteRspFifoDepth ), + .T ( host_wrsp_t ) + ) i_host_wrsp_fifo ( + .clk_i, + .rst_ni, + .flush_i ( 1'b0 ), + .testmode_i ( 1'b0 ), + .usage_o ( ), + .data_i ( host_wrsp_o ), + .valid_i ( host_wrsp_valid_o ), + .ready_o ( host_wrsp_ready_i ), + .data_o ( host_link_rsp_o.wrsp ), + .valid_o ( host_link_rsp_o.wrsp_valid ), + .ready_i ( host_link_req_i.wrsp_ready ) + ); + + ////////////////// + // Backend link // + ////////////////// + + hyper_rx_t rx_i; + hyper_tx_t tx_o; + hyper_cmd_t cmd_o; + logic rx_valid_i, rx_ready_o; + logic tx_valid_o, tx_ready_i; + logic wrsp_error_i, wrsp_valid_i, wrsp_ready_o; + logic cmd_valid_o, cmd_ready_i; + command_stage_t command_stage_in, command_stage_out; + logic command_stage_ready, command_stage_valid; assign rx_i = hyper_link_rsp_i.rx; assign rx_valid_i = hyper_link_rsp_i.rx_valid; @@ -106,86 +187,113 @@ module hyperbus_midend #( assign hyper_link_req_o.tx = tx_o; assign hyper_link_req_o.tx_valid = tx_valid_o; assign hyper_link_req_o.wrsp_ready = wrsp_ready_o; - assign hyper_link_req_o.cmd = cmd_o; - assign hyper_link_req_o.cmd_valid = cmd_valid_o; - - // Command decode and invalid-request response tracking. - typedef enum logic [1:0] { - ErrorIdle, - ErrorRead, - ErrorWrite, - ErrorWriteResp - } error_state_e; - - chip_sel_idx_t cmd_chip_sel_idx; - chip_sel_idx_t cmd_end_chip_sel_idx; - logic command_accepted; - logic adapter_started; - logic cmd_dec_valid; - logic cmd_dec_error; - logic cmd_end_dec_valid; - logic req_range_valid; - logic req_decode_error; - logic error_req_accepted; - error_state_e error_state_d, error_state_q; - hyperbus_pkg::hyper_blen_t error_beats_d, error_beats_q; - - host_r_t converted_host_r; - logic converted_host_r_valid; - logic converted_host_r_ready; - logic converted_host_w_ready; - host_w_t converted_host_w; - logic converted_host_w_valid; - - // Address decode and command segmentation. - host_cmd_t cmd_req; - host_addr_t req_phy_first_addr; - host_ext_addr_t req_phy_first_ext; - host_ext_addr_t req_phy_end_addr; - host_ext_addr_t req_last_addr; - host_ext_addr_t req_phy_bytes; - hyperbus_pkg::hyper_blen_t req_phy_burst; - host_ext_addr_t cmd_rule_end_addr; - logic req_addr_overflow; - logic atomic_range_valid; + assign hyper_link_req_o.cmd = command_stage_out.cmd; + assign hyper_link_req_o.cmd_valid = command_stage_valid; + + /////////////////////// + // Request selection // + /////////////////////// + + host_cmd_t cmd_req; + logic normal_req; + logic host_req_accepted; + logic command_accepted; + logic backend_command_accepted; + logic adapter_started; + logic normal_cmd_accepted; + logic normal_cmd_valid; + logic normal_req_ready; + + //////////////////// + // Address decode // + //////////////////// + + chip_sel_idx_t cmd_chip_sel_idx; + chip_sel_idx_t cmd_end_chip_sel_idx; + host_addr_t req_phy_first_addr; + host_addr_t req_phy_last_addr; + host_ext_addr_t req_phy_first_ext; + host_ext_addr_t req_phy_end_addr; + host_ext_addr_t req_last_addr; + host_ext_addr_t req_phy_bytes; + host_ext_addr_t cmd_rule_end_addr; + hyperbus_pkg::hyper_blen_t req_phy_burst; + logic cmd_dec_valid; + logic cmd_end_dec_valid; + logic req_addr_overflow; + logic req_range_valid; + logic req_decode_error; + logic atomic_range_valid; + + ////////////////////////// + // Command segmentation // + ////////////////////////// host_cmd_t segment_req_d, segment_req_q; hyperbus_pkg::hyper_blen_t segment_remaining_d, segment_remaining_q; hyperbus_pkg::hyper_blen_t cmd_remaining; - host_ext_addr_t cmd_rule_capacity; hyperbus_pkg::hyper_blen_t cmd_segment_burst; + host_ext_addr_t cmd_rule_capacity; host_addr_t cmd_next_addr; logic segment_pending_d, segment_pending_q; logic segment_final_d, segment_final_q; logic segment_wrsp_error_d, segment_wrsp_error_q; logic cmd_segment_final; - logic normal_cmd_accepted; logic normal_segment_complete; - // Atomic requests use the same command and data adapters as normal traffic. - logic atomic_req_accepted; - logic atomic_cmd_valid; - logic atomic_active; - logic atomic_completed; - logic normal_req; - logic atomic_request_valid; - host_cmd_t atomic_cmd; - host_w_t atomic_host_w; - logic atomic_host_w_valid; - logic atomic_host_w_ready; - host_r_t atomic_host_r; - logic atomic_host_r_valid; - host_wrsp_t atomic_host_wrsp; - logic atomic_host_wrsp_valid; - logic atomic_read_ready; - logic atomic_wrsp_ready; - - logic adapter_rx_last; - - logic trans_active_d; - logic trans_active_q; - logic trans_active_set; - logic trans_active_reset; - logic host_req_accepted; + logic normal_read_segment_complete; + logic normal_write_segment_complete; + + ////////////////// + // Atomic path // + ////////////////// + + host_cmd_t atomic_cmd; + host_w_t atomic_host_w; + host_r_t atomic_host_r; + host_wrsp_t atomic_host_wrsp; + logic atomic_req_accepted; + logic atomic_request_error; + logic atomic_cmd_valid; + logic atomic_active; + logic atomic_completed; + logic atomic_request_valid; + logic atomic_host_w_valid, atomic_host_w_ready; + logic atomic_host_r_valid; + logic atomic_host_wrsp_valid; + logic atomic_read_ready, atomic_wrsp_ready; + + /////////////////// + // Data adapters // + /////////////////// + + host_r_t converted_host_r; + host_w_t converted_host_w; + logic converted_host_r_valid, converted_host_r_ready; + logic converted_host_w_valid, converted_host_w_ready; + logic adapter_rx_last; + + ///////////////////// + // Error responses // + ///////////////////// + + typedef enum logic [1:0] { + ErrorIdle, + ErrorRead, + ErrorWrite, + ErrorWriteResp + } error_state_e; + + error_state_e error_state_d, error_state_q; + hyperbus_pkg::hyper_blen_t error_beats_d, error_beats_q; + logic error_req_accepted; + + ////////////////////////// + // Transaction lifetime // + ////////////////////////// + + logic trans_active_d, trans_active_q; + logic trans_active_set, trans_active_reset; + logic normal_transaction_completed; assign normal_req = host_cmd_i.atomic_op == hyperbus_pkg::HyperAtomicNone; assign atomic_request_valid = @@ -196,23 +304,23 @@ module hyperbus_midend #( ((host_cmd_i.atomic_op != hyperbus_pkg::HyperAtomicCompare) || (host_cmd_i.size != '0)) && atomic_range_valid; - assign cmd_valid_o = atomic_cmd_valid || - segment_pending_q || - (host_req_valid_i && !trans_active_q && cmd_dec_valid && - req_range_valid && normal_req); - assign host_req_ready_o = !trans_active_q && - (normal_req ? - (req_decode_error || (cmd_dec_valid && cmd_ready_i)) : 1'b1); + assign normal_cmd_valid = host_req_valid_i && !trans_active_q && cmd_dec_valid && + req_range_valid && normal_req; + assign normal_req_ready = req_decode_error || (cmd_dec_valid && command_stage_ready); + assign cmd_valid_o = atomic_cmd_valid || segment_pending_q || normal_cmd_valid; + assign host_req_ready_o = !trans_active_q && (!normal_req || normal_req_ready); assign host_req_accepted = host_req_valid_i && host_req_ready_o; - assign command_accepted = cmd_valid_o && cmd_ready_i; + assign command_accepted = cmd_valid_o && command_stage_ready; + assign backend_command_accepted = command_stage_valid && cmd_ready_i; assign normal_cmd_accepted = command_accepted && !atomic_cmd_valid; - assign adapter_started = atomic_cmd_valid ? command_accepted : - (normal_cmd_accepted && !segment_pending_q); + assign adapter_started = backend_command_accepted && command_stage_out.start_adapter; assign error_req_accepted = host_req_accepted && req_decode_error; assign atomic_req_accepted = host_req_accepted && !normal_req; - assign decode_error_o = error_req_accepted || - (atomic_req_accepted && !atomic_range_valid); - assign trans_active_o = trans_active_q; + assign atomic_request_error = atomic_req_accepted && !atomic_request_valid; + assign decode_error_o = error_req_accepted || atomic_request_error; + assign trans_active_o = trans_active_q || host_req_valid_i || host_w_valid_i || + host_link_rsp_o.r_valid || host_link_rsp_o.wrsp_valid || + command_stage_valid; always_comb begin : proc_cmd_cs cmd_o.cs = '0; @@ -242,42 +350,56 @@ module hyperbus_midend #( req_phy_bytes = req_phy_bytes << NumPhys; req_phy_burst = hyperbus_pkg::hyper_blen_t'(req_phy_bytes >> 1); req_phy_end_addr = req_phy_first_ext + req_phy_bytes; + req_phy_last_addr = host_addr_t'(req_phy_end_addr - 1'b1); req_addr_overflow = (req_last_addr > (host_ext_addr_t'(1) << HostAddrWidth)) || (req_phy_end_addr > (host_ext_addr_t'(1) << HostAddrWidth)); end + addr_decode #( + .NoIndices ( hyperbus_pkg::HyperNumChips ), + .NoRules ( hyperbus_pkg::HyperNumChips ), + .addr_t ( host_addr_t ), + .rule_t ( rule_t ), + .idx_t ( chip_sel_idx_t ) + ) i_start_addr_decode ( + .addr_i ( req_phy_first_addr ), + .addr_map_i ( chip_rules_i ), + .idx_o ( cmd_chip_sel_idx ), + .dec_valid_o ( cmd_dec_valid ), + .dec_error_o ( ), + .en_default_idx_i ( 1'b0 ), + .default_idx_i ( '0 ) + ); + + addr_decode #( + .NoIndices ( hyperbus_pkg::HyperNumChips ), + .NoRules ( hyperbus_pkg::HyperNumChips ), + .addr_t ( host_addr_t ), + .rule_t ( rule_t ), + .idx_t ( chip_sel_idx_t ) + ) i_end_addr_decode ( + .addr_i ( req_phy_last_addr ), + .addr_map_i ( chip_rules_i ), + .idx_o ( cmd_end_chip_sel_idx ), + .dec_valid_o ( cmd_end_dec_valid ), + .dec_error_o ( ), + .en_default_idx_i ( 1'b0 ), + .default_idx_i ( '0 ) + ); + // Software keeps ranges ordered and non-overlapping; transactions may cross contiguous ranges. always_comb begin : proc_req_rule_range - cmd_chip_sel_idx = '0; - cmd_end_chip_sel_idx = '0; - cmd_dec_valid = 1'b0; - cmd_end_dec_valid = 1'b0; - cmd_rule_end_addr = req_phy_first_ext; - - for (int unsigned i = 0; i < NumChips; i++) begin - host_ext_addr_t rule_start; - host_ext_addr_t rule_end; - - rule_start = host_ext_addr_t'(chip_rules_i[i].start_addr); - rule_end = (chip_rules_i[i].end_addr == '0) ? - (host_ext_addr_t'(1) << HostAddrWidth) : - host_ext_addr_t'(chip_rules_i[i].end_addr); - if (!cmd_dec_valid && (req_phy_first_ext >= rule_start) && - (req_phy_first_ext < rule_end)) begin - cmd_chip_sel_idx = chip_sel_idx_t'(i); - cmd_dec_valid = 1'b1; - cmd_rule_end_addr = rule_end; - end - if (!cmd_end_dec_valid && ((req_phy_end_addr - 1'b1) >= rule_start) && - ((req_phy_end_addr - 1'b1) < rule_end)) begin - cmd_end_chip_sel_idx = chip_sel_idx_t'(i); - cmd_end_dec_valid = 1'b1; - end + cmd_rule_end_addr = req_phy_first_ext; + if (cmd_dec_valid) begin + cmd_rule_end_addr = (chip_rules_i[cmd_chip_sel_idx].end_addr == '0) ? + (host_ext_addr_t'(1) << HostAddrWidth) : + host_ext_addr_t'( + chip_rules_i[cmd_chip_sel_idx].end_addr); end req_range_valid = !req_addr_overflow && cmd_dec_valid && cmd_end_dec_valid && (cmd_end_chip_sel_idx >= cmd_chip_sel_idx); - for (int unsigned i = 0; i < NumChips - 1; i++) begin + for (int unsigned i = 0; i < hyperbus_pkg::HyperNumChips - 1; i++) begin if ((i >= cmd_chip_sel_idx) && (i < cmd_end_chip_sel_idx) && (chip_rules_i[i].end_addr != chip_rules_i[i+1].start_addr)) begin req_range_valid = 1'b0; @@ -285,25 +407,32 @@ module hyperbus_midend #( end end - assign cmd_dec_error = !cmd_dec_valid; assign atomic_range_valid = req_range_valid && (cmd_chip_sel_idx == cmd_end_chip_sel_idx); assign req_decode_error = normal_req && - (cmd_dec_error || + (!cmd_dec_valid || (!atomic_active && !segment_pending_q && !req_range_valid)); assign cmd_o.trans.write = cmd_req.write; assign cmd_o.trans.burst_type = 1'b1; // Wrapping HyperBus bursts are not supported. assign cmd_o.trans.address_space = frontend_cfg_i.address_space; - assign cmd_o.trans.address = (NumPhys == 2) ? - (frontend_cfg_i.dual_phy ? - ((req_phy_first_addr & - ((host_addr_t'(1) << frontend_cfg_i.address_mask_msb) - 1)) >> 2) : - (((req_phy_first_addr & - ((host_addr_t'(1) << frontend_cfg_i.address_mask_msb) - 1)) >> 2) << 1)) : - ((req_phy_first_addr & - ((host_addr_t'(1) << frontend_cfg_i.address_mask_msb) - 1)) >> 1); + host_addr_t cmd_phy_address; + host_addr_t masked_req_address; + + always_comb begin : proc_cmd_address + masked_req_address = req_phy_first_addr & + ((host_addr_t'(1) << frontend_cfg_i.address_mask_msb) - 1); + cmd_phy_address = masked_req_address >> 1; + if (NumPhys == 2) begin + cmd_phy_address = masked_req_address >> 2; + if (!frontend_cfg_i.dual_phy) begin + cmd_phy_address = cmd_phy_address << 1; + end + end + end + + assign cmd_o.trans.address = cmd_phy_address; always_comb begin : proc_cmd_segment cmd_remaining = segment_pending_q ? segment_remaining_q : req_phy_burst; @@ -325,6 +454,28 @@ module hyperbus_midend #( cmd_o.trans.burst = cmd_segment_burst; end + assign command_stage_in.cmd = cmd_o; + assign command_stage_in.size = cmd_req.size; + assign command_stage_in.start_addr = cmd_req.addr[HostBusAddrWidth-1:0]; + assign command_stage_in.beats = cmd_req.beats; + assign command_stage_in.write = cmd_req.write; + assign command_stage_in.start_adapter = atomic_cmd_valid || !segment_pending_q; + + stream_register #( + .T ( command_stage_t ) + ) i_command_stage ( + .clk_i, + .rst_ni, + .clr_i ( 1'b0 ), + .testmode_i ( 1'b0 ), + .valid_i ( cmd_valid_o ), + .ready_o ( command_stage_ready ), + .data_i ( command_stage_in ), + .valid_o ( command_stage_valid ), + .ready_i ( cmd_ready_i ), + .data_o ( command_stage_out ) + ); + assign adapter_rx_last = rx_i.last && (atomic_active || segment_final_q); hyperbus_read_adapter #( @@ -335,19 +486,19 @@ module hyperbus_midend #( ) i_read_adapter ( .clk_i, .rst_ni, - .size_i ( cmd_req.size ), - .start_i ( adapter_started && !cmd_req.write ), - .dual_phy_i ( frontend_cfg_i.dual_phy ), - .start_addr_i ( cmd_req.addr[HostBusAddrWidth-1:0] ), - .burst_len_i ( cmd_req.beats ), - .phy_valid_i ( rx_valid_i ), - .phy_ready_o ( rx_ready_o ), - .data_i ( rx_i.data ), - .last_i ( adapter_rx_last ), - .error_i ( rx_i.error ), - .host_valid_o ( converted_host_r_valid ), - .host_ready_i ( converted_host_r_ready ), - .data_o ( converted_host_r ) + .size_i ( command_stage_out.size ), + .start_i ( adapter_started && !command_stage_out.write ), + .dual_phy_i ( frontend_cfg_i.dual_phy ), + .start_addr_i ( command_stage_out.start_addr ), + .burst_len_i ( command_stage_out.beats ), + .phy_valid_i ( rx_valid_i ), + .phy_ready_o ( rx_ready_o ), + .data_i ( rx_i.data ), + .last_i ( adapter_rx_last ), + .error_i ( rx_i.error ), + .host_valid_o ( converted_host_r_valid ), + .host_ready_i ( converted_host_r_ready ), + .data_o ( converted_host_r ) ); hyperbus_write_adapter #( @@ -357,18 +508,18 @@ module hyperbus_midend #( ) i_write_adapter ( .clk_i, .rst_ni, - .size_i ( cmd_req.size ), - .start_i ( adapter_started && cmd_req.write ), - .dual_phy_i ( frontend_cfg_i.dual_phy ), - .start_addr_i ( cmd_req.addr[HostBusAddrWidth-1:0] ), - .data_i ( converted_host_w ), - .host_valid_i ( converted_host_w_valid ), - .host_ready_o ( converted_host_w_ready ), - .data_o ( tx_o.data ), - .last_o ( tx_o.last ), - .strb_o ( tx_o.strb ), - .phy_valid_o ( tx_valid_o ), - .phy_ready_i ( tx_ready_i ) + .size_i ( command_stage_out.size ), + .start_i ( adapter_started && command_stage_out.write ), + .dual_phy_i ( frontend_cfg_i.dual_phy ), + .start_addr_i ( command_stage_out.start_addr ), + .data_i ( converted_host_w ), + .host_valid_i ( converted_host_w_valid ), + .host_ready_o ( converted_host_w_ready ), + .data_o ( tx_o.data ), + .last_o ( tx_o.last ), + .strb_o ( tx_o.strb ), + .phy_valid_o ( tx_valid_o ), + .phy_ready_i ( tx_ready_i ) ); hyperbus_atomic_handler #( @@ -388,7 +539,7 @@ module hyperbus_midend #( .completed_o ( atomic_completed ), .command_o ( atomic_cmd ), .command_valid_o ( atomic_cmd_valid ), - .command_ready_i ( cmd_ready_i ), + .command_ready_i ( command_stage_ready ), .host_w_i ( host_w_i ), .host_w_valid_i ( host_w_valid_i ), .host_w_ready_o ( atomic_host_w_ready ), @@ -409,9 +560,12 @@ module hyperbus_midend #( .write_rsp_ready_o ( atomic_wrsp_ready ) ); + assign normal_read_segment_complete = !segment_req_q.write && + rx_valid_i && rx_ready_o && rx_i.last; + assign normal_write_segment_complete = segment_req_q.write && + wrsp_valid_i && wrsp_ready_o; assign normal_segment_complete = trans_active_q && !atomic_active && - (segment_req_q.write ? (wrsp_valid_i && wrsp_ready_o) : - (rx_valid_i && rx_ready_o && rx_i.last)); + (normal_read_segment_complete || normal_write_segment_complete); always_comb begin : proc_segments segment_req_d = segment_req_q; @@ -449,12 +603,14 @@ module hyperbus_midend #( error_beats_d = error_beats_q; unique case (error_state_q) + // Capture a rejected command and select its response stream. ErrorIdle: begin if (error_req_accepted) begin error_beats_d = host_cmd_i.beats; error_state_d = host_cmd_i.write ? ErrorWrite : ErrorRead; end end + // Return one decode-error beat per requested read beat. ErrorRead: begin if (host_r_valid_o && host_r_ready_i) begin error_beats_d = error_beats_q - 1'b1; @@ -463,11 +619,13 @@ module hyperbus_midend #( end end end + // Drain all write data belonging to a rejected write command. ErrorWrite: begin if (host_w_valid_i && host_w_ready_o && host_w_i.last) begin error_state_d = ErrorWriteResp; end end + // Return the final decode-error write response. ErrorWriteResp: begin if (host_wrsp_valid_o && host_wrsp_ready_i) begin error_state_d = ErrorIdle; @@ -526,9 +684,11 @@ module hyperbus_midend #( assign trans_active_set = (normal_cmd_accepted && !segment_pending_q) || error_req_accepted || atomic_req_accepted; - assign trans_active_reset = atomic_active ? atomic_completed : - ((host_r_valid_o && host_r_ready_i && host_r_o.last) || - (host_wrsp_valid_o && host_wrsp_ready_i)); + assign normal_transaction_completed = + (host_r_valid_o && host_r_ready_i && host_r_o.last) || + (host_wrsp_valid_o && host_wrsp_ready_i); + assign trans_active_reset = (atomic_active && atomic_completed) || + (!atomic_active && normal_transaction_completed); always_comb begin : proc_trans_active trans_active_d = trans_active_q; @@ -553,6 +713,7 @@ module hyperbus_midend #( (host_cmd_i.atomic_op <= hyperbus_pkg::HyperAtomicUnsignedMin)) `ASSERT(HostReqAtomicWrite, (host_req_accepted && (host_cmd_i.atomic_op != hyperbus_pkg::HyperAtomicNone)) |-> host_cmd_i.write) - `ASSERT(BackendBurstNonzero, command_accepted |-> (cmd_o.trans.burst != '0)) + `ASSERT(BackendBurstNonzero, backend_command_accepted |-> + (command_stage_out.cmd.trans.burst != '0)) endmodule diff --git a/src/hyperbus_pkg.sv b/src/hyperbus_pkg.sv index fc85bce..589a57a 100644 --- a/src/hyperbus_pkg.sv +++ b/src/hyperbus_pkg.sv @@ -4,11 +4,18 @@ package hyperbus_pkg; + // The register map and physical interface expose every supported chip select. + localparam int unsigned HyperNumChips = 8; + + //////////////////////////// + // Host-side transaction // + //////////////////////////// + // Maximal burst size: 2^8 1024-bit words as 16-bit words (plus one as not decremented) localparam unsigned HyperBurstWidth = 8 + $clog2(1024/16) + 1; typedef logic [HyperBurstWidth-1:0] hyper_blen_t; - typedef logic [2:0] hyper_host_size_t; + typedef logic [2:0] hyper_host_size_t; typedef enum logic [1:0] { HyperBurstIncr, @@ -21,6 +28,7 @@ package hyperbus_pkg; HyperAtomicSwap, HyperAtomicCompare, HyperAtomicAdd, + HyperAtomicAnd, HyperAtomicClear, HyperAtomicXor, HyperAtomicSet, @@ -37,7 +45,10 @@ package hyperbus_pkg; HyperRespAtomicError } hyper_resp_e; - // configuration type + /////////////////// + // Configuration // + /////////////////// + typedef struct packed { logic [3:0] t_latency_access; logic en_latency_additional; @@ -61,6 +72,10 @@ package hyperbus_pkg; logic dual_phy; } phy_cfg_t; + ////////////////////////// + // Backend transaction // + ////////////////////////// + typedef struct packed { logic write; // transaction is a write hyper_blen_t burst; @@ -70,12 +85,12 @@ package hyperbus_pkg; } hyper_tf_t; typedef struct packed { - logic [15:0] data; - logic last; - logic error; + logic [15:0] data; + logic last; + logic error; } phy_rx_t; - typedef enum logic[3:0] { + typedef enum logic [3:0] { Startup, Idle, DelayCK, diff --git a/src/hyperbus_read_adapter.sv b/src/hyperbus_read_adapter.sv index 658c6e8..3e7aac5 100644 --- a/src/hyperbus_read_adapter.sv +++ b/src/hyperbus_read_adapter.sv @@ -36,25 +36,36 @@ module hyperbus_read_adapter #( localparam int unsigned WordCntWidth = (PhyBeatsPerHost == 1) ? 1 : $clog2(PhyBeatsPerHost); - typedef enum logic [2:0] { + typedef enum logic [1:0] { Idle, - WaitData, - Sample, - CntReady + WaitPhy, + CollectPhy, + EmitHost } read_adapter_state_e; + ////////////////////// + // Persistent state // + ////////////////////// + read_adapter_state_e state_d, state_q; logic [BurstLength-1:0] byte_host_addr_d, byte_host_addr_q; logic [BurstLength-1:0] byte_phy_cnt_d, byte_phy_cnt_q; logic [BurstLength-1:0] last_addr_d, last_addr_q; logic [3:0] size_d, size_q; T data_buffer_d, data_buffer_q; + + ////////////////////// + // Address tracking // + ////////////////////// + logic [WordCntWidth-1:0] word_cnt; logic enough_data; logic enough_data_q; logic sent_available_data; logic [BurstLength-1:0] next_host_addr; logic host_last; + logic host_accepted; + logic converted_accepted; logic [16*NumPhys-1:0] converted_data; logic converted_last; logic converted_error; @@ -68,6 +79,12 @@ module hyperbus_read_adapter #( assign enough_data_q = byte_phy_cnt_q >= next_host_addr; assign sent_available_data = byte_host_addr_d >= byte_phy_cnt_q; assign host_last = data_buffer_q.last && (last_addr_q == next_host_addr); + assign host_accepted = host_valid_o && host_ready_i; + assign converted_accepted = converted_valid && converted_ready; + + ////////////////////// + // Host read output // + ////////////////////// assign data_o.data = data_buffer_q.data; assign data_o.resp = data_buffer_q.resp; @@ -88,26 +105,30 @@ module hyperbus_read_adapter #( last_addr_d = ((start_addr_i >> size_i) << size_i) + (burst_len_i << size_i); size_d = size_i; end - if (host_valid_o && host_ready_i) begin + if (host_accepted) begin byte_host_addr_d = ((byte_host_addr_q >> size_q) << size_q) + (1 << size_q); end - if (converted_valid && converted_ready) begin + if (converted_accepted) begin byte_phy_cnt_d = byte_phy_cnt_q + NumPhys * 2; end end + ///////////////////// + // Beat assembly // + ///////////////////// + always_comb begin : proc_sample data_buffer_d = data_buffer_q; - if (state_d == Idle) begin + if (state_q == Idle) begin data_buffer_d.last = 1'b0; data_buffer_d.data = '0; data_buffer_d.resp = hyperbus_pkg::HyperRespOkay; end else begin - if (host_valid_o && host_ready_i) begin + if (host_accepted) begin data_buffer_d.resp = hyperbus_pkg::HyperRespOkay; end - if (converted_ready && converted_valid) begin + if (converted_accepted) begin data_buffer_d.data[word_cnt*(16*NumPhys) +: (16*NumPhys)] = converted_data; if (converted_error) begin data_buffer_d.resp = hyperbus_pkg::HyperRespAccessError; @@ -117,38 +138,46 @@ module hyperbus_read_adapter #( end end + /////////////////////////// + // Adapter state machine // + /////////////////////////// + always_comb begin : proc_fsm state_d = state_q; host_valid_o = 1'b0; converted_ready = 1'b0; unique case (state_q) + // Wait for a command to initialize address tracking. Idle: begin if (start_i) begin - state_d = WaitData; + state_d = WaitPhy; end end - WaitData: begin + // Accept the first physical word of the host beat. + WaitPhy: begin converted_ready = 1'b1; if (converted_valid) begin - state_d = Sample; + state_d = CollectPhy; end end - Sample: begin + // Accumulate physical words until one complete host beat is available. + CollectPhy: begin converted_ready = 1'b1; if (enough_data) begin - state_d = CntReady; + state_d = EmitHost; end end - CntReady: begin + // Hold the assembled host beat until accepted. + EmitHost: begin host_valid_o = enough_data_q; converted_ready = !enough_data_q; - if (host_valid_o && host_ready_i) begin + if (host_accepted) begin if (data_o.last || (last_addr_q == byte_host_addr_d)) begin state_d = Idle; end else if (sent_available_data) begin converted_ready = 1'b1; - state_d = converted_valid ? Sample : WaitData; + state_d = converted_valid ? CollectPhy : WaitPhy; end end end @@ -158,6 +187,10 @@ module hyperbus_read_adapter #( endcase end + ////////////////////////// + // Physical-width adapter // + ////////////////////////// + if (NumPhys == 2) begin : gen_dual_phy logic [15:0] lower_data_d, lower_data_q; logic lower_error_d, lower_error_q; @@ -207,6 +240,10 @@ module hyperbus_read_adapter #( end end + ///////////////////// + // State registers // + ///////////////////// + `FFARN(state_q, state_d, Idle, clk_i, rst_ni) `FFARN(data_buffer_q, data_buffer_d, '0, clk_i, rst_ni) `FFARN(byte_host_addr_q, byte_host_addr_d, '0, clk_i, rst_ni) diff --git a/src/hyperbus_synchronous.sv b/src/hyperbus_synchronous.sv index f6b48be..bad36a5 100644 --- a/src/hyperbus_synchronous.sv +++ b/src/hyperbus_synchronous.sv @@ -6,20 +6,21 @@ `include "common_cells/assertions.svh" module hyperbus_synchronous #( - parameter int unsigned NumChips = -1, - parameter int unsigned NumPhys = 2, - parameter int unsigned AxiAddrWidth = -1, - parameter int unsigned AxiDataWidth = -1, - parameter int unsigned AxiIdWidth = -1, - parameter int unsigned AxiUserWidth = -1, - parameter type axi_req_t = logic, - parameter type axi_rsp_t = logic, - parameter int unsigned RegDataWidth = -1, - parameter type reg_req_t = logic, - parameter type reg_rsp_t = logic, - parameter type axi_rule_t = logic, - parameter int unsigned PhyStartupCycles = 300 * 200, - parameter int unsigned SyncStages = 2 + parameter int unsigned NumPhys = 2, + parameter int unsigned AxiAddrWidth = -1, + parameter int unsigned AxiDataWidth = -1, + parameter int unsigned AxiIdWidth = -1, + parameter int unsigned AxiUserWidth = -1, + parameter type axi_req_t = logic, + parameter type axi_rsp_t = logic, + parameter int unsigned RegDataWidth = -1, + parameter type reg_req_t = logic, + parameter type reg_rsp_t = logic, + parameter type axi_rule_t = logic, + parameter int unsigned HostCommandDepth = 8, + parameter int unsigned HostWriteBufferBytes = 128, + parameter int unsigned PhyStartupCycles = 300 * 200, + parameter int unsigned SyncStages = 2 ) ( input logic clk_sys_i, input logic rst_sys_ni, @@ -34,7 +35,7 @@ module hyperbus_synchronous #( input reg_req_t reg_req_i, output reg_rsp_t reg_rsp_o, - output logic [NumPhys-1:0][NumChips-1:0] hyper_cs_no, + output logic [NumPhys-1:0][hyperbus_pkg::HyperNumChips-1:0] hyper_cs_no, output logic [NumPhys-1:0] hyper_ck_o, output logic [NumPhys-1:0] hyper_ck_no, output logic [NumPhys-1:0] hyper_rwds_o, @@ -52,10 +53,19 @@ module hyperbus_synchronous #( typedef logic [AxiDataWidth-1:0] host_data_t; typedef logic [AxiDataWidth/8-1:0] host_strb_t; `HYPERBUS_TYPEDEF_HOST_ALL_CT(host, host_addr_t, host_data_t, host_strb_t) - `HYPERBUS_TYPEDEF_LINK_ALL_CT(hyper, NumPhys, NumChips) + `HYPERBUS_TYPEDEF_LINK_ALL_CT(hyper, NumPhys) + + ///////////////////// + // Clock and reset // + ///////////////////// + + logic clk_backend; + logic rst_backend_n; + + //////////////////////// + // Configuration path // + //////////////////////// - logic clk_backend; - logic rst_backend_n; hyperbus_pkg::phy_cfg_t frontend_cfg_apply; logic frontend_cfg_apply_valid; logic frontend_cfg_apply_ready; @@ -68,7 +78,11 @@ module hyperbus_synchronous #( logic backend_cfg_apply_ready; hyperbus_pkg::frontend_cfg_t frontend_cfg; - axi_rule_t [NumChips-1:0] frontend_chip_rules; + axi_rule_t [hyperbus_pkg::HyperNumChips-1:0] frontend_chip_rules; + + //////////////////// + // Dataflow links // + //////////////////// host_req_t host_req; host_rsp_t host_rsp; @@ -90,8 +104,11 @@ module hyperbus_synchronous #( assign backend_req = midend_req; assign midend_rsp = backend_rsp; + //////////////////////////// + // Configuration frontend // + //////////////////////////// + hyperbus_cfg_frontend #( - .NumChips ( NumChips ), .NumPhys ( NumPhys ), .reg_req_t ( reg_req_t ), .reg_rsp_t ( reg_rsp_t ), @@ -118,6 +135,10 @@ module hyperbus_synchronous #( .decode_error_i ( midend_decode_error ) ); + ////////////////// + // AXI frontend // + ////////////////// + hyperbus_axi_frontend #( .AxiDataWidth ( AxiDataWidth ), .AxiAddrWidth ( AxiAddrWidth ), @@ -138,23 +159,28 @@ module hyperbus_synchronous #( .host_rsp_i ( host_rsp ) ); + //////////// + // Midend // + //////////// + hyperbus_midend #( - .HostAddrWidth ( AxiAddrWidth ), - .HostDataWidth ( AxiDataWidth ), - .NumChips ( NumChips ), - .NumPhys ( NumPhys ), - .host_cmd_t ( host_cmd_t ), - .host_w_t ( host_w_t ), - .host_r_t ( host_r_t ), - .host_wrsp_t ( host_wrsp_t ), - .host_req_t ( host_req_t ), - .host_rsp_t ( host_rsp_t ), - .hyper_rx_t ( hyper_rx_t ), - .hyper_tx_t ( hyper_tx_t ), - .hyper_cmd_t ( hyper_cmd_t ), - .hyper_req_t ( hyper_req_t ), - .hyper_rsp_t ( hyper_rsp_t ), - .rule_t ( axi_rule_t ) + .HostAddrWidth ( AxiAddrWidth ), + .HostDataWidth ( AxiDataWidth ), + .NumPhys ( NumPhys ), + .HostCommandDepth ( HostCommandDepth ), + .HostWriteBufferBytes ( HostWriteBufferBytes ), + .host_cmd_t ( host_cmd_t ), + .host_w_t ( host_w_t ), + .host_r_t ( host_r_t ), + .host_wrsp_t ( host_wrsp_t ), + .host_req_t ( host_req_t ), + .host_rsp_t ( host_rsp_t ), + .hyper_rx_t ( hyper_rx_t ), + .hyper_tx_t ( hyper_tx_t ), + .hyper_cmd_t ( hyper_cmd_t ), + .hyper_req_t ( hyper_req_t ), + .hyper_rsp_t ( hyper_rsp_t ), + .rule_t ( axi_rule_t ) ) i_midend ( .clk_i ( clk_sys_i ), .rst_ni ( rst_sys_ni ), @@ -168,8 +194,11 @@ module hyperbus_synchronous #( .hyper_link_rsp_i ( midend_rsp ) ); + ///////////// + // Backend // + ///////////// + hyperbus_backend #( - .NumChips ( NumChips ), .NumPhys ( NumPhys ), .StartupCycles ( PhyStartupCycles ), .SyncStages ( SyncStages ), diff --git a/src/hyperbus_write_adapter.sv b/src/hyperbus_write_adapter.sv index 65b9fc3..a63a2c9 100644 --- a/src/hyperbus_write_adapter.sv +++ b/src/hyperbus_write_adapter.sv @@ -36,10 +36,10 @@ module hyperbus_write_adapter #( localparam int unsigned WordCntWidth = (PhyBeatsPerHost == 1) ? 1 : $clog2(PhyBeatsPerHost); - typedef enum logic [2:0] { + typedef enum logic [1:0] { Idle, - Sample, - CntReady + CollectHost, + EmitPhy } write_adapter_state_e; typedef struct packed { @@ -48,33 +48,77 @@ module hyperbus_write_adapter #( logic last; } write_buffer_t; + ////////////////////// + // Persistent state // + ////////////////////// + write_adapter_state_e state_d, state_q; write_buffer_t data_buffer_d, data_buffer_q; - logic upsize; - logic enough_data; logic first_tx_d, first_tx_q; logic [NumPhys*2-1:0] mask_strobe_d, mask_strobe_q; - logic [WordCntWidth-1:0] word_cnt; logic [AddrWidth-1:0] byte_idx_d, byte_idx_q; logic [3:0] size_d, size_q; logic [AddrWidth-1:0] cnt_data_phy_d, cnt_data_phy_q; + + ////////////////////// + // Address tracking // + ////////////////////// + + logic upsize; + logic enough_data; + logic [WordCntWidth-1:0] word_cnt; + logic [AddrWidth-1:0] cnt_data_phy_next; logic keep_sending; logic [16*NumPhys-1:0] converted_data; logic [2*NumPhys-1:0] converted_strb; logic converted_last; logic converted_valid; logic converted_ready; + logic host_accepted; + logic converted_accepted; assign upsize = ((size_q == 1) && (NumPhys == 2)) || (size_q == 0); assign enough_data = !upsize; - assign keep_sending = (size_d > ($clog2(NumPhys) + 1)) && - (cnt_data_phy_d != byte_idx_q); + assign cnt_data_phy_next = cnt_data_phy_q + NumPhys * 2; + // Determine whether another PHY word follows without depending on downstream readiness. + assign keep_sending = (size_q > ($clog2(NumPhys) + 1)) && + (cnt_data_phy_next != byte_idx_q); assign word_cnt = cnt_data_phy_q >> ($clog2(NumPhys) + 1); assign converted_data = data_buffer_q.data[(16*NumPhys)*word_cnt +: (16*NumPhys)]; assign converted_strb = data_buffer_q.strb[(2*NumPhys)*word_cnt +: (2*NumPhys)] & mask_strobe_q; assign converted_last = data_buffer_q.last && (!keep_sending || upsize); + assign host_accepted = host_valid_i && host_ready_o; + assign converted_accepted = converted_valid && converted_ready; + + //////////////////////// + // Control conditions // + //////////////////////// + + logic collect_complete; + logic collect_partial_last; + logic phy_last_accepted; + logic wide_host_word_emitted; + logic wide_host_word_can_emit; + logic wide_host_word_needs_collect; + logic wide_host_word_without_data; + logic narrow_group_emitted; + assign collect_complete = host_accepted && + (enough_data || (byte_idx_d[NumPhys-1:0] == '0) || data_i.last); + assign collect_partial_last = host_accepted && !enough_data && + (byte_idx_d[NumPhys-1:0] != '0) && data_i.last; + + assign phy_last_accepted = converted_accepted && converted_last; + assign wide_host_word_emitted = converted_accepted && !converted_last && + (size_d >= NumPhys) && (cnt_data_phy_d == byte_idx_q); + assign wide_host_word_can_emit = wide_host_word_emitted && host_valid_i && enough_data; + assign wide_host_word_needs_collect = wide_host_word_emitted && + host_valid_i && !enough_data; + assign wide_host_word_without_data = wide_host_word_emitted && !host_valid_i; + assign narrow_group_emitted = converted_accepted && !converted_last && + (size_d < NumPhys) && + (cnt_data_phy_d[NumPhys-1:0] == '0); always_comb begin : proc_counters byte_idx_d = byte_idx_q; @@ -88,22 +132,25 @@ module hyperbus_write_adapter #( cnt_data_phy_d = (start_addr_i >> NumPhys) << NumPhys; first_tx_d = 1'b1; end - if (host_valid_i && host_ready_o) begin + if (host_accepted) begin byte_idx_d = ((byte_idx_q >> size_d) << size_d) + (1 << size_d); first_tx_d = 1'b0; end - if (converted_valid && converted_ready) begin + if (converted_accepted) begin cnt_data_phy_d = cnt_data_phy_q + NumPhys * 2; end end + /////////////////// + // Beat assembly // + /////////////////// + always_comb begin : proc_sample data_buffer_d = data_buffer_q; - if (state_d == Idle) begin + if (state_q == Idle) begin data_buffer_d.last = 1'b0; - data_buffer_d.data = '0; - end else if (host_ready_o && host_valid_i) begin + end else if (host_accepted) begin if (!upsize) begin // A full host beat remains buffered until all PHY words are emitted. data_buffer_d.data = data_i.data; @@ -129,54 +176,61 @@ module hyperbus_write_adapter #( end end + // Mask bytes beyond a short final host beat. + always_comb begin : proc_mask_strobe + mask_strobe_d = mask_strobe_q; + + if (state_q == Idle) begin + mask_strobe_d = '1; + end + if (collect_partial_last) begin + for (int unsigned i = 0; i < NumPhys * 2; i++) begin + mask_strobe_d[i] = i < byte_idx_d[NumPhys-1:0]; + end + end + end + + /////////////////////////// + // Adapter state machine // + /////////////////////////// + always_comb begin : proc_fsm - state_d = state_q; - mask_strobe_d = mask_strobe_q; - host_ready_o = 1'b0; + state_d = state_q; + host_ready_o = 1'b0; converted_valid = 1'b0; unique case (state_q) + // Wait for a command to initialize address tracking. Idle: begin - mask_strobe_d = '1; if (start_i) begin - state_d = Sample; + state_d = CollectHost; end end - Sample: begin + // Gather enough host bytes to form the next physical word. + CollectHost: begin host_ready_o = 1'b1; - if (host_valid_i && enough_data) begin - state_d = CntReady; - end else if (host_valid_i) begin - if (byte_idx_d[NumPhys-1:0] != '0) begin - if (data_i.last) begin - state_d = CntReady; - for (int unsigned i = 0; i < NumPhys * 2; i++) begin - mask_strobe_d[i] = i < byte_idx_d[NumPhys-1:0]; - end - end - end else begin - state_d = CntReady; - end + if (collect_complete) begin + state_d = EmitPhy; end end - CntReady: begin + // Emit physical words and return for more host data when needed. + EmitPhy: begin converted_valid = 1'b1; - if (converted_ready) begin - if (converted_last) begin - state_d = start_i ? Sample : Idle; - end else if (size_d >= NumPhys) begin - if (cnt_data_phy_d != byte_idx_q) begin - state_d = CntReady; - end else if (host_valid_i) begin - host_ready_o = 1'b1; - state_d = enough_data ? CntReady : Sample; - end else begin - state_d = Sample; - end - end else if (cnt_data_phy_d[NumPhys-1:0] == '0) begin - host_ready_o = !upsize; - state_d = Sample; - end + if (phy_last_accepted && start_i) begin + state_d = CollectHost; + end else if (phy_last_accepted) begin + state_d = Idle; + end else if (wide_host_word_can_emit) begin + host_ready_o = 1'b1; + state_d = EmitPhy; + end else if (wide_host_word_needs_collect) begin + host_ready_o = 1'b1; + state_d = CollectHost; + end else if (wide_host_word_without_data) begin + state_d = CollectHost; + end else if (narrow_group_emitted) begin + host_ready_o = !upsize; + state_d = CollectHost; end end default: begin @@ -185,6 +239,10 @@ module hyperbus_write_adapter #( endcase end + ////////////////////////// + // Physical-width adapter // + ////////////////////////// + if (NumPhys == 2) begin : gen_dual_phy logic split_d, split_q; @@ -225,6 +283,10 @@ module hyperbus_write_adapter #( end end + ///////////////////// + // State registers // + ///////////////////// + `FFARN(state_q, state_d, Idle, clk_i, rst_ni) `FFARN(data_buffer_q, data_buffer_d, '0, clk_i, rst_ni) `FFARN(byte_idx_q, byte_idx_d, '0, clk_i, rst_ni) diff --git a/test/axi_hyper_tb.sv b/test/axi_hyper_tb.sv index 8a18dc9..13052da 100644 --- a/test/axi_hyper_tb.sv +++ b/test/axi_hyper_tb.sv @@ -7,7 +7,7 @@ module axi_hyper_tb import axi_pkg::*; #( - parameter int unsigned NumChips = 2, + parameter int unsigned NumConnectedChips = 2, parameter int unsigned NumPhys = 2, parameter int unsigned IsClockODelayed = 0, parameter int unsigned NB_CH = 1, @@ -72,8 +72,7 @@ module axi_hyper_tb typedef axi_pkg::xbar_rule_32_t rule_t; localparam int unsigned RegBusDW = 32; - // The stable register map is a hierarchical 4 KiB window. - localparam int unsigned RegBusAW = 12; + localparam int unsigned RegBusAW = 8; localparam int unsigned TbDramDataWidth = 8; localparam int unsigned TbDramLenWidth = 32'h80000; @@ -668,12 +667,12 @@ module axi_hyper_tb localparam axi_addr_t BarrierAddr = axi_addr_t'(32'h8000_6000); logic reg_error; time write_done_time; - time cfg_done_time; + time flush_done_time; write_done_time = 0; - cfg_done_time = 0; + flush_done_time = 0; $display("==========================="); - $display("= Config drain/apply ="); + $display("= Config flush barrier ="); $display("==========================="); fork @@ -683,19 +682,17 @@ module axi_hyper_tb end begin repeat (32) @(posedge clk); - // Any accepted configuration write triggers the automatic drain/apply - // sequence; COMMAND remains inert until staged apply is implemented. - reg_drv.send_write(32'h410, 32'd350, '1, reg_error); - cfg_done_time = $time; + reg_drv.send_write(32'h50, '0, '1, reg_error); + flush_done_time = $time; if (reg_error != 1'b0) begin - $error("[CFG-DRAIN] Configuration write returned an error"); + $error("[CFG-BARRIER] Flush register write returned an error"); end end join - if ((write_done_time == 0) || (cfg_done_time < write_done_time)) begin - $error("[CFG-DRAIN] Configuration completed at %0t before AXI write completed at %0t", - cfg_done_time, write_done_time); + if ((write_done_time == 0) || (flush_done_time < write_done_time)) begin + $error("[CFG-BARRIER] Flush completed at %0t before AXI write completed at %0t", + flush_done_time, write_done_time); end endtask @@ -741,13 +738,13 @@ module axi_hyper_tb $error("[DECODE] Invalid write returned response %0d", b.b_resp); end - reg_drv.send_read(32'h010, status, reg_error); + reg_drv.send_read(32'h54, status, reg_error); if ((reg_error != 1'b0) || !status[0]) begin $error("[DECODE] Sticky decode-error status was not set"); end - reg_drv.send_write(32'h010, 32'h1, '1, reg_error); + reg_drv.send_write(32'h54, 32'h1, '1, reg_error); if (reg_error != 1'b0) $error("unexpected error"); - reg_drv.send_read(32'h010, status, reg_error); + reg_drv.send_read(32'h54, status, reg_error); if ((reg_error != 1'b0) || status[0]) begin $error("[DECODE] Sticky decode-error status did not clear"); end @@ -762,7 +759,7 @@ module axi_hyper_tb logic [31:0] segment_start_snapshot; logic reg_error; - if (NumChips < 2) begin + if (NumConnectedChips < 2) begin return; end @@ -770,9 +767,9 @@ module axi_hyper_tb $display("= Cross-chip burst ="); $display("==========================="); - reg_drv.send_write(32'h404, Boundary, '1, reg_error); + reg_drv.send_write(32'h34, Boundary, '1, reg_error); if (reg_error != 1'b0) $error("unexpected error"); - reg_drv.send_write(32'h440, Boundary, '1, reg_error); + reg_drv.send_write(32'h38, Boundary, '1, reg_error); if (reg_error != 1'b0) $error("unexpected error"); segment_start_snapshot = segment_start_count; @@ -783,9 +780,9 @@ module axi_hyper_tb segment_start_count - segment_start_snapshot); end - reg_drv.send_write(32'h440, 32'h8100_0000, '1, reg_error); + reg_drv.send_write(32'h38, 32'h8100_0000, '1, reg_error); if (reg_error != 1'b0) $error("unexpected error"); - reg_drv.send_write(32'h404, 32'h8100_0000, '1, reg_error); + reg_drv.send_write(32'h34, 32'h8100_0000, '1, reg_error); if (reg_error != 1'b0) $error("unexpected error"); endtask @@ -821,7 +818,7 @@ module axi_hyper_tb axi_ctrl_master_t::r_beat_t r; logic reg_error; - if (NumChips < 2) begin + if (NumConnectedChips < 2) begin return; end @@ -830,7 +827,7 @@ module axi_hyper_tb $display("==========================="); // A zero bound extends the final rule through the end of the address space. - reg_drv.send_write(32'h444, '0, '1, reg_error); + reg_drv.send_write(32'h3c, '0, '1, reg_error); if (reg_error != 1'b0) $error("unexpected error"); axi_write_subword(axi_drv, ValidAddr, TestData, 3); axi_check_subword(axi_drv, ValidAddr, TestData, 3); @@ -851,7 +848,7 @@ module axi_hyper_tb end end - reg_drv.send_write(32'h444, 32'h8200_0000, '1, reg_error); + reg_drv.send_write(32'h3c, 32'h8200_0000, '1, reg_error); if (reg_error != 1'b0) $error("unexpected error"); endtask @@ -951,7 +948,7 @@ module axi_hyper_tb logic [31:0] segment_start_snapshot; logic reg_error; - if (NumChips < 2) begin + if (NumConnectedChips < 2) begin return; end @@ -988,7 +985,7 @@ module axi_hyper_tb end // A zero-ended final rule must contain ordinary atomic accesses. - reg_drv.send_write(32'h444, '0, '1, reg_error); + reg_drv.send_write(32'h3c, '0, '1, reg_error); if (reg_error != 1'b0) $error("unexpected error"); axi_write_subword(axi_drv, ZeroEndAddr, ZeroEndInitial, 2); ax.ax_addr = ZeroEndAddr; @@ -1007,7 +1004,7 @@ module axi_hyper_tb r.r_data[31:0], r.r_resp, b.b_resp); end axi_check_subword(axi_drv, ZeroEndAddr, ZeroEndInitial + ZeroEndAddend, 2); - reg_drv.send_write(32'h444, 32'h8200_0000, '1, reg_error); + reg_drv.send_write(32'h3c, 32'h8200_0000, '1, reg_error); if (reg_error != 1'b0) $error("unexpected error"); endtask @@ -1040,26 +1037,26 @@ module axi_hyper_tb mst_scoreboard.monitor(); // Map each chip to a distinct 16 MiB host-address window. - if (NumChips > 1) begin - reg_master.send_write(32'h444, 32'h8200_0000, '1, s_reg_error); + if (NumConnectedChips > 1) begin + reg_master.send_write(32'h3c, 32'h8200_0000, '1, s_reg_error); if (s_reg_error != 1'b0) $error("unexpected error"); - reg_master.send_write(32'h440, 32'h8100_0000, '1, s_reg_error); + reg_master.send_write(32'h38, 32'h8100_0000, '1, s_reg_error); if (s_reg_error != 1'b0) $error("unexpected error"); end - reg_master.send_write(32'h404, 32'h8100_0000, '1, s_reg_error); + reg_master.send_write(32'h34, 32'h8100_0000, '1, s_reg_error); if (s_reg_error != 1'b0) $error("unexpected error"); - reg_master.send_write(32'h400, 32'h8000_0000, '1, s_reg_error); + reg_master.send_write(32'h30, 32'h8000_0000, '1, s_reg_error); if (s_reg_error != 1'b0) $error("unexpected error"); - reg_master.send_write(32'h418, TbRxDelayLineTaps, '1, s_reg_error); + reg_master.send_write(32'h4 << 2, TbRxDelayLineTaps, '1, s_reg_error); if (s_reg_error != 1'b0) $error("unexpected error"); - reg_master.send_write(32'h300, TbTxDelayLineTaps, '1, s_reg_error); + reg_master.send_write(32'h5 << 2, TbTxDelayLineTaps, '1, s_reg_error); if (s_reg_error != 1'b0) $error("unexpected error"); if (TbDutVariant == 0) begin - reg_master.send_read(32'h200, reg_read, s_reg_error); + reg_master.send_read(32'h78, reg_read, s_reg_error); if ((s_reg_error != 1'b0) || (reg_read != 8)) $error("unexpected divider reset value"); - reg_master.send_write(32'h200, 8'd2, '1, s_reg_error); + reg_master.send_write(32'h78, 8'd2, '1, s_reg_error); if (s_reg_error != 1'b0) $error("unexpected error"); end @@ -1067,18 +1064,18 @@ module axi_hyper_tb if (TbDutVariant == 0) begin // The configuration barrier completes only after the divided clock resumes. - reg_master.send_write(32'h200, 8'd4, '1, s_reg_error); + reg_master.send_write(32'h78, 8'd4, '1, s_reg_error); if (s_reg_error != 1'b0) $error("unexpected error"); - reg_master.send_read(32'h200, reg_read, s_reg_error); + reg_master.send_read(32'h78, reg_read, s_reg_error); if ((s_reg_error != 1'b0) || (reg_read != 4)) $error("divider update failed"); divider_cycle_snapshot = cycle_count; axi_write_slow(axi_ctrl_mst, 32'h8000_7000, 4, 0); div4_write_cycles = cycle_count - divider_cycle_snapshot; - reg_master.send_write(32'h200, 8'd2, '1, s_reg_error); + reg_master.send_write(32'h78, 8'd2, '1, s_reg_error); if (s_reg_error != 1'b0) $error("unexpected error"); - reg_master.send_read(32'h200, reg_read, s_reg_error); + reg_master.send_read(32'h78, reg_read, s_reg_error); if ((s_reg_error != 1'b0) || (reg_read != 2)) $error("divider restore failed"); divider_cycle_snapshot = cycle_count; @@ -1163,7 +1160,7 @@ module axi_hyper_tb $display("= Use only phy 0 ="); $display("==========================="); - reg_master.send_write(32'h100,1'b0,'1,s_reg_error); + reg_master.send_write(32'h20,1'b0,'1,s_reg_error); if (s_reg_error != 1'b0) $error("unexpected error"); axi_rand_mst.reset(); @@ -1199,7 +1196,7 @@ module axi_hyper_tb .RegAw ( RegBusAW ), .RegDw ( RegBusDW ), - .NumChips ( NumChips ), + .NumConnectedChips ( NumConnectedChips ), .NumPhys ( NumPhys ), .AnnotateSdf ( TbAnnotateSdf ), .IsClockODelayed ( IsClockODelayed ), diff --git a/test/dut_if.sv b/test/dut_if.sv index 5075bf1..d7485e8 100644 --- a/test/dut_if.sv +++ b/test/dut_if.sv @@ -22,7 +22,7 @@ module dut_if parameter int RegAw = -1, parameter int RegDw = -1, - parameter int NumChips = -1, + parameter int NumConnectedChips = 2, parameter int NumPhys = -1, parameter bit AnnotateSdf = 1'b1, parameter int IsClockODelayed = -1, @@ -74,7 +74,7 @@ module dut_if `REG_BUS_ASSIGN_FROM_RSP(reg_slv_if,reg_resp) - logic [NumPhys-1:0][NumChips-1:0] hyper_cs_n_wire; + logic [NumPhys-1:0][hyperbus_pkg::HyperNumChips-1:0] hyper_cs_n_wire; logic [NumPhys-1:0] hyper_ck_wire; logic [NumPhys-1:0] hyper_ck_n_wire; logic [NumPhys-1:0] hyper_rwds_o; @@ -85,10 +85,10 @@ module dut_if logic [NumPhys-1:0] hyper_dq_oe; logic [NumPhys-1:0] hyper_reset_n_wire; logic phy_clk; - logic [NumPhys-1:0][NumChips-1:0] hyper_cs_n_q; + logic [NumPhys-1:0][hyperbus_pkg::HyperNumChips-1:0] hyper_cs_n_q; logic segment_start; - wire [NumPhys-1:0][NumChips-1:0] pad_hyper_csn; + wire [NumPhys-1:0][NumConnectedChips-1:0] pad_hyper_csn; wire [NumPhys-1:0] pad_hyper_ck; wire [NumPhys-1:0] pad_hyper_ckn; wire [NumPhys-1:0] pad_hyper_rwds; @@ -154,7 +154,6 @@ module dut_if // DUT hyperbus_test_dut #( .DutVariant ( DutVariant ), - .NumChips ( NumChips ), .NumPhys ( NumPhys ), .AxiAddrWidth ( AxiAddrWidth ), .AxiDataWidth ( AxiDataWidth ), @@ -196,7 +195,7 @@ module dut_if generate for (genvar i=0; i Date: Tue, 11 Aug 2026 15:47:08 +0200 Subject: [PATCH 2/5] Tune frontend queues and drain accounting --- src/hyperbus_asynchronous.sv | 34 ++++++++------ src/hyperbus_axi_frontend.sv | 88 ++++++++++++++++++++++++++---------- src/hyperbus_isochronous.sv | 34 ++++++++------ src/hyperbus_midend.sv | 9 ++-- src/hyperbus_synchronous.sv | 34 ++++++++------ 5 files changed, 128 insertions(+), 71 deletions(-) diff --git a/src/hyperbus_asynchronous.sv b/src/hyperbus_asynchronous.sv index 3069b4e..0c0d567 100644 --- a/src/hyperbus_asynchronous.sv +++ b/src/hyperbus_asynchronous.sv @@ -17,8 +17,9 @@ module hyperbus_asynchronous #( parameter type reg_req_t = logic, parameter type reg_rsp_t = logic, parameter type axi_rule_t = logic, - parameter int unsigned HostCommandDepth = 8, - parameter int unsigned HostWriteBufferBytes = 128, + parameter int unsigned AxiMaxReadTxns = 4, + parameter int unsigned AxiMaxWriteTxns = 4, + parameter int unsigned HostWriteBufferBytes = 64, parameter int unsigned RxFifoLogDepth = 3, parameter int unsigned TxFifoLogDepth = 3, parameter int unsigned PhyStartupCycles = 300 * 200, @@ -53,10 +54,13 @@ module hyperbus_asynchronous #( ); `ASSERT_INIT(AxiAddrWidthValid, AxiAddrWidth >= $clog2(AxiDataWidth / 8)) + `ASSERT_INIT(AxiMaxReadTxnsValid, AxiMaxReadTxns >= 1) + `ASSERT_INIT(AxiMaxWriteTxnsValid, AxiMaxWriteTxns >= 1) typedef logic [AxiAddrWidth-1:0] host_addr_t; typedef logic [AxiDataWidth-1:0] host_data_t; typedef logic [AxiDataWidth/8-1:0] host_strb_t; + localparam int unsigned AxiDataBytes = AxiDataWidth / 8; `HYPERBUS_TYPEDEF_HOST_ALL_CT(host, host_addr_t, host_data_t, host_strb_t) `HYPERBUS_TYPEDEF_LINK_ALL_CT(hyper, NumPhys) @@ -150,14 +154,17 @@ module hyperbus_asynchronous #( ////////////////// hyperbus_axi_frontend #( - .AxiDataWidth ( AxiDataWidth ), - .AxiAddrWidth ( AxiAddrWidth ), - .AxiIdWidth ( AxiIdWidth ), - .AxiUserWidth ( AxiUserWidth ), - .axi_req_t ( axi_req_t ), - .axi_rsp_t ( axi_rsp_t ), - .host_req_t ( host_req_t ), - .host_rsp_t ( host_rsp_t ) + .AxiDataWidth ( AxiDataWidth ), + .AxiAddrWidth ( AxiAddrWidth ), + .AxiIdWidth ( AxiIdWidth ), + .AxiUserWidth ( AxiUserWidth ), + .AxiMaxReadTxns ( AxiMaxReadTxns ), + .AxiMaxWriteTxns ( AxiMaxWriteTxns ), + .MaxWriteDataBeats ( HostWriteBufferBytes / AxiDataBytes ), + .axi_req_t ( axi_req_t ), + .axi_rsp_t ( axi_rsp_t ), + .host_req_t ( host_req_t ), + .host_rsp_t ( host_rsp_t ) ) i_axi_frontend ( .clk_i ( clk_sys_i ), .rst_ni ( rst_sys_ni ), @@ -174,10 +181,9 @@ module hyperbus_asynchronous #( //////////// hyperbus_midend #( - .HostAddrWidth ( AxiAddrWidth ), - .HostDataWidth ( AxiDataWidth ), - .NumPhys ( NumPhys ), - .HostCommandDepth ( HostCommandDepth ), + .HostAddrWidth ( AxiAddrWidth ), + .HostDataWidth ( AxiDataWidth ), + .NumPhys ( NumPhys ), .HostWriteBufferBytes ( HostWriteBufferBytes ), .host_cmd_t ( host_cmd_t ), .host_w_t ( host_w_t ), diff --git a/src/hyperbus_axi_frontend.sv b/src/hyperbus_axi_frontend.sv index 1fcff28..c110ec2 100644 --- a/src/hyperbus_axi_frontend.sv +++ b/src/hyperbus_axi_frontend.sv @@ -7,14 +7,18 @@ `include "common_cells/assertions.svh" module hyperbus_axi_frontend #( - parameter int unsigned AxiDataWidth = -1, - parameter int unsigned AxiAddrWidth = -1, - parameter int unsigned AxiIdWidth = -1, - parameter int unsigned AxiUserWidth = -1, - parameter type axi_req_t = logic, - parameter type axi_rsp_t = logic, - parameter type host_req_t = logic, - parameter type host_rsp_t = logic + parameter int unsigned AxiDataWidth = -1, + parameter int unsigned AxiAddrWidth = -1, + parameter int unsigned AxiIdWidth = -1, + parameter int unsigned AxiUserWidth = -1, + parameter int unsigned AxiMaxReadTxns = 4, + parameter int unsigned AxiMaxWriteTxns = 4, + // Maximum number of independently accepted W beats held by the host path. + parameter int unsigned MaxWriteDataBeats = 1, + parameter type axi_req_t = logic, + parameter type axi_rsp_t = logic, + parameter type host_req_t = logic, + parameter type host_rsp_t = logic ) ( input logic clk_i, input logic rst_ni, @@ -35,6 +39,9 @@ module hyperbus_axi_frontend #( (AxiDataWidth & (AxiDataWidth - 1)) == 0) `ASSERT_INIT(AxiIdWidthValid, AxiIdWidth >= 1) `ASSERT_INIT(AxiUserWidthValid, AxiUserWidth >= 1) + `ASSERT_INIT(AxiMaxReadTxnsValid, AxiMaxReadTxns >= 1) + `ASSERT_INIT(AxiMaxWriteTxnsValid, AxiMaxWriteTxns >= 1) + `ASSERT_INIT(MaxWriteDataBeatsValid, MaxWriteDataBeats >= 1) typedef logic [AxiAddrWidth-1:0] axi_addr_t; @@ -75,26 +82,59 @@ module hyperbus_axi_frontend #( // Drain accounting // ////////////////////// - localparam int unsigned PendingWidth = 8; - typedef logic [PendingWidth-1:0] pending_cnt_t; - typedef logic signed [PendingWidth-1:0] write_balance_t; - - pending_cnt_t read_pending_d, read_pending_q; - pending_cnt_t write_pending_d, write_pending_q; - write_balance_t write_balance_d, write_balance_q; + // Widths cover the configured serializer queues. ATOP read responses are + // admitted only after the serializer has drained the existing ID FIFOs. + localparam longint unsigned AxiMaxReadTxnsLong = AxiMaxReadTxns; + localparam longint unsigned AxiMaxWriteTxnsLong = AxiMaxWriteTxns; + localparam int unsigned ReadPendingWidth = (AxiMaxReadTxns < 1) ? + 1 : $clog2(AxiMaxReadTxnsLong + 64'd1); + localparam int unsigned WritePendingWidth = (AxiMaxWriteTxns < 1) ? + 1 : $clog2(AxiMaxWriteTxnsLong + 64'd1); + // The balance can be positive for AWs accepted ahead of W, or negative for + // complete W bursts buffered ahead of their matching AW. Use the larger magnitude; + // the 64-bit literal keeps the width calculation from overflowing when a + // 32-bit parameter is at its maximum value. + localparam longint unsigned MaxWriteDataBeatsLong = MaxWriteDataBeats; + localparam int unsigned WriteBalancePositiveBits = + $clog2(AxiMaxWriteTxnsLong + 64'd1); + localparam int unsigned WriteBalanceNegativeBits = + $clog2(MaxWriteDataBeatsLong); + localparam int unsigned WriteBalanceWidth = 1 + + ((WriteBalancePositiveBits > WriteBalanceNegativeBits) ? + WriteBalancePositiveBits : WriteBalanceNegativeBits); + localparam longint unsigned WriteBalanceHalfRange = 64'd1 << (WriteBalanceWidth - 1); + typedef logic [ReadPendingWidth-1:0] read_pending_t; + typedef logic [WritePendingWidth-1:0] write_pending_t; + typedef logic signed [WriteBalanceWidth-1:0] write_balance_t; + + `ASSERT_INIT(WriteBalancePositiveRangeValid, + AxiMaxWriteTxns < WriteBalanceHalfRange) + `ASSERT_INIT(WriteBalanceNegativeRangeValid, + MaxWriteDataBeats <= WriteBalanceHalfRange) + + read_pending_t read_pending_d, read_pending_q; + write_pending_t write_pending_d, write_pending_q; + write_balance_t write_balance_d, write_balance_q; logic w_partial_d, w_partial_q; logic allow_aw, allow_w; + logic allow_aw_for_complete_w, allow_aw_for_partial_w; logic axi_ar_accepted, axi_aw_accepted, axi_w_accepted; logic axi_atomic_read_started; logic axi_r_completed, axi_b_accepted; + // A negative balance means that one or more complete W bursts are waiting + // for AW. A partial burst with zero balance is also waiting for its AW; + // allowing that AW lets a full W FIFO drain instead of deadlocking. + assign allow_aw_for_complete_w = write_balance_q < 0; + assign allow_aw_for_partial_w = w_partial_q && (write_balance_q == '0); + always_comb begin : proc_axi_drain allow_aw = !drain_i; allow_w = !drain_i; if (drain_i) begin // Complete only channel fragments accepted before the barrier. - allow_aw = !w_partial_q && (write_balance_q < 0); + allow_aw = allow_aw_for_complete_w || allow_aw_for_partial_w; allow_w = w_partial_q || (write_balance_q > 0); end @@ -123,9 +163,9 @@ module hyperbus_axi_frontend #( write_balance_d = write_balance_q; w_partial_d = w_partial_q; - read_pending_d = read_pending_q + pending_cnt_t'(axi_ar_accepted) + - pending_cnt_t'(axi_atomic_read_started) - - pending_cnt_t'(axi_r_completed); + read_pending_d = read_pending_q + read_pending_t'(axi_ar_accepted) + + read_pending_t'(axi_atomic_read_started) - + read_pending_t'(axi_r_completed); unique case ({axi_aw_accepted, axi_b_accepted}) 2'b10: write_pending_d = write_pending_q + 1'b1; @@ -157,11 +197,11 @@ module hyperbus_axi_frontend #( ///////////////////////// axi_serializer #( - .MaxReadTxns ( 4 ), - .MaxWriteTxns ( 4 ), - .AxiIdWidth ( AxiIdWidth ), - .axi_req_t ( axi_req_t ), - .axi_resp_t ( axi_rsp_t ) + .MaxReadTxns ( AxiMaxReadTxns ), + .MaxWriteTxns ( AxiMaxWriteTxns ), + .AxiIdWidth ( AxiIdWidth ), + .axi_req_t ( axi_req_t ), + .axi_resp_t ( axi_rsp_t ) ) i_axi_serializer ( .clk_i, .rst_ni, diff --git a/src/hyperbus_isochronous.sv b/src/hyperbus_isochronous.sv index 050daaa..74b4d62 100644 --- a/src/hyperbus_isochronous.sv +++ b/src/hyperbus_isochronous.sv @@ -18,8 +18,9 @@ module hyperbus_isochronous #( parameter type reg_req_t = logic, parameter type reg_rsp_t = logic, parameter type axi_rule_t = logic, - parameter int unsigned HostCommandDepth = 8, - parameter int unsigned HostWriteBufferBytes = 128, + parameter int unsigned AxiMaxReadTxns = 4, + parameter int unsigned AxiMaxWriteTxns = 4, + parameter int unsigned HostWriteBufferBytes = 64, parameter int unsigned PhyStartupCycles = 300 * 200, parameter int unsigned SyncStages = 2 ) ( @@ -49,10 +50,13 @@ module hyperbus_isochronous #( ); `ASSERT_INIT(AxiAddrWidthValid, AxiAddrWidth >= $clog2(AxiDataWidth / 8)) + `ASSERT_INIT(AxiMaxReadTxnsValid, AxiMaxReadTxns >= 1) + `ASSERT_INIT(AxiMaxWriteTxnsValid, AxiMaxWriteTxns >= 1) typedef logic [AxiAddrWidth-1:0] host_addr_t; typedef logic [AxiDataWidth-1:0] host_data_t; typedef logic [AxiDataWidth/8-1:0] host_strb_t; + localparam int unsigned AxiDataBytes = AxiDataWidth / 8; `HYPERBUS_TYPEDEF_HOST_ALL_CT(host, host_addr_t, host_data_t, host_strb_t) `HYPERBUS_TYPEDEF_LINK_ALL_CT(hyper, NumPhys) @@ -202,14 +206,17 @@ module hyperbus_isochronous #( ////////////////// hyperbus_axi_frontend #( - .AxiDataWidth ( AxiDataWidth ), - .AxiAddrWidth ( AxiAddrWidth ), - .AxiIdWidth ( AxiIdWidth ), - .AxiUserWidth ( AxiUserWidth ), - .axi_req_t ( axi_req_t ), - .axi_rsp_t ( axi_rsp_t ), - .host_req_t ( host_req_t ), - .host_rsp_t ( host_rsp_t ) + .AxiDataWidth ( AxiDataWidth ), + .AxiAddrWidth ( AxiAddrWidth ), + .AxiIdWidth ( AxiIdWidth ), + .AxiUserWidth ( AxiUserWidth ), + .AxiMaxReadTxns ( AxiMaxReadTxns ), + .AxiMaxWriteTxns ( AxiMaxWriteTxns ), + .MaxWriteDataBeats ( HostWriteBufferBytes / AxiDataBytes ), + .axi_req_t ( axi_req_t ), + .axi_rsp_t ( axi_rsp_t ), + .host_req_t ( host_req_t ), + .host_rsp_t ( host_rsp_t ) ) i_axi_frontend ( .clk_i ( clk_sys_i ), .rst_ni ( rst_sys_ni ), @@ -226,10 +233,9 @@ module hyperbus_isochronous #( //////////// hyperbus_midend #( - .HostAddrWidth ( AxiAddrWidth ), - .HostDataWidth ( AxiDataWidth ), - .NumPhys ( NumPhys ), - .HostCommandDepth ( HostCommandDepth ), + .HostAddrWidth ( AxiAddrWidth ), + .HostDataWidth ( AxiDataWidth ), + .NumPhys ( NumPhys ), .HostWriteBufferBytes ( HostWriteBufferBytes ), .host_cmd_t ( host_cmd_t ), .host_w_t ( host_w_t ), diff --git a/src/hyperbus_midend.sv b/src/hyperbus_midend.sv index 8e2702f..8b25cf2 100644 --- a/src/hyperbus_midend.sv +++ b/src/hyperbus_midend.sv @@ -9,8 +9,7 @@ module hyperbus_midend #( parameter int unsigned HostAddrWidth = -1, parameter int unsigned HostDataWidth = -1, parameter int unsigned NumPhys = -1, - parameter int unsigned HostCommandDepth = 8, - parameter int unsigned HostWriteBufferBytes = 128, + parameter int unsigned HostWriteBufferBytes = 64, parameter type host_cmd_t = logic, parameter type host_w_t = logic, parameter type host_r_t = logic, @@ -43,6 +42,7 @@ module hyperbus_midend #( localparam int unsigned HostBusAddrWidth = $clog2(HostDataBytes); localparam int unsigned PhyDataWidth = NumPhys * 16; localparam int unsigned WriteFifoDepth = HostWriteBufferBytes / HostDataBytes; + localparam int unsigned CommandFifoDepth = 2; localparam int unsigned ReadFifoDepth = 4; localparam int unsigned WriteRspFifoDepth = 4; localparam int unsigned ChipSelWidth = @@ -54,7 +54,6 @@ module hyperbus_midend #( HostDataWidth >= PhyDataWidth && HostDataWidth <= 1024 && (HostDataWidth & (HostDataWidth - 1)) == 0 && (HostDataWidth % PhyDataWidth) == 0) - `ASSERT_INIT(HostCommandDepthValid, HostCommandDepth >= 1) `ASSERT_INIT(HostWriteBufferSizeValid, HostWriteBufferBytes >= HostDataBytes && (HostWriteBufferBytes % HostDataBytes) == 0) @@ -92,8 +91,8 @@ module hyperbus_midend #( stream_fifo #( .FALL_THROUGH ( 1'b0 ), - .DEPTH ( HostCommandDepth ), - .T ( host_cmd_t ) + .DEPTH ( CommandFifoDepth ), + .T ( host_cmd_t ) ) i_host_cmd_fifo ( .clk_i, .rst_ni, diff --git a/src/hyperbus_synchronous.sv b/src/hyperbus_synchronous.sv index bad36a5..fdca6cd 100644 --- a/src/hyperbus_synchronous.sv +++ b/src/hyperbus_synchronous.sv @@ -17,8 +17,9 @@ module hyperbus_synchronous #( parameter type reg_req_t = logic, parameter type reg_rsp_t = logic, parameter type axi_rule_t = logic, - parameter int unsigned HostCommandDepth = 8, - parameter int unsigned HostWriteBufferBytes = 128, + parameter int unsigned AxiMaxReadTxns = 4, + parameter int unsigned AxiMaxWriteTxns = 4, + parameter int unsigned HostWriteBufferBytes = 64, parameter int unsigned PhyStartupCycles = 300 * 200, parameter int unsigned SyncStages = 2 ) ( @@ -48,10 +49,13 @@ module hyperbus_synchronous #( ); `ASSERT_INIT(AxiAddrWidthValid, AxiAddrWidth >= $clog2(AxiDataWidth / 8)) + `ASSERT_INIT(AxiMaxReadTxnsValid, AxiMaxReadTxns >= 1) + `ASSERT_INIT(AxiMaxWriteTxnsValid, AxiMaxWriteTxns >= 1) typedef logic [AxiAddrWidth-1:0] host_addr_t; typedef logic [AxiDataWidth-1:0] host_data_t; typedef logic [AxiDataWidth/8-1:0] host_strb_t; + localparam int unsigned AxiDataBytes = AxiDataWidth / 8; `HYPERBUS_TYPEDEF_HOST_ALL_CT(host, host_addr_t, host_data_t, host_strb_t) `HYPERBUS_TYPEDEF_LINK_ALL_CT(hyper, NumPhys) @@ -140,14 +144,17 @@ module hyperbus_synchronous #( ////////////////// hyperbus_axi_frontend #( - .AxiDataWidth ( AxiDataWidth ), - .AxiAddrWidth ( AxiAddrWidth ), - .AxiIdWidth ( AxiIdWidth ), - .AxiUserWidth ( AxiUserWidth ), - .axi_req_t ( axi_req_t ), - .axi_rsp_t ( axi_rsp_t ), - .host_req_t ( host_req_t ), - .host_rsp_t ( host_rsp_t ) + .AxiDataWidth ( AxiDataWidth ), + .AxiAddrWidth ( AxiAddrWidth ), + .AxiIdWidth ( AxiIdWidth ), + .AxiUserWidth ( AxiUserWidth ), + .AxiMaxReadTxns ( AxiMaxReadTxns ), + .AxiMaxWriteTxns ( AxiMaxWriteTxns ), + .MaxWriteDataBeats ( HostWriteBufferBytes / AxiDataBytes ), + .axi_req_t ( axi_req_t ), + .axi_rsp_t ( axi_rsp_t ), + .host_req_t ( host_req_t ), + .host_rsp_t ( host_rsp_t ) ) i_axi_frontend ( .clk_i ( clk_sys_i ), .rst_ni ( rst_sys_ni ), @@ -164,10 +171,9 @@ module hyperbus_synchronous #( //////////// hyperbus_midend #( - .HostAddrWidth ( AxiAddrWidth ), - .HostDataWidth ( AxiDataWidth ), - .NumPhys ( NumPhys ), - .HostCommandDepth ( HostCommandDepth ), + .HostAddrWidth ( AxiAddrWidth ), + .HostDataWidth ( AxiDataWidth ), + .NumPhys ( NumPhys ), .HostWriteBufferBytes ( HostWriteBufferBytes ), .host_cmd_t ( host_cmd_t ), .host_w_t ( host_w_t ), From 4ea832c3ed29bb6a37aaeff5a68d517e0f888cf2 Mon Sep 17 00:00:00 2001 From: Philippe Sauter Date: Wed, 12 Aug 2026 00:00:28 +0200 Subject: [PATCH 3/5] Test HyperBus buffering and atomic handling --- Bender.yml | 1 + scripts/start.tcl | 1 + test/axi_hyper_tb.sv | 94 +++++++- test/hyperbus_atomic_handler_tb.sv | 361 +++++++++++++++++++++++++++++ 4 files changed, 455 insertions(+), 2 deletions(-) create mode 100644 test/hyperbus_atomic_handler_tb.sv diff --git a/Bender.yml b/Bender.yml index 4842667..df9a1bc 100644 --- a/Bender.yml +++ b/Bender.yml @@ -77,4 +77,5 @@ sources: - test/dut_if.sv - test/hyperbus_tb_pkg.sv - test/axi_hyper_tb.sv + - test/hyperbus_atomic_handler_tb.sv - test/hyperbus_cfg_regs_tb.sv diff --git a/scripts/start.tcl b/scripts/start.tcl index e1256cc..b192465 100644 --- a/scripts/start.tcl +++ b/scripts/start.tcl @@ -41,6 +41,7 @@ run_test axi_hyper_tb_isochronous sim_run_isochronous.wlf run_test axi_hyper_tb_synchronous sim_run_synchronous.wlf run_test axi_hyper_tb_asynchronous sim_run_asynchronous.wlf run_test axi_hyper_tb_synchronous_one_phy sim_run_synchronous_one_phy.wlf +run_test hyperbus_atomic_handler_tb sim_run_atomic_handler.wlf run_test hyperbus_cfg_regs_tb sim_run_cfg_regs.wlf quit -code $regression_failed -f diff --git a/test/axi_hyper_tb.sv b/test/axi_hyper_tb.sv index 13052da..d300ed0 100644 --- a/test/axi_hyper_tb.sv +++ b/test/axi_hyper_tb.sv @@ -505,6 +505,78 @@ module axi_hyper_tb end endtask + task automatic check_midend_buffering(input axi_ctrl_master_t axi_drv); + localparam int unsigned NumTransactions = 4; + localparam int unsigned WriteBeats = 4; + localparam axi_addr_t BufferBaseAddr = axi_addr_t'(32'h8000_2000); + axi_ctrl_master_t::ax_beat_t ax; + axi_ctrl_master_t::w_beat_t w; + axi_ctrl_master_t::b_beat_t b; + axi_ctrl_master_t::r_beat_t r; + axi_addr_t transaction_addr; + + $display("==========================="); + $display("= Midend queue buffering ="); + $display("==========================="); + + // Fill all write contexts and the 128-byte W buffer before accepting responses. + for (int unsigned transaction = 0; transaction < NumTransactions; transaction++) begin + ax = new(); + ax.ax_addr = BufferBaseAddr + axi_addr_t'(transaction * WriteBeats * + (TbAxiDataWidthFull / 8)); + ax.ax_id = TbAxiIdWidthFull'(transaction + 1); + ax.ax_len = WriteBeats - 1; + ax.ax_size = $clog2(TbAxiDataWidthFull / 8); + ax.ax_burst = axi_pkg::BURST_INCR; + axi_drv.send_aw(ax); + end + + for (int unsigned transaction = 0; transaction < NumTransactions; transaction++) begin + transaction_addr = BufferBaseAddr + axi_addr_t'(transaction * WriteBeats * + (TbAxiDataWidthFull / 8)); + for (int unsigned beat = 0; beat < WriteBeats; beat++) begin + w = new(); + w.w_data = slow_stress_data(transaction_addr, beat); + w.w_strb = '1; + w.w_last = beat == WriteBeats - 1; + axi_drv.send_w(w); + end + end + + for (int unsigned transaction = 0; transaction < NumTransactions; transaction++) begin + axi_drv.recv_b(b); + if ((b.b_resp != axi_pkg::RESP_OKAY) || + (b.b_id != TbAxiIdWidthFull'(transaction + 1))) begin + $error("[MIDEND-BUFFER] Write %0d returned id=%0d resp=%0d", + transaction, b.b_id, b.b_resp); + end + end + + // Four one-beat reads can occupy all read contexts and response entries. + for (int unsigned transaction = 0; transaction < NumTransactions; transaction++) begin + ax = new(); + ax.ax_addr = BufferBaseAddr + axi_addr_t'(transaction * WriteBeats * + (TbAxiDataWidthFull / 8)); + ax.ax_id = TbAxiIdWidthFull'(transaction + 1); + ax.ax_len = '0; + ax.ax_size = $clog2(TbAxiDataWidthFull / 8); + ax.ax_burst = axi_pkg::BURST_INCR; + axi_drv.send_ar(ax); + end + + for (int unsigned transaction = 0; transaction < NumTransactions; transaction++) begin + axi_drv.recv_r(r); + transaction_addr = BufferBaseAddr + axi_addr_t'(transaction * WriteBeats * + (TbAxiDataWidthFull / 8)); + if ((r.r_resp != axi_pkg::RESP_OKAY) || !r.r_last || + (r.r_id != TbAxiIdWidthFull'(transaction + 1)) || + (r.r_data != slow_stress_data(transaction_addr, 0))) begin + $error("[MIDEND-BUFFER] Read %0d returned id=%0d data=0x%016x last=%0b resp=%0d", + transaction, r.r_id, r.r_data, r.r_last, r.r_resp); + end + end + endtask + task automatic run_performance_smoke(input axi_ctrl_master_t axi_drv); localparam int unsigned NumCases = 5; localparam axi_addr_t PerfBaseAddr = axi_addr_t'(32'h8000_8000); @@ -852,7 +924,10 @@ module axi_hyper_tb if (reg_error != 1'b0) $error("unexpected error"); endtask - task automatic check_atomic_add(input axi_ctrl_master_t axi_drv); + task automatic check_atomic_add( + input axi_ctrl_master_t axi_drv, + input reg_bus_master_t reg_drv + ); localparam axi_addr_t AtomicAddr = axi_addr_t'(32'h8000_0200); localparam logic [31:0] InitialValue = 32'h1234_5678; localparam logic [31:0] Addend = 32'h0102_0304; @@ -860,6 +935,8 @@ module axi_hyper_tb axi_ctrl_master_t::w_beat_t w = new(); axi_ctrl_master_t::b_beat_t b; axi_ctrl_master_t::r_beat_t r; + logic [31:0] status; + logic reg_error; $display("==========================="); $display("= Atomic add ="); @@ -906,6 +983,12 @@ module axi_hyper_tb $error("[ATOMIC] Unsupported operation returned rresp=%0d bresp=%0d", r.r_resp, b.b_resp); end + reg_drv.send_read(32'h54, status, reg_error); + if ((reg_error != 1'b0) || !status[0]) begin + $error("[ATOMIC] Unsupported operation did not set sticky error status"); + end + reg_drv.send_write(32'h54, 32'h1, '1, reg_error); + if (reg_error != 1'b0) $error("unexpected error"); axi_check_subword(axi_drv, AtomicAddr, InitialValue + Addend, 2); // A malformed multi-beat atomic must drain all W beats before returning an error. @@ -928,6 +1011,12 @@ module axi_hyper_tb $error("[ATOMIC] Multi-beat operation returned rresp=%0d bresp=%0d", r.r_resp, b.b_resp); end + reg_drv.send_read(32'h54, status, reg_error); + if ((reg_error != 1'b0) || !status[0]) begin + $error("[ATOMIC] Malformed operation did not set sticky error status"); + end + reg_drv.send_write(32'h54, 32'h1, '1, reg_error); + if (reg_error != 1'b0) $error("unexpected error"); axi_write_subword(axi_drv, AtomicAddr, 32'h89ab_cdef, 2); axi_check_subword(axi_drv, AtomicAddr, 32'h89ab_cdef, 2); endtask @@ -1088,6 +1177,7 @@ module axi_hyper_tb end end + check_midend_buffering(axi_ctrl_mst); run_performance_smoke(axi_ctrl_mst); run_slow_backpressure_test(axi_ctrl_mst, reg_master); check_config_barrier(axi_ctrl_mst, reg_master); @@ -1095,7 +1185,7 @@ module axi_hyper_tb check_cross_chip_burst(axi_ctrl_mst, reg_master); check_large_rule_distance(axi_ctrl_mst); check_range_edges(axi_ctrl_mst, reg_master); - check_atomic_add(axi_ctrl_mst); + check_atomic_add(axi_ctrl_mst, reg_master); check_atomic_range_errors(axi_ctrl_mst, reg_master); if (TbDutVariant == 0) begin diff --git a/test/hyperbus_atomic_handler_tb.sv b/test/hyperbus_atomic_handler_tb.sv new file mode 100644 index 0000000..b4f027d --- /dev/null +++ b/test/hyperbus_atomic_handler_tb.sv @@ -0,0 +1,361 @@ +// Copyright 2026 ETH Zurich and University of Bologna. +// Solderpad Hardware License, Version 0.51, see LICENSE for details. +// SPDX-License-Identifier: SHL-0.51 + +`include "hyperbus/typedef.svh" + +module hyperbus_atomic_handler_tb; + + localparam int unsigned HostAddrWidth = 32; + localparam int unsigned HostDataWidth = 64; + + typedef logic [HostAddrWidth-1:0] host_addr_t; + typedef logic [HostDataWidth-1:0] host_data_t; + typedef logic [HostDataWidth/8-1:0] host_strb_t; + + `HYPERBUS_TYPEDEF_HOST_ALL_CT(tb_host, host_addr_t, host_data_t, host_strb_t) + + logic clk; + logic rst_n; + logic start; + logic request_valid; + tb_host_cmd_t request; + logic active; + logic completed; + tb_host_cmd_t command; + logic command_valid; + logic command_ready; + tb_host_w_t host_w; + logic host_w_valid; + logic host_w_ready; + tb_host_r_t host_r; + logic host_r_valid; + logic host_r_ready; + tb_host_wrsp_t host_wrsp; + logic host_wrsp_valid; + logic host_wrsp_ready; + tb_host_r_t read; + logic read_valid; + logic read_ready; + tb_host_w_t write; + logic write_valid; + logic write_ready; + logic write_rsp_error; + logic write_rsp_valid; + logic write_rsp_ready; + + hyperbus_atomic_handler #( + .HostAddrWidth ( HostAddrWidth ), + .HostDataWidth ( HostDataWidth ), + .host_cmd_t ( tb_host_cmd_t ), + .host_w_t ( tb_host_w_t ), + .host_r_t ( tb_host_r_t ), + .host_wrsp_t ( tb_host_wrsp_t ) + ) i_dut ( + .clk_i ( clk ), + .rst_ni ( rst_n ), + .start_i ( start ), + .request_valid_i ( request_valid ), + .request_i ( request ), + .active_o ( active ), + .completed_o ( completed ), + .command_o ( command ), + .command_valid_o ( command_valid ), + .command_ready_i ( command_ready ), + .host_w_i ( host_w ), + .host_w_valid_i ( host_w_valid ), + .host_w_ready_o ( host_w_ready ), + .host_r_o ( host_r ), + .host_r_valid_o ( host_r_valid ), + .host_r_ready_i ( host_r_ready ), + .host_wrsp_o ( host_wrsp ), + .host_wrsp_valid_o ( host_wrsp_valid ), + .host_wrsp_ready_i ( host_wrsp_ready ), + .read_i ( read ), + .read_valid_i ( read_valid ), + .read_ready_o ( read_ready ), + .write_o ( write ), + .write_valid_o ( write_valid ), + .write_ready_i ( write_ready ), + .write_rsp_error_i ( write_rsp_error ), + .write_rsp_valid_i ( write_rsp_valid ), + .write_rsp_ready_o ( write_rsp_ready ) + ); + + initial begin + clk = 1'b0; + forever #5ns clk = ~clk; + end + + task automatic check_operation( + input hyperbus_pkg::hyper_atomic_op_e operation, + input logic [31:0] old_value, + input logic [31:0] operand, + input logic [31:0] expected, + input logic [31:0] swap_value = '0, + input logic compare = 1'b0, + input logic write_error = 1'b0 + ); + localparam logic [31:0] UpperWord = 32'ha5a5_5a5a; + host_data_t old_data; + host_data_t operand_data; + host_data_t expected_data; + + old_data = {UpperWord, old_value}; + operand_data = compare ? {swap_value, operand} : host_data_t'(operand); + expected_data = {UpperWord, expected}; + + @(negedge clk); + request = '0; + request.write = 1'b1; + request.addr = 32'h8000_0100; + request.beats = 1; + request.size = compare ? 3'd3 : 3'd2; + request.burst = hyperbus_pkg::HyperBurstIncr; + request.atomic_op = operation; + request.atomic_return = 1'b1; + request.ordered = 1'b1; + request_valid = 1'b1; + start = 1'b1; + + @(negedge clk); + request_valid = 1'b0; + start = 1'b0; + + wait (host_w_ready); + @(negedge clk); + host_w = '{data: operand_data, strb: '1, last: 1'b1}; + host_w_valid = 1'b1; + @(negedge clk); + host_w_valid = 1'b0; + + wait (command_valid); + #1ps; + if (command.write || (command.size != 2)) begin + $error("Atomic %s issued an invalid read command", operation.name()); + end + + wait (read_ready); + @(negedge clk); + read = '0; + read.data = old_data; + read.resp = hyperbus_pkg::HyperRespOkay; + read.last = 1'b1; + read_valid = 1'b1; + @(negedge clk); + read_valid = 1'b0; + + wait (command_valid); + #1ps; + if (!command.write || (command.size != 2)) begin + $error("Atomic %s issued an invalid write command", operation.name()); + end + + wait (write_valid); + #1ps; + if ((write.data != expected_data) || (write.strb != 8'h0f) || !write.last) begin + $error("Atomic %s produced data 0x%016h strb 0x%02h, expected 0x%016h/0x0f", + operation.name(), write.data, write.strb, expected_data); + end + + wait (write_rsp_ready); + @(negedge clk); + write_rsp_error = write_error; + write_rsp_valid = 1'b1; + @(negedge clk); + write_rsp_error = 1'b0; + write_rsp_valid = 1'b0; + + wait (host_r_valid && host_wrsp_valid); + #1ps; + if ((host_r.data != old_data) || !host_r.last || + (host_r.resp != (write_error ? hyperbus_pkg::HyperRespAccessError : + hyperbus_pkg::HyperRespOkay)) || + (host_wrsp.resp != (write_error ? hyperbus_pkg::HyperRespAccessError : + hyperbus_pkg::HyperRespOkay)) || + (host_r.atomic_ok == write_error) || (host_wrsp.atomic_ok == write_error)) begin + $error("Atomic %s returned an invalid host response", operation.name()); + end + + // Accept the read response first and ensure the write response remains stable. + @(negedge clk); + host_r_ready = 1'b1; + #1ps; + if (completed) begin + $error("Atomic %s completed before both host responses were accepted", + operation.name()); + end + @(negedge clk); + host_r_ready = 1'b0; + #1ps; + if (host_r_valid || !host_wrsp_valid || !active || completed) begin + $error("Atomic %s did not hold its pending write response", operation.name()); + end + + host_wrsp_ready = 1'b1; + #1ps; + if (!completed) begin + $error("Atomic %s did not complete with its final response", operation.name()); + end + @(negedge clk); + host_wrsp_ready = 1'b0; + wait (!active); + endtask + + task automatic check_read_error; + @(negedge clk); + request = '0; + request.write = 1'b1; + request.addr = 32'h8000_0200; + request.beats = 1; + request.size = 3'd2; + request.burst = hyperbus_pkg::HyperBurstIncr; + request.atomic_op = hyperbus_pkg::HyperAtomicAdd; + request.atomic_return = 1'b1; + request.ordered = 1'b1; + request_valid = 1'b1; + start = 1'b1; + + @(negedge clk); + request_valid = 1'b0; + start = 1'b0; + wait (host_w_ready); + @(negedge clk); + host_w = '{data: 64'h1, strb: '1, last: 1'b1}; + host_w_valid = 1'b1; + @(negedge clk); + host_w_valid = 1'b0; + + wait (read_ready); + @(negedge clk); + read = '0; + read.resp = hyperbus_pkg::HyperRespAccessError; + read.last = 1'b1; + read_valid = 1'b1; + @(negedge clk); + read_valid = 1'b0; + + wait (host_r_valid && host_wrsp_valid); + #1ps; + if (command_valid || write_valid || + (host_r.resp != hyperbus_pkg::HyperRespAccessError) || + (host_wrsp.resp != hyperbus_pkg::HyperRespAccessError) || + host_r.atomic_ok || host_wrsp.atomic_ok) begin + $error("Atomic backend read error was not returned without a write"); + end + + host_r_ready = 1'b1; + host_wrsp_ready = 1'b1; + @(negedge clk); + host_r_ready = 1'b0; + host_wrsp_ready = 1'b0; + wait (!active); + endtask + + task automatic check_invalid_operand_drain; + @(negedge clk); + request = '0; + request.write = 1'b1; + request.addr = 32'h8000_0300; + request.beats = 2; + request.size = 3'd2; + request.burst = hyperbus_pkg::HyperBurstIncr; + request.atomic_op = hyperbus_pkg::HyperAtomicAdd; + request.atomic_return = 1'b1; + request_valid = 1'b0; + start = 1'b1; + + @(negedge clk); + start = 1'b0; + wait (host_w_ready); + @(negedge clk); + host_w = '{data: 64'hdead_beef, strb: '1, last: 1'b0}; + host_w_valid = 1'b1; + @(negedge clk); + host_w_valid = 1'b0; + #1ps; + if (!active || command_valid || host_r_valid || host_wrsp_valid) begin + $error("Rejected atomic did not continue draining its operand stream"); + end + + @(negedge clk); + host_w = '{data: 64'hfeed_cafe, strb: '1, last: 1'b1}; + host_w_valid = 1'b1; + @(negedge clk); + host_w_valid = 1'b0; + wait (host_r_valid && host_wrsp_valid); + #1ps; + if (command_valid || write_valid || + (host_r.resp != hyperbus_pkg::HyperRespAtomicError) || + (host_wrsp.resp != hyperbus_pkg::HyperRespAtomicError)) begin + $error("Rejected atomic operand drain returned an invalid response"); + end + + host_r_ready = 1'b1; + host_wrsp_ready = 1'b1; + @(negedge clk); + host_r_ready = 1'b0; + host_wrsp_ready = 1'b0; + wait (!active); + endtask + + initial begin + rst_n = 1'b0; + start = 1'b0; + request_valid = 1'b0; + request = '0; + command_ready = 1'b1; + host_w = '0; + host_w_valid = 1'b0; + host_r_ready = 1'b0; + host_wrsp_ready = 1'b0; + read = '0; + read_valid = 1'b0; + write_ready = 1'b1; + write_rsp_error = 1'b0; + write_rsp_valid = 1'b0; + + repeat (4) @(posedge clk); + rst_n = 1'b1; + repeat (2) @(posedge clk); + + check_operation(hyperbus_pkg::HyperAtomicSwap, + 32'h1234_5678, 32'h89ab_cdef, 32'h89ab_cdef); + check_operation(hyperbus_pkg::HyperAtomicCompare, + 32'h1234_5678, 32'h1234_5678, 32'hfeed_cafe, 32'hfeed_cafe, 1'b1); + check_operation(hyperbus_pkg::HyperAtomicCompare, + 32'h1234_5678, 32'h8765_4321, 32'h1234_5678, 32'hfeed_cafe, 1'b1); + check_operation(hyperbus_pkg::HyperAtomicAdd, + 32'h1234_5678, 32'h0102_0304, 32'h1336_597c); + check_operation(hyperbus_pkg::HyperAtomicAnd, + 32'hf0f0_55aa, 32'h0ff0_f00f, 32'h00f0_500a); + check_operation(hyperbus_pkg::HyperAtomicClear, + 32'hffff_55aa, 32'h0ff0_f00f, 32'hf00f_05a0); + check_operation(hyperbus_pkg::HyperAtomicXor, + 32'hf0f0_55aa, 32'h0ff0_f00f, 32'hff00_a5a5); + check_operation(hyperbus_pkg::HyperAtomicSet, + 32'hf0f0_55aa, 32'h0ff0_f00f, 32'hfff0_f5af); + check_operation(hyperbus_pkg::HyperAtomicSignedMax, + 32'hffff_fffb, 32'h0000_0003, 32'h0000_0003); + check_operation(hyperbus_pkg::HyperAtomicSignedMin, + 32'h0000_0003, 32'hffff_fffb, 32'hffff_fffb); + check_operation(hyperbus_pkg::HyperAtomicUnsignedMax, + 32'h0000_0002, 32'hffff_fff0, 32'hffff_fff0); + check_operation(hyperbus_pkg::HyperAtomicUnsignedMin, + 32'h0000_0002, 32'hffff_fff0, 32'h0000_0002); + check_operation(hyperbus_pkg::HyperAtomicAdd, + 32'h1234_5678, 32'h0102_0304, 32'h1336_597c, + '0, 1'b0, 1'b1); + check_read_error(); + check_invalid_operand_drain(); + + $display("Atomic handler operation and protocol tests passed"); + $finish; + end + + initial begin + #100us; + $fatal(1, "Atomic handler test timed out"); + end + +endmodule : hyperbus_atomic_handler_tb From 6c684c2b1053721f3c47b40de9682a456994f939 Mon Sep 17 00:00:00 2001 From: Philippe Sauter Date: Tue, 11 Aug 2026 15:47:21 +0200 Subject: [PATCH 4/5] Test AXI drain with write data before address --- Bender.yml | 1 + scripts/start.tcl | 1 + test/axi_pre_aw_drain_tb.sv | 180 ++++++++++++++++++++++++++++++++++++ test/fixture_hyperbus.sv | 13 ++- test/hyperbus_test_dut.sv | 28 +++--- 5 files changed, 206 insertions(+), 17 deletions(-) create mode 100644 test/axi_pre_aw_drain_tb.sv diff --git a/Bender.yml b/Bender.yml index df9a1bc..54a50e2 100644 --- a/Bender.yml +++ b/Bender.yml @@ -79,3 +79,4 @@ sources: - test/axi_hyper_tb.sv - test/hyperbus_atomic_handler_tb.sv - test/hyperbus_cfg_regs_tb.sv + - test/axi_pre_aw_drain_tb.sv diff --git a/scripts/start.tcl b/scripts/start.tcl index b192465..7d64da1 100644 --- a/scripts/start.tcl +++ b/scripts/start.tcl @@ -43,5 +43,6 @@ run_test axi_hyper_tb_asynchronous sim_run_asynchronous.wlf run_test axi_hyper_tb_synchronous_one_phy sim_run_synchronous_one_phy.wlf run_test hyperbus_atomic_handler_tb sim_run_atomic_handler.wlf run_test hyperbus_cfg_regs_tb sim_run_cfg_regs.wlf +run_test axi_pre_aw_drain_tb sim_run_pre_aw_drain.wlf quit -code $regression_failed -f diff --git a/test/axi_pre_aw_drain_tb.sv b/test/axi_pre_aw_drain_tb.sv new file mode 100644 index 0000000..74f3f88 --- /dev/null +++ b/test/axi_pre_aw_drain_tb.sv @@ -0,0 +1,180 @@ +// Copyright 2026 ETH Zurich and University of Bologna. +// Solderpad Hardware License, Version 0.51, see LICENSE for details. +// SPDX-License-Identifier: SHL-0.51 + +`timescale 1 ns / 1 ps + +// Directed regression for AXI W-before-AW ordering across a configuration +// drain. The fixture is connected directly to the DUT so the AXI mux used by +// the larger randomized test cannot impose an AW-before-W ordering. +module axi_pre_aw_drain_tb; + + localparam int unsigned HostWriteBufferBytes = 128; + localparam int unsigned AxiDataBytes = 8; + localparam int unsigned NumTransactions = HostWriteBufferBytes / AxiDataBytes; + localparam int unsigned PartialBurstBeats = 32; + localparam logic [31:0] BaseAddress = 32'h0000_6800; + localparam logic [31:0] PartialBaseAddress = 32'h0000_7000; + // A regular stable-map configuration write starts the automatic drain/apply + // barrier in point5. The staged COMMAND/STATUS interface is not enabled. + localparam logic [31:0] DrainConfigAddr = 32'h410; + localparam logic [31:0] DrainConfigValue = 32'd350; + + bit complete_config_done; + bit partial_w_fifo_full; + bit partial_config_busy; + bit partial_config_done; + bit partial_w_done; + + fixture_hyperbus #( + .NumConnectedChips ( 2 ), + .NumPhys ( 2 ), + .DutVariant ( 1 ), + .HostWriteBufferBytes( HostWriteBufferBytes ), + .SysClkPeriod ( 10ns ), + .AnnotateSdf ( 1'b1 ) + ) fix (); + + initial begin + fix.reset_end(); + + // Fill all 16 entries of the 128-byte host W FIFO without presenting AW. + for (int unsigned transaction = 0; transaction < NumTransactions; transaction++) begin + fix.w_beat.w_data = 64'hd15e_a5e5_0000_0000 ^ + (BaseAddress + transaction * AxiDataBytes); + fix.w_beat.w_strb = '1; + fix.w_beat.w_last = 1'b1; + fix.axi_master_drv.send_w(fix.w_beat); + end + + // Start the automatic configuration barrier, then verify it remains busy until the + // matching AW requests have made the buffered W beats actionable. + complete_config_done = 1'b0; + fork + begin : config_complete_w_bursts + logic config_error; + fix.i_rmaster.send_write(DrainConfigAddr, DrainConfigValue, '1, config_error); + if (config_error) $fatal(1, "drain configuration write failed"); + complete_config_done = 1'b1; + end + join_none + repeat (4) @(posedge fix.sys_clk); + if (complete_config_done) begin + $fatal(1, "drain configuration write completed before buffered W bursts drained"); + end + + // Pair each AW with its B response to avoid filling the serializer's + // bounded write-ID queue while the drain is still active. + for (int unsigned transaction = 0; transaction < NumTransactions; transaction++) begin + fix.aw_beat.ax_addr = BaseAddress + transaction * AxiDataBytes; + fix.aw_beat.ax_id = transaction + 1; + fix.aw_beat.ax_len = '0; + fix.aw_beat.ax_size = 3; + fix.aw_beat.ax_burst = axi_pkg::BURST_INCR; + fix.aw_beat.ax_atop = '0; + fix.axi_master_drv.send_aw(fix.aw_beat); + fix.axi_master_drv.recv_b(fix.b_beat); + if ((fix.b_beat.b_resp != axi_pkg::RESP_OKAY) || + (fix.b_beat.b_id != transaction + 1)) begin + $fatal(1, "write %0d returned id=%0d resp=%0d", transaction, + fix.b_beat.b_id, fix.b_beat.b_resp); + end + end + + wait (complete_config_done); + + for (int unsigned transaction = 0; transaction < NumTransactions; transaction++) begin + fix.ar_beat.ax_addr = BaseAddress + transaction * AxiDataBytes; + fix.ar_beat.ax_id = transaction + 1; + fix.ar_beat.ax_len = '0; + fix.ar_beat.ax_size = 3; + fix.ar_beat.ax_burst = axi_pkg::BURST_INCR; + fix.axi_master_drv.send_ar(fix.ar_beat); + fix.axi_master_drv.recv_r(fix.r_beat); + if ((fix.r_beat.r_resp != axi_pkg::RESP_OKAY) || !fix.r_beat.r_last || + (fix.r_beat.r_data != (64'hd15e_a5e5_0000_0000 ^ + (BaseAddress + transaction * AxiDataBytes)))) begin + $fatal(1, "read %0d returned data=0x%016x last=%0b resp=%0d", transaction, + fix.r_beat.r_data, fix.r_beat.r_last, fix.r_beat.r_resp); + end + end + + // Start a burst longer than the host W FIFO before presenting its AW. The + // sender must stop at the 16-beat FIFO capacity while the drain barrier is + // entered, then resume only after the matching AW is accepted. + partial_w_fifo_full = 1'b0; + partial_config_busy = 1'b0; + partial_config_done = 1'b0; + partial_w_done = 1'b0; + fork + begin : send_partial_w_burst + for (int unsigned beat = 0; beat < PartialBurstBeats; beat++) begin + fix.w_beat.w_data = 64'hd15e_a5e5_0000_0000 ^ + (PartialBaseAddress + beat * AxiDataBytes); + fix.w_beat.w_strb = '1; + fix.w_beat.w_last = (beat == PartialBurstBeats - 1); + fix.axi_master_drv.send_w(fix.w_beat); + if (beat + 1 == NumTransactions) partial_w_fifo_full = 1'b1; + end + partial_w_done = 1'b1; + end + begin : config_partial_w_burst + logic partial_error; + + wait (partial_w_fifo_full); + repeat (2) @(posedge fix.sys_clk); + if (fix.axi_dv.w_ready !== 1'b0) begin + $fatal(1, "partial W burst did not reach FIFO backpressure"); + end + + partial_config_busy = 1'b1; + fix.i_rmaster.send_write(DrainConfigAddr, DrainConfigValue, '1, partial_error); + if (partial_error) $fatal(1, "partial drain configuration write failed"); + partial_config_done = 1'b1; + end + join_none + + wait (partial_config_busy); + fix.aw_beat.ax_addr = PartialBaseAddress; + fix.aw_beat.ax_id = 6'd32; + fix.aw_beat.ax_len = PartialBurstBeats - 1; + fix.aw_beat.ax_size = 3; + fix.aw_beat.ax_burst = axi_pkg::BURST_INCR; + fix.aw_beat.ax_atop = '0; + fix.axi_master_drv.send_aw(fix.aw_beat); + fix.axi_master_drv.recv_b(fix.b_beat); + if ((fix.b_beat.b_resp != axi_pkg::RESP_OKAY) || (fix.b_beat.b_id != 6'd32)) begin + $fatal(1, "partial write returned id=%0d resp=%0d", fix.b_beat.b_id, + fix.b_beat.b_resp); + end + wait (partial_w_done); + wait (partial_config_done); + + for (int unsigned beat = 0; beat < PartialBurstBeats; beat++) begin + fix.ar_beat.ax_addr = PartialBaseAddress + beat * AxiDataBytes; + fix.ar_beat.ax_id = beat + 1; + fix.ar_beat.ax_len = '0; + fix.ar_beat.ax_size = 3; + fix.ar_beat.ax_burst = axi_pkg::BURST_INCR; + fix.axi_master_drv.send_ar(fix.ar_beat); + fix.axi_master_drv.recv_r(fix.r_beat); + if ((fix.r_beat.r_resp != axi_pkg::RESP_OKAY) || !fix.r_beat.r_last || + (fix.r_beat.r_data != (64'hd15e_a5e5_0000_0000 ^ + (PartialBaseAddress + beat * AxiDataBytes)))) begin + $fatal(1, "partial read %0d returned data=0x%016x last=%0b resp=%0d", beat, + fix.r_beat.r_data, fix.r_beat.r_last, fix.r_beat.r_resp); + end + end + + fix.eos = 1'b1; + #100ns; + $display("Pre-AW W drain regressions passed"); + $finish; + end + + initial begin + #2ms; + $fatal(1, "Pre-AW W drain regression timed out"); + end + +endmodule diff --git a/test/fixture_hyperbus.sv b/test/fixture_hyperbus.sv index 9c200ae..8662102 100644 --- a/test/fixture_hyperbus.sv +++ b/test/fixture_hyperbus.sv @@ -11,16 +11,18 @@ `include "register_interface/typedef.svh" module fixture_hyperbus #( - parameter int unsigned NumConnectedChips = 2, - parameter int unsigned NumPhys = 2, - parameter int unsigned DutVariant = 0, - parameter bit AnnotateSdf = 1'b1 + parameter int unsigned NumConnectedChips = 2, + parameter int unsigned NumPhys = 2, + parameter int unsigned DutVariant = 0, + parameter int unsigned HostWriteBufferBytes = 64, + parameter time SysClkPeriod = 4ns, + parameter bit AnnotateSdf = 1'b1 ); int unsigned k, j; - localparam time SYS_TCK = 4ns; + localparam time SYS_TCK = SysClkPeriod; localparam time SYS_TA = 2ns; localparam time SYS_TT = SYS_TCK - 1ns; @@ -171,6 +173,7 @@ module fixture_hyperbus #( hyperbus_test_dut #( .DutVariant ( DutVariant ), .NumPhys ( NumPhys ), + .HostWriteBufferBytes ( HostWriteBufferBytes ), .AxiAddrWidth ( AxiAw ), .AxiDataWidth ( AxiDw ), .AxiIdWidth ( AxiIw ), diff --git a/test/hyperbus_test_dut.sv b/test/hyperbus_test_dut.sv index 10d958b..d897c81 100644 --- a/test/hyperbus_test_dut.sv +++ b/test/hyperbus_test_dut.sv @@ -3,18 +3,19 @@ // SPDX-License-Identifier: SHL-0.51 module hyperbus_test_dut #( - parameter int unsigned DutVariant = 0, - parameter int unsigned NumPhys = 2, - parameter int unsigned AxiAddrWidth = -1, - parameter int unsigned AxiDataWidth = -1, - parameter int unsigned AxiIdWidth = -1, - parameter int unsigned AxiUserWidth = -1, - parameter type axi_req_t = logic, - parameter type axi_rsp_t = logic, - parameter int unsigned RegDataWidth = -1, - parameter type reg_req_t = logic, - parameter type reg_rsp_t = logic, - parameter type axi_rule_t = logic + parameter int unsigned DutVariant = 0, + parameter int unsigned NumPhys = 2, + parameter int unsigned AxiAddrWidth = -1, + parameter int unsigned AxiDataWidth = -1, + parameter int unsigned AxiIdWidth = -1, + parameter int unsigned AxiUserWidth = -1, + parameter type axi_req_t = logic, + parameter type axi_rsp_t = logic, + parameter int unsigned RegDataWidth = -1, + parameter int unsigned HostWriteBufferBytes = 64, + parameter type reg_req_t = logic, + parameter type reg_rsp_t = logic, + parameter type axi_rule_t = logic ) ( input logic clk_sys_i, input logic rst_sys_ni, @@ -50,6 +51,7 @@ module hyperbus_test_dut #( if (DutVariant == VariantIsochronous) begin : gen_isochronous hyperbus_isochronous #( .NumPhys ( NumPhys ), + .HostWriteBufferBytes ( HostWriteBufferBytes ), .AxiAddrWidth ( AxiAddrWidth ), .AxiDataWidth ( AxiDataWidth ), .AxiIdWidth ( AxiIdWidth ), @@ -85,6 +87,7 @@ module hyperbus_test_dut #( end else if (DutVariant == VariantSynchronous) begin : gen_synchronous hyperbus_synchronous #( .NumPhys ( NumPhys ), + .HostWriteBufferBytes ( HostWriteBufferBytes ), .AxiAddrWidth ( AxiAddrWidth ), .AxiDataWidth ( AxiDataWidth ), .AxiIdWidth ( AxiIdWidth ), @@ -120,6 +123,7 @@ module hyperbus_test_dut #( end else if (DutVariant == VariantAsynchronous) begin : gen_asynchronous hyperbus_asynchronous #( .NumPhys ( NumPhys ), + .HostWriteBufferBytes ( HostWriteBufferBytes ), .AxiAddrWidth ( AxiAddrWidth ), .AxiDataWidth ( AxiDataWidth ), .AxiIdWidth ( AxiIdWidth ), From cedccdbac481a18ec236e1bfb25da3b752e1fd6e Mon Sep 17 00:00:00 2001 From: Philippe Sauter Date: Wed, 12 Aug 2026 01:11:54 +0200 Subject: [PATCH 5/5] Use hierarchical configuration addresses in AXI regression Update directed AXI tests to use the hierarchical 4 KiB register map and exercise automatic configuration drain and apply behavior. --- test/axi_hyper_tb.sv | 79 +++++++++++++++++++++++--------------------- 1 file changed, 41 insertions(+), 38 deletions(-) diff --git a/test/axi_hyper_tb.sv b/test/axi_hyper_tb.sv index d300ed0..263eb71 100644 --- a/test/axi_hyper_tb.sv +++ b/test/axi_hyper_tb.sv @@ -72,7 +72,8 @@ module axi_hyper_tb typedef axi_pkg::xbar_rule_32_t rule_t; localparam int unsigned RegBusDW = 32; - localparam int unsigned RegBusAW = 8; + // The stable register map is a hierarchical 4 KiB window. + localparam int unsigned RegBusAW = 12; localparam int unsigned TbDramDataWidth = 8; localparam int unsigned TbDramLenWidth = 32'h80000; @@ -739,12 +740,12 @@ module axi_hyper_tb localparam axi_addr_t BarrierAddr = axi_addr_t'(32'h8000_6000); logic reg_error; time write_done_time; - time flush_done_time; + time cfg_done_time; write_done_time = 0; - flush_done_time = 0; + cfg_done_time = 0; $display("==========================="); - $display("= Config flush barrier ="); + $display("= Config drain/apply ="); $display("==========================="); fork @@ -754,17 +755,19 @@ module axi_hyper_tb end begin repeat (32) @(posedge clk); - reg_drv.send_write(32'h50, '0, '1, reg_error); - flush_done_time = $time; + // Any accepted configuration write triggers the automatic drain/apply + // sequence; COMMAND remains inert until staged apply is implemented. + reg_drv.send_write(32'h410, 32'd350, '1, reg_error); + cfg_done_time = $time; if (reg_error != 1'b0) begin - $error("[CFG-BARRIER] Flush register write returned an error"); + $error("[CFG-DRAIN] Configuration write returned an error"); end end join - if ((write_done_time == 0) || (flush_done_time < write_done_time)) begin - $error("[CFG-BARRIER] Flush completed at %0t before AXI write completed at %0t", - flush_done_time, write_done_time); + if ((write_done_time == 0) || (cfg_done_time < write_done_time)) begin + $error("[CFG-DRAIN] Configuration completed at %0t before AXI write completed at %0t", + cfg_done_time, write_done_time); end endtask @@ -810,13 +813,13 @@ module axi_hyper_tb $error("[DECODE] Invalid write returned response %0d", b.b_resp); end - reg_drv.send_read(32'h54, status, reg_error); + reg_drv.send_read(32'h010, status, reg_error); if ((reg_error != 1'b0) || !status[0]) begin $error("[DECODE] Sticky decode-error status was not set"); end - reg_drv.send_write(32'h54, 32'h1, '1, reg_error); + reg_drv.send_write(32'h010, 32'h1, '1, reg_error); if (reg_error != 1'b0) $error("unexpected error"); - reg_drv.send_read(32'h54, status, reg_error); + reg_drv.send_read(32'h010, status, reg_error); if ((reg_error != 1'b0) || status[0]) begin $error("[DECODE] Sticky decode-error status did not clear"); end @@ -839,9 +842,9 @@ module axi_hyper_tb $display("= Cross-chip burst ="); $display("==========================="); - reg_drv.send_write(32'h34, Boundary, '1, reg_error); + reg_drv.send_write(32'h404, Boundary, '1, reg_error); if (reg_error != 1'b0) $error("unexpected error"); - reg_drv.send_write(32'h38, Boundary, '1, reg_error); + reg_drv.send_write(32'h440, Boundary, '1, reg_error); if (reg_error != 1'b0) $error("unexpected error"); segment_start_snapshot = segment_start_count; @@ -852,9 +855,9 @@ module axi_hyper_tb segment_start_count - segment_start_snapshot); end - reg_drv.send_write(32'h38, 32'h8100_0000, '1, reg_error); + reg_drv.send_write(32'h440, 32'h8100_0000, '1, reg_error); if (reg_error != 1'b0) $error("unexpected error"); - reg_drv.send_write(32'h34, 32'h8100_0000, '1, reg_error); + reg_drv.send_write(32'h404, 32'h8100_0000, '1, reg_error); if (reg_error != 1'b0) $error("unexpected error"); endtask @@ -899,7 +902,7 @@ module axi_hyper_tb $display("==========================="); // A zero bound extends the final rule through the end of the address space. - reg_drv.send_write(32'h3c, '0, '1, reg_error); + reg_drv.send_write(32'h444, '0, '1, reg_error); if (reg_error != 1'b0) $error("unexpected error"); axi_write_subword(axi_drv, ValidAddr, TestData, 3); axi_check_subword(axi_drv, ValidAddr, TestData, 3); @@ -920,7 +923,7 @@ module axi_hyper_tb end end - reg_drv.send_write(32'h3c, 32'h8200_0000, '1, reg_error); + reg_drv.send_write(32'h444, 32'h8200_0000, '1, reg_error); if (reg_error != 1'b0) $error("unexpected error"); endtask @@ -983,11 +986,11 @@ module axi_hyper_tb $error("[ATOMIC] Unsupported operation returned rresp=%0d bresp=%0d", r.r_resp, b.b_resp); end - reg_drv.send_read(32'h54, status, reg_error); + reg_drv.send_read(32'h010, status, reg_error); if ((reg_error != 1'b0) || !status[0]) begin $error("[ATOMIC] Unsupported operation did not set sticky error status"); end - reg_drv.send_write(32'h54, 32'h1, '1, reg_error); + reg_drv.send_write(32'h010, 32'h1, '1, reg_error); if (reg_error != 1'b0) $error("unexpected error"); axi_check_subword(axi_drv, AtomicAddr, InitialValue + Addend, 2); @@ -1011,11 +1014,11 @@ module axi_hyper_tb $error("[ATOMIC] Multi-beat operation returned rresp=%0d bresp=%0d", r.r_resp, b.b_resp); end - reg_drv.send_read(32'h54, status, reg_error); + reg_drv.send_read(32'h010, status, reg_error); if ((reg_error != 1'b0) || !status[0]) begin $error("[ATOMIC] Malformed operation did not set sticky error status"); end - reg_drv.send_write(32'h54, 32'h1, '1, reg_error); + reg_drv.send_write(32'h010, 32'h1, '1, reg_error); if (reg_error != 1'b0) $error("unexpected error"); axi_write_subword(axi_drv, AtomicAddr, 32'h89ab_cdef, 2); axi_check_subword(axi_drv, AtomicAddr, 32'h89ab_cdef, 2); @@ -1074,7 +1077,7 @@ module axi_hyper_tb end // A zero-ended final rule must contain ordinary atomic accesses. - reg_drv.send_write(32'h3c, '0, '1, reg_error); + reg_drv.send_write(32'h444, '0, '1, reg_error); if (reg_error != 1'b0) $error("unexpected error"); axi_write_subword(axi_drv, ZeroEndAddr, ZeroEndInitial, 2); ax.ax_addr = ZeroEndAddr; @@ -1093,7 +1096,7 @@ module axi_hyper_tb r.r_data[31:0], r.r_resp, b.b_resp); end axi_check_subword(axi_drv, ZeroEndAddr, ZeroEndInitial + ZeroEndAddend, 2); - reg_drv.send_write(32'h3c, 32'h8200_0000, '1, reg_error); + reg_drv.send_write(32'h444, 32'h8200_0000, '1, reg_error); if (reg_error != 1'b0) $error("unexpected error"); endtask @@ -1127,25 +1130,25 @@ module axi_hyper_tb // Map each chip to a distinct 16 MiB host-address window. if (NumConnectedChips > 1) begin - reg_master.send_write(32'h3c, 32'h8200_0000, '1, s_reg_error); + reg_master.send_write(32'h444, 32'h8200_0000, '1, s_reg_error); if (s_reg_error != 1'b0) $error("unexpected error"); - reg_master.send_write(32'h38, 32'h8100_0000, '1, s_reg_error); + reg_master.send_write(32'h440, 32'h8100_0000, '1, s_reg_error); if (s_reg_error != 1'b0) $error("unexpected error"); end - reg_master.send_write(32'h34, 32'h8100_0000, '1, s_reg_error); + reg_master.send_write(32'h404, 32'h8100_0000, '1, s_reg_error); if (s_reg_error != 1'b0) $error("unexpected error"); - reg_master.send_write(32'h30, 32'h8000_0000, '1, s_reg_error); + reg_master.send_write(32'h400, 32'h8000_0000, '1, s_reg_error); if (s_reg_error != 1'b0) $error("unexpected error"); - reg_master.send_write(32'h4 << 2, TbRxDelayLineTaps, '1, s_reg_error); + reg_master.send_write(32'h418, TbRxDelayLineTaps, '1, s_reg_error); if (s_reg_error != 1'b0) $error("unexpected error"); - reg_master.send_write(32'h5 << 2, TbTxDelayLineTaps, '1, s_reg_error); + reg_master.send_write(32'h300, TbTxDelayLineTaps, '1, s_reg_error); if (s_reg_error != 1'b0) $error("unexpected error"); if (TbDutVariant == 0) begin - reg_master.send_read(32'h78, reg_read, s_reg_error); + reg_master.send_read(32'h200, reg_read, s_reg_error); if ((s_reg_error != 1'b0) || (reg_read != 8)) $error("unexpected divider reset value"); - reg_master.send_write(32'h78, 8'd2, '1, s_reg_error); + reg_master.send_write(32'h200, 8'd2, '1, s_reg_error); if (s_reg_error != 1'b0) $error("unexpected error"); end @@ -1153,18 +1156,18 @@ module axi_hyper_tb if (TbDutVariant == 0) begin // The configuration barrier completes only after the divided clock resumes. - reg_master.send_write(32'h78, 8'd4, '1, s_reg_error); + reg_master.send_write(32'h200, 8'd4, '1, s_reg_error); if (s_reg_error != 1'b0) $error("unexpected error"); - reg_master.send_read(32'h78, reg_read, s_reg_error); + reg_master.send_read(32'h200, reg_read, s_reg_error); if ((s_reg_error != 1'b0) || (reg_read != 4)) $error("divider update failed"); divider_cycle_snapshot = cycle_count; axi_write_slow(axi_ctrl_mst, 32'h8000_7000, 4, 0); div4_write_cycles = cycle_count - divider_cycle_snapshot; - reg_master.send_write(32'h78, 8'd2, '1, s_reg_error); + reg_master.send_write(32'h200, 8'd2, '1, s_reg_error); if (s_reg_error != 1'b0) $error("unexpected error"); - reg_master.send_read(32'h78, reg_read, s_reg_error); + reg_master.send_read(32'h200, reg_read, s_reg_error); if ((s_reg_error != 1'b0) || (reg_read != 2)) $error("divider restore failed"); divider_cycle_snapshot = cycle_count; @@ -1250,7 +1253,7 @@ module axi_hyper_tb $display("= Use only phy 0 ="); $display("==========================="); - reg_master.send_write(32'h20,1'b0,'1,s_reg_error); + reg_master.send_write(32'h100,1'b0,'1,s_reg_error); if (s_reg_error != 1'b0) $error("unexpected error"); axi_rand_mst.reset();