diff --git a/docs/regs/hyperbus_cfg_regs.md b/docs/regs/hyperbus_cfg_regs.md index 9458acf..a7c120e 100644 --- a/docs/regs/hyperbus_cfg_regs.md +++ b/docs/regs/hyperbus_cfg_regs.md @@ -334,7 +334,7 @@ Don't override. Generated from: hyperbus_cfg_regs - Base Offset: 0xC - Size: 0x4 -

Chip latency configuration. t_latency_access must be 3 through 15; rwds_sample_delay must be at most 15 and is active when capability.rwds_sample_timing is set. Chip 0 is shared when capability.per_chip_cfg is clear.

+

Chip latency configuration. t_latency_access must be 3 through 15; rwds_sample_delay must be at most t_latency_access minus 2 and is active when capability.rwds_sample_timing is set. Chip 0 is shared when capability.per_chip_cfg is clear.

|Bits| Identifier |Access|Reset|Name| |----|---------------------|------|-----|----| @@ -440,7 +440,7 @@ Don't override. Generated from: hyperbus_cfg_regs - Base Offset: 0xC - Size: 0x4 -

Chip latency configuration. t_latency_access must be 3 through 15; rwds_sample_delay must be at most 15 and is active when capability.rwds_sample_timing is set. Chip 0 is shared when capability.per_chip_cfg is clear.

+

Chip latency configuration. t_latency_access must be 3 through 15; rwds_sample_delay must be at most t_latency_access minus 2 and is active when capability.rwds_sample_timing is set. Chip 0 is shared when capability.per_chip_cfg is clear.

|Bits| Identifier |Access|Reset|Name| |----|---------------------|------|-----|----| @@ -546,7 +546,7 @@ Don't override. Generated from: hyperbus_cfg_regs - Base Offset: 0xC - Size: 0x4 -

Chip latency configuration. t_latency_access must be 3 through 15; rwds_sample_delay must be at most 15 and is active when capability.rwds_sample_timing is set. Chip 0 is shared when capability.per_chip_cfg is clear.

+

Chip latency configuration. t_latency_access must be 3 through 15; rwds_sample_delay must be at most t_latency_access minus 2 and is active when capability.rwds_sample_timing is set. Chip 0 is shared when capability.per_chip_cfg is clear.

|Bits| Identifier |Access|Reset|Name| |----|---------------------|------|-----|----| @@ -652,7 +652,7 @@ Don't override. Generated from: hyperbus_cfg_regs - Base Offset: 0xC - Size: 0x4 -

Chip latency configuration. t_latency_access must be 3 through 15; rwds_sample_delay must be at most 15 and is active when capability.rwds_sample_timing is set. Chip 0 is shared when capability.per_chip_cfg is clear.

+

Chip latency configuration. t_latency_access must be 3 through 15; rwds_sample_delay must be at most t_latency_access minus 2 and is active when capability.rwds_sample_timing is set. Chip 0 is shared when capability.per_chip_cfg is clear.

|Bits| Identifier |Access|Reset|Name| |----|---------------------|------|-----|----| @@ -758,7 +758,7 @@ Don't override. Generated from: hyperbus_cfg_regs - Base Offset: 0xC - Size: 0x4 -

Chip latency configuration. t_latency_access must be 3 through 15; rwds_sample_delay must be at most 15 and is active when capability.rwds_sample_timing is set. Chip 0 is shared when capability.per_chip_cfg is clear.

+

Chip latency configuration. t_latency_access must be 3 through 15; rwds_sample_delay must be at most t_latency_access minus 2 and is active when capability.rwds_sample_timing is set. Chip 0 is shared when capability.per_chip_cfg is clear.

|Bits| Identifier |Access|Reset|Name| |----|---------------------|------|-----|----| @@ -864,7 +864,7 @@ Don't override. Generated from: hyperbus_cfg_regs - Base Offset: 0xC - Size: 0x4 -

Chip latency configuration. t_latency_access must be 3 through 15; rwds_sample_delay must be at most 15 and is active when capability.rwds_sample_timing is set. Chip 0 is shared when capability.per_chip_cfg is clear.

+

Chip latency configuration. t_latency_access must be 3 through 15; rwds_sample_delay must be at most t_latency_access minus 2 and is active when capability.rwds_sample_timing is set. Chip 0 is shared when capability.per_chip_cfg is clear.

|Bits| Identifier |Access|Reset|Name| |----|---------------------|------|-----|----| @@ -970,7 +970,7 @@ Don't override. Generated from: hyperbus_cfg_regs - Base Offset: 0xC - Size: 0x4 -

Chip latency configuration. t_latency_access must be 3 through 15; rwds_sample_delay must be at most 15 and is active when capability.rwds_sample_timing is set. Chip 0 is shared when capability.per_chip_cfg is clear.

+

Chip latency configuration. t_latency_access must be 3 through 15; rwds_sample_delay must be at most t_latency_access minus 2 and is active when capability.rwds_sample_timing is set. Chip 0 is shared when capability.per_chip_cfg is clear.

|Bits| Identifier |Access|Reset|Name| |----|---------------------|------|-----|----| @@ -1076,7 +1076,7 @@ Don't override. Generated from: hyperbus_cfg_regs - Base Offset: 0xC - Size: 0x4 -

Chip latency configuration. t_latency_access must be 3 through 15; rwds_sample_delay must be at most 15 and is active when capability.rwds_sample_timing is set. Chip 0 is shared when capability.per_chip_cfg is clear.

+

Chip latency configuration. t_latency_access must be 3 through 15; rwds_sample_delay must be at most t_latency_access minus 2 and is active when capability.rwds_sample_timing is set. Chip 0 is shared when capability.per_chip_cfg is clear.

|Bits| Identifier |Access|Reset|Name| |----|---------------------|------|-----|----| diff --git a/src/backend/hyperbus_backend.sv b/src/backend/hyperbus_backend.sv index ac3e4d2..4b6cd64 100644 --- a/src/backend/hyperbus_backend.sv +++ b/src/backend/hyperbus_backend.sv @@ -49,7 +49,7 @@ module hyperbus_backend #( hyperbus_pkg::phy_cfg_t cfg_q; logic cfg_apply_accepted; logic phy_busy_any; - logic clk_tx; + logic [NumPhys-1:0] clk_tx; `ASSERT_INIT(NumPhysValid, NumPhys == 1 || NumPhys == 2) `ASSERT_INIT(SyncStagesValid, SyncStages >= 2) @@ -60,16 +60,18 @@ module hyperbus_backend #( `FFLARN(cfg_q, cfg_apply_i, cfg_apply_accepted, '0, clk_i, rst_ni) - hyperbus_tx_clk_delay i_tx_clk_delay ( - .rst_ni, + for (genvar i = 0; i < NumPhys; i++) begin : gen_tx_clk_delay + hyperbus_tx_clk_delay i_tx_clk_delay ( + .rst_ni, `ifdef TARGET_XILINX - .clk_ref200_i, + .clk_ref200_i, `endif - .clk_i, - .in_i ( clk_i ), - .delay_i ( cfg_q.t_tx_clk_delay ), - .out_o ( clk_tx ) - ); + .clk_i, + .in_i ( clk_i ), + .delay_i ( cfg_q.phy[i].tx_delay ), + .out_o ( clk_tx[i] ) + ); + end ///////////////////// // Physical lanes // @@ -177,10 +179,11 @@ module hyperbus_backend #( hyperbus_phy #( .StartupCycles ( StartupCycles ), .NumPhys ( NumPhys ), - .SyncStages ( SyncStages ) + .SyncStages ( SyncStages ), + .PhyIndex ( i ) ) i_phy ( .clk_i, - .clk_tx_i ( clk_tx ), + .clk_tx_i ( clk_tx[i] ), .rst_ni, .test_mode_i, .cfg_i ( cfg_q ), @@ -218,10 +221,11 @@ module hyperbus_backend #( hyperbus_phy #( .StartupCycles ( StartupCycles ), .NumPhys ( NumPhys ), - .SyncStages ( SyncStages ) + .SyncStages ( SyncStages ), + .PhyIndex ( 0 ) ) i_phy ( .clk_i, - .clk_tx_i ( clk_tx ), + .clk_tx_i ( clk_tx[0] ), .rst_ni, .test_mode_i, .cfg_i ( cfg_q ), diff --git a/src/backend/hyperbus_phy.sv b/src/backend/hyperbus_phy.sv index 031a830..02d25d7 100644 --- a/src/backend/hyperbus_phy.sv +++ b/src/backend/hyperbus_phy.sv @@ -14,6 +14,7 @@ module hyperbus_phy import hyperbus_pkg::*; #( parameter int unsigned TimerWidth = 16, parameter int unsigned RxFifoLogDepth = 3, parameter int unsigned SyncStages = 2, + parameter int unsigned PhyIndex = 0, // Conservative startup delay: 300 us at 200 MHz. parameter int unsigned StartupCycles = 300 * 200 )( @@ -67,9 +68,9 @@ module hyperbus_phy import hyperbus_pkg::*; #( localparam int unsigned RxOutstandingLimit = (RxFifoDepth > RxFifoStopMargin) ? (RxFifoDepth - RxFifoStopMargin) : 1; - logic [1:0] words_per_beat; + `ASSERT_INIT(PhyIndexValid, PhyIndex < 2) - assign words_per_beat = (NumPhys == 2 && cfg_i.dual_phy) ? 2 : 1; + logic [1:0] words_per_beat; ////////////////////// // Persistent state // @@ -80,6 +81,22 @@ module hyperbus_phy import hyperbus_pkg::*; #( hyper_tf_t tf_d, tf_q; logic [HyperNumChips-1:0] cs_d, cs_q; logic add_latency_d, add_latency_q; + logic [HyperNumChips-1:0] cfg_select_cs; + logic [2:0] cfg_chip_idx; + chip_phy_cfg_t cfg_chip; + + // During Idle the incoming command CS is authoritative. Once accepted, + // retain the registered CS for every subsequent phase of the transfer. + assign cfg_select_cs = (state_q == Idle) ? trans_cs_i : cs_q; + onehot_to_bin #( + .ONEHOT_WIDTH ( HyperNumChips ) + ) i_cfg_chip_idx ( + .onehot ( cfg_select_cs ), + .bin ( cfg_chip_idx ) + ); + assign cfg_chip = cfg_i.chip[cfg_chip_idx]; + + assign words_per_beat = (NumPhys == 2 && cfg_i.dual_phy) ? 2 : 1; // Whether B response is pending logic b_pending_q; @@ -144,7 +161,7 @@ module hyperbus_phy import hyperbus_pkg::*; #( .tx_data_oe_i ( trx_tx_data_oe ), .tx_rwds_i ( trx_tx_rwds ), .tx_rwds_oe_i ( trx_tx_rwds_oe ), - .rx_clk_delay_i ( cfg_i.chip.t_rx_clk_delay ), + .rx_clk_delay_i ( cfg_chip.t_rx_clk_delay ), .rx_clk_set_i ( trx_rx_clk_set ), .rx_clk_reset_i ( trx_rx_clk_reset ), .rx_data_o ( trx_rx_data ), @@ -189,7 +206,7 @@ module hyperbus_phy import hyperbus_pkg::*; #( trx_tx_data = tx_data_i; trx_tx_rwds = ~tx_strb_i; tx_ready_o = 1'b1; // Memory always ready within HyperBus burst - ctl_wclk_ena = tx_valid_i; + ctl_wclk_ena = tx_valid_i; end end @@ -234,8 +251,8 @@ module hyperbus_phy import hyperbus_pkg::*; #( // Auxiliary control signals assign ctl_write_zero_lat = tf_q.address_space & tf_q.write; - // cfg_i.chip.en_latency_additional overwrites the sampled RWDS value. - assign ctl_add_latency = trx_rwds_sample | cfg_i.chip.en_latency_additional; + // The selected chip configuration overwrites the sampled RWDS value. + assign ctl_add_latency = trx_rwds_sample | cfg_chip.en_latency_additional; assign ctl_tf_burst_last = (tf_q.burst == 1) || (tf_q.burst == words_per_beat); assign ctl_tf_burst_done = (tf_q.burst == 0); @@ -288,11 +305,11 @@ module hyperbus_phy import hyperbus_pkg::*; #( cs_d = trans_cs_i; add_latency_d = 1'b0; - if(cfg_i.chip.csn_to_ck_cycles != 0) begin + if(cfg_chip.csn_to_ck_cycles != 0) begin // assert CS but delay hyper_ck to allow more time // for memory to drive RWDS (to satisfy t_DSV) state_d = DelayCK; - timer_d = cfg_i.chip.csn_to_ck_cycles -1; + timer_d = cfg_chip.csn_to_ck_cycles -1; end else begin // max throughput when memory RWDS signal arrives early state_d = SendCA; @@ -323,10 +340,10 @@ module hyperbus_phy import hyperbus_pkg::*; #( trx_rwds_sample_ena = ~ctl_write_zero_lat; if (ctl_timer_zero) begin if (ctl_write_zero_lat) begin - timer_d = cfg_i.chip.t_burst_max; + timer_d = cfg_chip.t_burst_max; state_d = Write; end else begin - timer_d = TimerWidth'(cfg_i.chip.t_latency_access); + timer_d = TimerWidth'(cfg_chip.t_latency_access); add_latency_d = ctl_add_latency; state_d = WaitLatAccess; end @@ -344,7 +361,7 @@ module hyperbus_phy import hyperbus_pkg::*; #( if (~add_latency_q) begin // Substract cycle for last CA and another for state delay if(ctl_timer_two) begin - timer_d = cfg_i.chip.t_burst_max; + timer_d = cfg_chip.t_burst_max; // Switch to write or read phase and already start // turnaround of tri-state driver (depending on latency // config and if read or write transaction). @@ -364,7 +381,7 @@ module hyperbus_phy import hyperbus_pkg::*; #( end else if (ctl_timer_one) begin // instead of going to 0, add another latency count state_d = WaitAddLatAccess; - timer_d = TimerWidth'(cfg_i.chip.t_latency_access); + timer_d = TimerWidth'(cfg_chip.t_latency_access); add_latency_d = 1'b0; end end @@ -375,7 +392,7 @@ module hyperbus_phy import hyperbus_pkg::*; #( trx_clk_ena = 1'b1; trx_tx_data_oe = 1'b1; if (ctl_timer_two) begin - timer_d = cfg_i.chip.t_burst_max; + timer_d = cfg_chip.t_burst_max; if (tf_q.write) begin state_d = Write; trx_tx_data_oe = 1'b1; @@ -397,13 +414,13 @@ module hyperbus_phy import hyperbus_pkg::*; #( tf_d.burst = tf_q.burst - words_per_beat; tf_d.address = tf_q.address + 1; if (ctl_tf_burst_last) begin - timer_d = cfg_i.chip.t_csh_cycles; + timer_d = cfg_chip.t_csh_cycles; state_d = WaitXfer; end end // Force-terminate access on burst time limit if (ctl_timer_one) begin - timer_d = cfg_i.chip.t_csh_cycles; + timer_d = cfg_chip.t_csh_cycles; state_d = WaitXfer; end end @@ -420,13 +437,13 @@ module hyperbus_phy import hyperbus_pkg::*; #( tf_d.address = tf_q.address + 1; if (ctl_tf_burst_last) begin b_pending_set = 1'b1; - timer_d = cfg_i.chip.t_csh_cycles; + timer_d = cfg_chip.t_csh_cycles; state_d = WaitXfer; end end // Force-terminate access on burst time limit if (ctl_timer_one) begin - timer_d = cfg_i.chip.t_csh_cycles; + timer_d = cfg_chip.t_csh_cycles; state_d = WaitXfer; end end @@ -435,7 +452,7 @@ module hyperbus_phy import hyperbus_pkg::*; #( // Wait for FFed Clock and output to stop // May have to be prolonged for potential future devices with t_CSH > 0 if (ctl_timer_zero) begin - timer_d = cfg_i.chip.t_read_write_recovery; + timer_d = cfg_chip.t_read_write_recovery; state_d = WaitRWR; end end @@ -491,5 +508,7 @@ module hyperbus_phy import hyperbus_pkg::*; #( `ASSERT(RxCaptureActiveDuringRead, state_q == Read |-> !trx_rx_clk_reset) `ASSERT(RxCaptureResetAfterDrain, (r_outstand_dec && r_outstand_q == 1 && state_q != Read) |=> trx_rx_clk_reset) + `ASSERT(OutputDriversDisabledDuringRecovery, + state_q == WaitRWR |-> (!hyper_dq_oe_o && !hyper_rwds_oe_o)) endmodule diff --git a/src/hyperbus_asynchronous.sv b/src/hyperbus_asynchronous.sv index 0c0d567..85e6e7e 100644 --- a/src/hyperbus_asynchronous.sv +++ b/src/hyperbus_asynchronous.sv @@ -14,6 +14,7 @@ module hyperbus_asynchronous #( parameter type axi_req_t = logic, parameter type axi_rsp_t = logic, parameter int unsigned RegDataWidth = -1, + parameter int unsigned RegAddrWidth = 32, parameter type reg_req_t = logic, parameter type reg_rsp_t = logic, parameter type axi_rule_t = logic, @@ -127,7 +128,8 @@ module hyperbus_asynchronous #( .reg_req_t ( reg_req_t ), .reg_rsp_t ( reg_rsp_t ), .host_rule_t ( axi_rule_t ), - .RegDataWidth ( RegDataWidth ) + .RegDataWidth ( RegDataWidth ), + .RegAddrWidth ( RegAddrWidth ) ) i_cfg_frontend ( .clk_i ( clk_sys_i ), .rst_ni ( rst_sys_ni ), diff --git a/src/hyperbus_cfg_frontend.sv b/src/hyperbus_cfg_frontend.sv index ce7a07e..0607409 100644 --- a/src/hyperbus_cfg_frontend.sv +++ b/src/hyperbus_cfg_frontend.sv @@ -6,112 +6,83 @@ `include "common_cells/assertions.svh" module hyperbus_cfg_frontend #( - parameter int unsigned NumPhys = 2, - parameter type reg_req_t = logic, - parameter type reg_rsp_t = logic, - parameter type host_rule_t = logic, - parameter int unsigned RegDataWidth = -1, - parameter logic [7:0] CapabilityFeatures = 8'b0010_0000 + parameter int unsigned NumPhys = 2, + parameter int unsigned RegDataWidth = -1, + parameter int unsigned RegAddrWidth = 32, + parameter bit ClockDividerImplemented = 1'b0, + parameter type reg_req_t = logic, + parameter type reg_rsp_t = logic, + parameter type host_rule_t = logic ) ( - input logic clk_i, - input logic rst_ni, - - input reg_req_t reg_req_i, - output reg_rsp_t reg_rsp_o, - - output logic drain_o, - input logic host_idle_i, - input logic trans_active_i, - - output hyperbus_pkg::phy_cfg_t cfg_apply_o, - output logic cfg_apply_valid_o, - input logic cfg_apply_ready_i, - input logic cfg_apply_done_i, - - output logic [7:0] clock_div_apply_o, - output logic clock_div_apply_valid_o, - input logic clock_div_apply_ready_i, - input logic clock_div_apply_done_i, - + input logic clk_i, + input logic rst_ni, + input reg_req_t reg_req_i, + output reg_rsp_t reg_rsp_o, + output logic drain_o, + input logic host_idle_i, + input logic trans_active_i, + output hyperbus_pkg::phy_cfg_t cfg_apply_o, + output logic cfg_apply_valid_o, + input logic cfg_apply_ready_i, + input logic cfg_apply_done_i, + output logic [7:0] clock_div_apply_o, + output logic clock_div_apply_valid_o, + input logic clock_div_apply_ready_i, + input logic clock_div_apply_done_i, output hyperbus_pkg::frontend_cfg_t frontend_cfg_o, output host_rule_t [hyperbus_pkg::HyperNumChips-1:0] chip_rules_o, - input logic decode_error_i + input logic decode_error_i ); - - typedef enum logic [2:0] { + typedef enum logic [3:0] { + CfgInit, CfgIdle, CfgDrain, - CfgCommit, - CfgObserve, CfgClockSend, - CfgClockWaitAck, + CfgClockWait, CfgPhySend, - CfgPhyWaitAck + CfgPhyWait } cfg_state_e; - ////////////////////// - // Persistent state // - ////////////////////// - - cfg_state_e cfg_state_d; - cfg_state_e cfg_state_q; - hyperbus_pkg::phy_cfg_t phy_cfg; - hyperbus_pkg::phy_cfg_t cfg_applied_d; - hyperbus_pkg::phy_cfg_t cfg_applied_q; - logic [7:0] clock_div_applied_d; - logic [7:0] clock_div_applied_q; - logic phy_cfg_changed; - logic clock_div_changed; - logic cfg_changed; - logic cfg_write_pending_d; - logic cfg_write_pending_q; + cfg_state_e cfg_state_d, cfg_state_q; + hyperbus_pkg::frontend_cfg_t staged_frontend_cfg; + hyperbus_pkg::phy_cfg_t staged_phy_cfg; + hyperbus_pkg::frontend_cfg_t applied_frontend_d, applied_frontend_q; + hyperbus_pkg::phy_cfg_t applied_phy_d, applied_phy_q; + hyperbus_pkg::frontend_cfg_t pending_frontend_d, pending_frontend_q; + hyperbus_pkg::phy_cfg_t pending_phy_d, pending_phy_q; + logic command_flush, command_apply; + logic apply_pending_d, apply_pending_q; + logic cfg_changed; + logic status_access; + logic clock_changed; + logic phy_changed; + logic drain_complete; `ASSERT_INIT(NumPhysValid, NumPhys == 1 || NumPhys == 2) `ASSERT_INIT(RegDataWidthValid, RegDataWidth == 32) - - ////////////////////////// - // 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; - assign cfg_changed = phy_cfg_changed || clock_div_changed; - assign drain_o = (cfg_state_q != CfgIdle) || cfg_changed; - assign cfg_apply_valid_o = cfg_state_q == CfgPhySend; - 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 // - //////////////////////////// - + `ASSERT_INIT(RegAddrWidthValid, RegAddrWidth >= 12) + + assign cfg_changed = (staged_frontend_cfg != applied_frontend_q) || + (staged_phy_cfg != applied_phy_q); + assign clock_changed = staged_frontend_cfg.divider != applied_frontend_q.divider; + assign phy_changed = staged_phy_cfg != applied_phy_q; + assign drain_complete = host_idle_i && !trans_active_i; + assign status_access = reg_req_i.valid && + (reg_req_i.addr == RegAddrWidth'(12'h010)); + + assign drain_o = cfg_state_q != CfgIdle; + assign cfg_apply_o = pending_phy_q; + assign cfg_apply_valid_o = cfg_state_q == CfgPhySend; + assign clock_div_apply_o = pending_frontend_q.divider; + assign clock_div_apply_valid_o = cfg_state_q == CfgClockSend; + + // STATUS reads and W1C writes stay live while a barrier is in progress. 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; - reg_rsp_o = cfg_reg_rsp; - - if (reg_req_i.write) begin - cfg_reg_req.valid = reg_req_i.valid && (cfg_state_q == CfgCommit); - if (cfg_state_q != CfgCommit) begin - reg_rsp_o.ready = 1'b0; - reg_rsp_o.error = 1'b0; - reg_rsp_o.rdata = '0; - end - end else if (cfg_state_q != CfgIdle) begin + reg_rsp_o = cfg_reg_rsp; + if (cfg_state_q != CfgIdle && !status_access) begin cfg_reg_req.valid = 1'b0; reg_rsp_o.ready = 1'b0; reg_rsp_o.error = 1'b0; @@ -119,119 +90,153 @@ 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; - clock_div_applied_d = clock_div_applied_q; - cfg_write_pending_d = cfg_write_pending_q; + always_comb begin : proc_cfg_fsm + cfg_state_d = cfg_state_q; + applied_frontend_d = applied_frontend_q; + applied_phy_d = applied_phy_q; + pending_frontend_d = pending_frontend_q; + pending_phy_d = pending_phy_q; + apply_pending_d = apply_pending_q; unique case (cfg_state_q) - // Detect pending register or applied-configuration changes. + CfgInit: begin + pending_frontend_d = staged_frontend_cfg; + pending_phy_d = staged_phy_cfg; + apply_pending_d = 1'b1; + if (drain_complete) begin + if (ClockDividerImplemented && clock_changed) begin + cfg_state_d = CfgClockSend; + end else if (phy_changed) begin + cfg_state_d = CfgPhySend; + end else begin + applied_frontend_d = staged_frontend_cfg; + applied_phy_d = staged_phy_cfg; + apply_pending_d = 1'b0; + cfg_state_d = CfgIdle; + end + end + end CfgIdle: begin - if (cfg_changed) begin - cfg_write_pending_d = 1'b0; + // APPLY wins if both command bits are written together. + if (command_apply) begin + pending_frontend_d = staged_frontend_cfg; + pending_phy_d = staged_phy_cfg; + apply_pending_d = 1'b1; cfg_state_d = CfgDrain; - end else if (cfg_write_requested) begin - cfg_write_pending_d = 1'b1; + end else if (command_flush) begin + apply_pending_d = 1'b0; cfg_state_d = CfgDrain; end end - // Stop admission and wait until all accepted traffic has completed. CfgDrain: 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_write_accepted) begin - cfg_write_pending_d = 1'b0; - cfg_state_d = CfgObserve; + if (drain_complete) begin + if (apply_pending_q) begin + if (ClockDividerImplemented && + (pending_frontend_q.divider != applied_frontend_q.divider)) begin + cfg_state_d = CfgClockSend; + end else if (pending_phy_q != applied_phy_q) begin + cfg_state_d = CfgPhySend; + end else begin + applied_frontend_d = pending_frontend_q; + applied_phy_d = pending_phy_q; + apply_pending_d = 1'b0; + cfg_state_d = CfgIdle; + end + end else begin + cfg_state_d = CfgIdle; + end end end - // Re-evaluate generated configuration outputs after the write commits. - CfgObserve: begin - if (clock_div_changed) begin - cfg_state_d = CfgClockSend; - end else if (phy_cfg_changed) begin - cfg_state_d = CfgPhySend; - end else begin - cfg_state_d = CfgIdle; - end - end - // Send a changed divider value to the isochronous clock generator. CfgClockSend: begin - 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; + if (clock_div_apply_valid_o && clock_div_apply_ready_i) begin + if (clock_div_apply_done_i) begin + if (pending_phy_q != applied_phy_q) begin + cfg_state_d = CfgPhySend; + end else begin + applied_frontend_d = pending_frontend_q; + apply_pending_d = 1'b0; + cfg_state_d = CfgIdle; + end + end else begin + cfg_state_d = CfgClockWait; + end end end - // Hold the barrier until the divider confirms the update. - CfgClockWaitAck: begin + CfgClockWait: 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; + if (pending_phy_q != applied_phy_q) begin + cfg_state_d = CfgPhySend; + end else begin + applied_frontend_d = pending_frontend_q; + apply_pending_d = 1'b0; + cfg_state_d = CfgIdle; + end end end - // Send changed PHY configuration through the selected bridge. CfgPhySend: begin - 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; + if (cfg_apply_valid_o && cfg_apply_ready_i) begin + cfg_state_d = cfg_apply_done_i ? CfgIdle : CfgPhyWait; + if (cfg_apply_done_i) begin + applied_frontend_d = pending_frontend_q; + applied_phy_d = pending_phy_q; + apply_pending_d = 1'b0; + end end end - // Hold the barrier until the backend confirms the PHY update. - CfgPhyWaitAck: begin + CfgPhyWait: begin if (cfg_apply_done_i) begin - cfg_applied_d = phy_cfg; - cfg_state_d = CfgIdle; + applied_frontend_d = pending_frontend_q; + applied_phy_d = pending_phy_q; + apply_pending_d = 1'b0; + cfg_state_d = CfgIdle; end end - default: begin - cfg_state_d = CfgIdle; - end + default: cfg_state_d = CfgInit; 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 // - /////////////////// + `FFARN(cfg_state_q, cfg_state_d, CfgInit, clk_i, rst_ni) + `FFARN(applied_frontend_q, applied_frontend_d, '0, clk_i, rst_ni) + `FFARN(applied_phy_q, applied_phy_d, '0, clk_i, rst_ni) + `FFARN(pending_frontend_q, pending_frontend_d, '0, clk_i, rst_ni) + `FFARN(pending_phy_q, pending_phy_d, '0, clk_i, rst_ni) + `FFARN(apply_pending_q, apply_pending_d, 1'b0, clk_i, rst_ni) hyperbus_cfg_regs #( - .NumPhys ( NumPhys ), - .RegDataWidth ( RegDataWidth ), - .CapabilityFeatures ( CapabilityFeatures ), - .reg_req_t ( reg_req_t ), - .reg_rsp_t ( reg_rsp_t ), - .addr_rule_t ( host_rule_t ) + .NumPhys ( NumPhys ), + .RegDataWidth ( RegDataWidth ), + .RegAddrWidth ( RegAddrWidth ), + .ClockDividerImplemented ( ClockDividerImplemented ), + .reg_req_t ( reg_req_t ), + .reg_rsp_t ( reg_rsp_t ), + .addr_rule_t ( host_rule_t ) ) i_cfg_regs ( .clk_i ( clk_i ), .rst_ni ( rst_ni ), .reg_req_i ( cfg_reg_req ), .reg_rsp_o ( cfg_reg_rsp ), - .frontend_cfg_o ( frontend_cfg_o ), - .phy_cfg_o ( phy_cfg ), - .chip_rules_o ( chip_rules_o ), - .decode_error_i ( decode_error_i ) + .status_busy_i ( cfg_state_q != CfgIdle ), + .status_dirty_i ( cfg_changed ), + .decode_error_i ( decode_error_i ), + .command_flush_o( command_flush ), + .command_apply_o( command_apply ), + .frontend_cfg_o ( staged_frontend_cfg ), + .phy_cfg_o ( staged_phy_cfg ), + .chip_rules_o ( ) ); -endmodule + assign frontend_cfg_o = applied_frontend_q; + always_comb begin : proc_applied_rules + chip_rules_o = '0; + for (int unsigned i = 0; i < hyperbus_pkg::HyperNumChips; i++) begin + chip_rules_o[i].idx = unsigned'(i); + chip_rules_o[i].start_addr = applied_frontend_q.chip[i].range_base; + chip_rules_o[i].end_addr = applied_frontend_q.chip[i].range_bound; + end + end + + for (genvar i = 0; i < hyperbus_pkg::HyperNumChips; i++) begin : gen_cfg_range_checks + `ASSERT(CfgLatencyAccessRange, staged_phy_cfg.chip[i].t_latency_access >= 4'd3, + clk_i, !rst_ni) + end +endmodule : hyperbus_cfg_frontend diff --git a/src/hyperbus_cfg_regs.sv b/src/hyperbus_cfg_regs.sv index a83979b..af8a3ba 100644 --- a/src/hyperbus_cfg_regs.sv +++ b/src/hyperbus_cfg_regs.sv @@ -1,42 +1,40 @@ // Copyright 2023 ETH Zurich and University of Bologna. // Solderpad Hardware License, Version 0.51, see LICENSE for details. // SPDX-License-Identifier: SHL-0.51 -// -// Paul Scheffler `include "common_cells/assertions.svh" module hyperbus_cfg_regs #( - parameter int unsigned NumPhys = -1, - parameter int unsigned RegDataWidth = -1, - parameter int unsigned RegAddrWidth = 32, - // Capability bits are supplied by the selected top-level clocking - // implementation. The register map itself remains stable across tops. - parameter logic [7:0] CapabilityFeatures = 8'b0010_0000, - parameter type reg_req_t = logic, - parameter type reg_rsp_t = logic, - parameter type addr_rule_t = logic + parameter int unsigned NumPhys = -1, + parameter int unsigned RegDataWidth = -1, + parameter int unsigned RegAddrWidth = 32, + parameter bit ClockDividerImplemented = 1'b0, + parameter type reg_req_t = logic, + parameter type reg_rsp_t = logic, + parameter type addr_rule_t = logic ) ( - input logic clk_i, - input logic rst_ni, - - input reg_req_t reg_req_i, - output reg_rsp_t reg_rsp_o, - - output hyperbus_pkg::frontend_cfg_t frontend_cfg_o, - output hyperbus_pkg::phy_cfg_t phy_cfg_o, - output addr_rule_t [hyperbus_pkg::HyperNumChips-1:0] chip_rules_o, - input logic decode_error_i + input logic clk_i, + input logic rst_ni, + input reg_req_t reg_req_i, + output reg_rsp_t reg_rsp_o, + input logic status_busy_i, + input logic status_dirty_i, + input logic decode_error_i, + output logic command_flush_o, + output logic command_apply_o, + output hyperbus_pkg::frontend_cfg_t frontend_cfg_o, + output hyperbus_pkg::phy_cfg_t phy_cfg_o, + output addr_rule_t [hyperbus_pkg::HyperNumChips-1:0] chip_rules_o ); - `include "common_cells/registers.svh" + localparam int unsigned RegStrbWidth = RegDataWidth / 8; `ASSERT_INIT(NumPhysValid, NumPhys == 1 || NumPhys == 2) - `ASSERT_INIT(RegAddrWidthValid, RegAddrWidth >= 12) `ASSERT_INIT(RegDataWidthValid, RegDataWidth == 32) + `ASSERT_INIT(RegAddrWidthValid, RegAddrWidth >= 12) - typedef logic [11:0] cfg_addr_t; - typedef logic [31:0] cfg_data_t; - typedef logic [3:0] cfg_strb_t; + typedef logic [11:0] cfg_addr_t; + typedef logic [31:0] cfg_data_t; + typedef logic [3:0] cfg_strb_t; typedef struct packed { cfg_addr_t addr; @@ -53,7 +51,7 @@ module hyperbus_cfg_regs #( } cfg_reg_rsp_t; typedef struct packed { - cfg_addr_t paddr; + cfg_addr_t paddr; logic [2:0] pprot; logic psel; logic penable; @@ -68,161 +66,129 @@ module hyperbus_cfg_regs #( logic pslverr; } cfg_apb_rsp_t; - 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 [hyperbus_pkg::HyperNumChips-1:0] chip_rules_all; - - cfg_addr_t cfg_addr; - logic cfg_addr_in_window; - logic sel_reg_mapped; - logic cfg_access_active_d, cfg_access_active_q; - logic cfg_access_open; - logic cfg_status_access; - logic cfg_value_valid; - logic unused_cfg_fields; - + hyperbus_cfg_regblock_pkg::hyperbus_cfg_regs__out_t cfg_hwif_out; cfg_reg_req_t cfg_reg_req; cfg_reg_rsp_t cfg_reg_rsp; cfg_apb_req_t cfg_apb_req; cfg_apb_rsp_t cfg_apb_rsp; + cfg_addr_t cfg_addr; + logic addr_in_window; + logic cfg_addr_mapped; + logic cfg_value_valid; assign cfg_addr = cfg_addr_t'(reg_req_i.addr); - assign cfg_addr_in_window = reg_req_i.addr == RegAddrWidth'(cfg_addr); - assign cfg_status_access = reg_req_i.valid && - cfg_addr_in_window && (cfg_addr == 12'h010); + assign addr_in_window = reg_req_i.addr == RegAddrWidth'(cfg_addr); always_comb begin : proc_cfg_addr_mapped - sel_reg_mapped = cfg_addr_in_window && - ((cfg_addr == 12'h000) || - (cfg_addr == 12'h004) || - (cfg_addr == 12'h008) || - (cfg_addr == 12'h00c) || - (cfg_addr == 12'h010) || - (cfg_addr == 12'h100) || - (cfg_addr == 12'h200) || - (cfg_addr == 12'hffc)); + cfg_addr_mapped = addr_in_window && + ((cfg_addr == 12'h000) || + (cfg_addr == 12'h004) || + (cfg_addr == 12'h008) || + (cfg_addr == 12'h00c) || + (cfg_addr == 12'h010) || + (cfg_addr == 12'h100) || + (cfg_addr == 12'h200) || + (cfg_addr == 12'hffc)); for (int unsigned i = 0; i < 2; i++) begin - sel_reg_mapped |= cfg_addr_in_window && - ((cfg_addr == (12'h300 + i * 12'h40)) || - (cfg_addr == (12'h304 + i * 12'h40))); + cfg_addr_mapped |= addr_in_window && + ((cfg_addr == (12'h300 + i * 12'h40)) || + (cfg_addr == (12'h304 + i * 12'h40))); end - for (int unsigned i = 0; i < 8; i++) begin - sel_reg_mapped |= cfg_addr_in_window && - ((cfg_addr == (12'h400 + i * 12'h40)) || - (cfg_addr == (12'h404 + i * 12'h40)) || - (cfg_addr == (12'h408 + i * 12'h40)) || - (cfg_addr == (12'h40c + i * 12'h40)) || - (cfg_addr == (12'h410 + i * 12'h40)) || - (cfg_addr == (12'h414 + i * 12'h40)) || - (cfg_addr == (12'h418 + i * 12'h40))); + for (int unsigned i = 0; i < hyperbus_pkg::HyperNumChips; i++) begin + cfg_addr_mapped |= addr_in_window && + ((cfg_addr == (12'h400 + i * 12'h40)) || + (cfg_addr == (12'h404 + i * 12'h40)) || + (cfg_addr == (12'h408 + i * 12'h40)) || + (cfg_addr == (12'h40c + i * 12'h40)) || + (cfg_addr == (12'h410 + i * 12'h40)) || + (cfg_addr == (12'h414 + i * 12'h40)) || + (cfg_addr == (12'h418 + i * 12'h40))); end end - // The configuration frontend owns draining and automatic apply sequencing. - // Keep this wrapper purely responsible for the stable register-map access. - assign cfg_access_open = 1'b1; always_comb begin : proc_cfg_value_valid cfg_value_valid = 1'b1; - if (reg_req_i.valid && reg_req_i.write && sel_reg_mapped) begin - // All byte-backed legacy values are range-checked only when their - // corresponding byte is written, preserving partial-write behavior. + if (reg_req_i.valid && reg_req_i.write && addr_in_window) begin + // Range-check byte-backed fields before narrowing them for the RTL. if ((cfg_addr == 12'h100) && reg_req_i.wstrb[0]) begin - cfg_value_valid &= (reg_req_i.wdata[7:0] <= 8'd1); + cfg_value_valid &= reg_req_i.wdata[7:0] <= 8'd1; end if ((cfg_addr == 12'h200) && reg_req_i.wstrb[0]) begin - cfg_value_valid &= (reg_req_i.wdata[7:0] >= 8'd2); + cfg_value_valid &= reg_req_i.wdata[7:0] >= 8'd2; end - if (((cfg_addr == 12'h304) || (cfg_addr == 12'h344)) && - reg_req_i.wstrb[0]) begin - cfg_value_valid &= (reg_req_i.wdata[7:0] <= 8'd15); + if (((cfg_addr == 12'h304) || (cfg_addr == 12'h344)) && reg_req_i.wstrb[0]) begin + cfg_value_valid &= reg_req_i.wdata[7:0] <= 8'd15; end - for (int unsigned i = 0; i < 8; i++) begin + for (int unsigned i = 0; i < hyperbus_pkg::HyperNumChips; i++) begin if (cfg_addr == (12'h408 + i * 12'h40)) begin - if (reg_req_i.wstrb[0]) cfg_value_valid &= (reg_req_i.wdata[7:0] <= 8'd1); - if (reg_req_i.wstrb[1]) cfg_value_valid &= (reg_req_i.wdata[15:8] <= 8'd31); - if (reg_req_i.wstrb[2]) cfg_value_valid &= (reg_req_i.wdata[23:16] <= 8'd1); + if (reg_req_i.wstrb[0]) begin + cfg_value_valid &= reg_req_i.wdata[7:0] <= 8'd1; + end + if (reg_req_i.wstrb[1]) begin + cfg_value_valid &= reg_req_i.wdata[15:8] <= 8'd31; + end + if (reg_req_i.wstrb[2]) begin + cfg_value_valid &= reg_req_i.wdata[23:16] <= 8'd1; + end end if (cfg_addr == (12'h40c + i * 12'h40)) begin - if (reg_req_i.wstrb[0]) cfg_value_valid &= - (reg_req_i.wdata[7:0] >= 8'd3 && reg_req_i.wdata[7:0] <= 8'd15); - if (reg_req_i.wstrb[1]) cfg_value_valid &= (reg_req_i.wdata[15:8] <= 8'd15); - if (reg_req_i.wstrb[2]) cfg_value_valid &= (reg_req_i.wdata[23:16] <= 8'd1); + if (reg_req_i.wstrb[0]) begin + cfg_value_valid &= reg_req_i.wdata[7:0] inside {[8'd3:8'd15]}; + end + if (reg_req_i.wstrb[1]) begin + cfg_value_valid &= reg_req_i.wdata[15:8] <= 8'd15; + end + if (reg_req_i.wstrb[2]) begin + cfg_value_valid &= reg_req_i.wdata[23:16] <= 8'd1; + end end if (cfg_addr == (12'h414 + i * 12'h40)) begin - if (reg_req_i.wstrb[0]) cfg_value_valid &= (reg_req_i.wdata[7:0] <= 8'd15); - if (reg_req_i.wstrb[1]) cfg_value_valid &= (reg_req_i.wdata[15:8] <= 8'd15); - if (reg_req_i.wstrb[2]) cfg_value_valid &= (reg_req_i.wdata[23:16] <= 8'd15); + if (reg_req_i.wstrb[0]) begin + cfg_value_valid &= reg_req_i.wdata[7:0] <= 8'd15; + end + if (reg_req_i.wstrb[1]) begin + cfg_value_valid &= reg_req_i.wdata[15:8] <= 8'd15; + end + if (reg_req_i.wstrb[2]) begin + cfg_value_valid &= reg_req_i.wdata[23:16] <= 8'd15; + end end end end end - assign reg_rsp_o.ready = cfg_access_open & - (~reg_req_i.valid | ~sel_reg_mapped | ~cfg_value_valid | cfg_reg_rsp.ready); - assign reg_rsp_o.error = (reg_req_i.valid && ~sel_reg_mapped) | - (reg_req_i.valid && reg_req_i.write && sel_reg_mapped && !cfg_value_valid) | - cfg_reg_rsp.error; - assign reg_rsp_o.rdata = sel_reg_mapped ? RegDataWidth'(cfg_reg_rsp.rdata) : '0; + assign reg_rsp_o.ready = !reg_req_i.valid || !cfg_addr_mapped || + !cfg_value_valid || cfg_reg_rsp.ready; + assign reg_rsp_o.error = reg_req_i.valid && + (!cfg_addr_mapped || !cfg_value_valid || cfg_reg_rsp.error); + assign reg_rsp_o.rdata = RegDataWidth'(cfg_reg_rsp.rdata); - assign cfg_reg_req.valid = reg_req_i.valid & sel_reg_mapped & cfg_access_open & cfg_value_valid; + assign cfg_reg_req.valid = reg_req_i.valid && cfg_addr_mapped && cfg_value_valid; assign cfg_reg_req.addr = cfg_addr; assign cfg_reg_req.write = reg_req_i.write; - assign cfg_reg_req.wdata = 32'(reg_req_i.wdata); + assign cfg_reg_req.wdata = cfg_data_t'(reg_req_i.wdata); assign cfg_reg_req.wstrb = cfg_strb_t'(reg_req_i.wstrb); - always_comb begin : proc_cfg_access - cfg_access_active_d = cfg_access_active_q; - if (!cfg_access_active_q && cfg_reg_req.valid && !cfg_status_access) begin - cfg_access_active_d = 1'b1; - end - if (cfg_access_active_q && cfg_reg_rsp.ready) begin - cfg_access_active_d = 1'b0; - end - end - - `FFARN(cfg_access_active_q, cfg_access_active_d, 1'b0, clk_i, rst_ni); - - always_comb begin : proc_chip_rules - chip_rules_all = '0; - 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}; - chip_rules_all[0].end_addr = {cfg_hwif_out.chip_0.range_bound.value.value, 22'b0}; - chip_rules_all[1].start_addr = {cfg_hwif_out.chip_1.range_base.value.value, 22'b0}; - chip_rules_all[1].end_addr = {cfg_hwif_out.chip_1.range_bound.value.value, 22'b0}; - chip_rules_all[2].start_addr = {cfg_hwif_out.chip_2.range_base.value.value, 22'b0}; - chip_rules_all[2].end_addr = {cfg_hwif_out.chip_2.range_bound.value.value, 22'b0}; - chip_rules_all[3].start_addr = {cfg_hwif_out.chip_3.range_base.value.value, 22'b0}; - chip_rules_all[3].end_addr = {cfg_hwif_out.chip_3.range_bound.value.value, 22'b0}; - chip_rules_all[4].start_addr = {cfg_hwif_out.chip_4.range_base.value.value, 22'b0}; - chip_rules_all[4].end_addr = {cfg_hwif_out.chip_4.range_bound.value.value, 22'b0}; - chip_rules_all[5].start_addr = {cfg_hwif_out.chip_5.range_base.value.value, 22'b0}; - chip_rules_all[5].end_addr = {cfg_hwif_out.chip_5.range_bound.value.value, 22'b0}; - chip_rules_all[6].start_addr = {cfg_hwif_out.chip_6.range_base.value.value, 22'b0}; - chip_rules_all[6].end_addr = {cfg_hwif_out.chip_6.range_bound.value.value, 22'b0}; - chip_rules_all[7].start_addr = {cfg_hwif_out.chip_7.range_base.value.value, 22'b0}; - chip_rules_all[7].end_addr = {cfg_hwif_out.chip_7.range_bound.value.value, 22'b0}; - end - + // Capability and live status fields are driven by the implementation. 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]; - assign cfg_hwif_in.global_cfg.capability.chip_enable.next = CapabilityFeatures[2]; - assign cfg_hwif_in.global_cfg.capability.clock_divider.next = CapabilityFeatures[3]; - assign cfg_hwif_in.global_cfg.capability.staged_apply.next = CapabilityFeatures[4]; - assign cfg_hwif_in.global_cfg.capability.error_status.next = CapabilityFeatures[5]; - assign cfg_hwif_in.global_cfg.capability.rwds_sample_timing.next = - CapabilityFeatures[6]; - assign cfg_hwif_in.global_cfg.capability.rwds_oe_timing.next = CapabilityFeatures[7]; - assign cfg_hwif_in.global_cfg.status.decode_error.next = + assign cfg_hwif_in.global_cfg.capability.per_chip_cfg.next = 1'b1; + assign cfg_hwif_in.global_cfg.capability.per_phy_cfg.next = 1'b1; + assign cfg_hwif_in.global_cfg.capability.chip_enable.next = 1'b1; + assign cfg_hwif_in.global_cfg.capability.clock_divider.next = ClockDividerImplemented; + assign cfg_hwif_in.global_cfg.capability.staged_apply.next = 1'b1; + assign cfg_hwif_in.global_cfg.capability.error_status.next = 1'b1; + assign cfg_hwif_in.global_cfg.capability.rwds_sample_timing.next = 1'b0; + assign cfg_hwif_in.global_cfg.capability.rwds_oe_timing.next = 1'b0; + assign cfg_hwif_in.global_cfg.status.busy.next = status_busy_i; + assign cfg_hwif_in.global_cfg.status.dirty.next = status_dirty_i; + assign cfg_hwif_in.global_cfg.status.decode_error.next = cfg_hwif_out.global_cfg.status.decode_error.value; assign cfg_hwif_in.global_cfg.status.decode_error.hwset = decode_error_i; - assign cfg_hwif_in.global_cfg.status.busy.next = 1'b0; - assign cfg_hwif_in.global_cfg.status.dirty.next = 1'b0; - // COMMAND fields are intentionally left unconsumed until staged apply is - // implemented. STATUS busy/dirty likewise have no point-3 source. + + assign command_flush_o = cfg_hwif_out.global_cfg.command.flush.value; + assign command_apply_o = cfg_hwif_out.global_cfg.command.apply.value; reg_to_apb #( .reg_req_t ( cfg_reg_req_t ), @@ -239,67 +205,168 @@ module hyperbus_cfg_regs #( ); hyperbus_cfg_regblock i_cfg_regblock ( - .clk ( clk_i ), - .arst_n ( rst_ni ), - .s_apb_psel ( cfg_apb_req.psel ), - .s_apb_penable ( cfg_apb_req.penable ), - .s_apb_pwrite ( cfg_apb_req.pwrite ), - .s_apb_pprot ( cfg_apb_req.pprot ), - .s_apb_paddr ( cfg_apb_req.paddr ), - .s_apb_pwdata ( cfg_apb_req.pwdata ), - .s_apb_pstrb ( cfg_apb_req.pstrb ), - .s_apb_pready ( cfg_apb_rsp.pready ), - .s_apb_prdata ( cfg_apb_rsp.prdata ), - .s_apb_pslverr ( cfg_apb_rsp.pslverr ), - .hwif_in ( cfg_hwif_in ), - .hwif_out ( cfg_hwif_out ) + .clk ( clk_i ), + .arst_n ( rst_ni ), + .s_apb_psel ( cfg_apb_req.psel ), + .s_apb_penable ( cfg_apb_req.penable ), + .s_apb_pwrite ( cfg_apb_req.pwrite ), + .s_apb_pprot ( cfg_apb_req.pprot ), + .s_apb_paddr ( cfg_apb_req.paddr ), + .s_apb_pwdata ( cfg_apb_req.pwdata ), + .s_apb_pstrb ( cfg_apb_req.pstrb ), + .s_apb_pready ( cfg_apb_rsp.pready ), + .s_apb_prdata ( cfg_apb_rsp.prdata ), + .s_apb_pslverr ( cfg_apb_rsp.pslverr ), + .hwif_in ( cfg_hwif_in ), + .hwif_out ( cfg_hwif_out ) ); always_comb begin : proc_cfg_output frontend_cfg_o = '0; phy_cfg_o = '0; - frontend_cfg_o.address_mask_msb = cfg_hwif_out.chip_0.address_cfg.address_mask_msb.value; - frontend_cfg_o.address_space = cfg_hwif_out.chip_0.address_cfg.address_space.value[0]; - frontend_cfg_o.dual_phy = (NumPhys == 2) && + frontend_cfg_o.dual_phy = (NumPhys == 2) && cfg_hwif_out.frontend.frontend_cfg.dual_phy.value; - frontend_cfg_o.phy_clock_div = cfg_hwif_out.backend.clock_cfg.divider.value; + frontend_cfg_o.divider = cfg_hwif_out.backend.clock_cfg.divider.value; - phy_cfg_o.chip.t_latency_access = cfg_hwif_out.chip_0.latency_cfg.t_latency_access.value; - phy_cfg_o.chip.en_latency_additional = cfg_hwif_out.chip_0.latency_cfg.en_latency_additional.value; - phy_cfg_o.chip.t_burst_max = cfg_hwif_out.chip_0.burst_cfg.t_burst_max.value; - phy_cfg_o.chip.t_read_write_recovery = cfg_hwif_out.chip_0.chip_timing.t_read_write_recovery.value; - phy_cfg_o.chip.t_rx_clk_delay = cfg_hwif_out.chip_0.rx_delay.value.value; - phy_cfg_o.chip.t_csh_cycles = cfg_hwif_out.chip_0.chip_timing.t_csh_cycles.value; - phy_cfg_o.chip.csn_to_ck_cycles = cfg_hwif_out.chip_0.chip_timing.csn_to_ck_cycles.value; - phy_cfg_o.t_tx_clk_delay = cfg_hwif_out.phy_0.tx_delay.value.value; - phy_cfg_o.dual_phy = (NumPhys == 2) && - cfg_hwif_out.frontend.frontend_cfg.dual_phy.value; - end + phy_cfg_o.dual_phy = frontend_cfg_o.dual_phy; + for (int unsigned i = 0; i < hyperbus_pkg::HyperNumChips; i++) begin + unique case (i) + 0: begin + frontend_cfg_o.chip[i].range_base = {cfg_hwif_out.chip_0.range_base.value.value, 22'b0}; + frontend_cfg_o.chip[i].range_bound = {cfg_hwif_out.chip_0.range_bound.value.value, 22'b0}; + frontend_cfg_o.chip[i].address_space = cfg_hwif_out.chip_0.address_cfg.address_space.value[0]; + frontend_cfg_o.chip[i].address_mask_msb = cfg_hwif_out.chip_0.address_cfg.address_mask_msb.value[4:0]; + frontend_cfg_o.chip[i].enable = cfg_hwif_out.chip_0.address_cfg.enable.value; + phy_cfg_o.chip[i].t_latency_access = cfg_hwif_out.chip_0.latency_cfg.t_latency_access.value[3:0]; + phy_cfg_o.chip[i].rwds_sample_delay = cfg_hwif_out.chip_0.latency_cfg.rwds_sample_delay.value[3:0]; + phy_cfg_o.chip[i].en_latency_additional = cfg_hwif_out.chip_0.latency_cfg.en_latency_additional.value; + phy_cfg_o.chip[i].t_burst_max = cfg_hwif_out.chip_0.burst_cfg.t_burst_max.value; + phy_cfg_o.chip[i].t_read_write_recovery = cfg_hwif_out.chip_0.chip_timing.t_read_write_recovery.value[3:0]; + phy_cfg_o.chip[i].t_csh_cycles = cfg_hwif_out.chip_0.chip_timing.t_csh_cycles.value[3:0]; + phy_cfg_o.chip[i].csn_to_ck_cycles = cfg_hwif_out.chip_0.chip_timing.csn_to_ck_cycles.value[3:0]; + phy_cfg_o.chip[i].t_rx_clk_delay = cfg_hwif_out.chip_0.rx_delay.value.value; + end + 1: begin + frontend_cfg_o.chip[i].range_base = {cfg_hwif_out.chip_1.range_base.value.value, 22'b0}; + frontend_cfg_o.chip[i].range_bound = {cfg_hwif_out.chip_1.range_bound.value.value, 22'b0}; + frontend_cfg_o.chip[i].address_space = cfg_hwif_out.chip_1.address_cfg.address_space.value[0]; + frontend_cfg_o.chip[i].address_mask_msb = cfg_hwif_out.chip_1.address_cfg.address_mask_msb.value[4:0]; + frontend_cfg_o.chip[i].enable = cfg_hwif_out.chip_1.address_cfg.enable.value; + phy_cfg_o.chip[i].t_latency_access = cfg_hwif_out.chip_1.latency_cfg.t_latency_access.value[3:0]; + phy_cfg_o.chip[i].rwds_sample_delay = cfg_hwif_out.chip_1.latency_cfg.rwds_sample_delay.value[3:0]; + phy_cfg_o.chip[i].en_latency_additional = cfg_hwif_out.chip_1.latency_cfg.en_latency_additional.value; + phy_cfg_o.chip[i].t_burst_max = cfg_hwif_out.chip_1.burst_cfg.t_burst_max.value; + phy_cfg_o.chip[i].t_read_write_recovery = cfg_hwif_out.chip_1.chip_timing.t_read_write_recovery.value[3:0]; + phy_cfg_o.chip[i].t_csh_cycles = cfg_hwif_out.chip_1.chip_timing.t_csh_cycles.value[3:0]; + phy_cfg_o.chip[i].csn_to_ck_cycles = cfg_hwif_out.chip_1.chip_timing.csn_to_ck_cycles.value[3:0]; + phy_cfg_o.chip[i].t_rx_clk_delay = cfg_hwif_out.chip_1.rx_delay.value.value; + end + 2: begin + frontend_cfg_o.chip[i].range_base = {cfg_hwif_out.chip_2.range_base.value.value, 22'b0}; + frontend_cfg_o.chip[i].range_bound = {cfg_hwif_out.chip_2.range_bound.value.value, 22'b0}; + frontend_cfg_o.chip[i].address_space = cfg_hwif_out.chip_2.address_cfg.address_space.value[0]; + frontend_cfg_o.chip[i].address_mask_msb = cfg_hwif_out.chip_2.address_cfg.address_mask_msb.value[4:0]; + frontend_cfg_o.chip[i].enable = cfg_hwif_out.chip_2.address_cfg.enable.value; + phy_cfg_o.chip[i].t_latency_access = cfg_hwif_out.chip_2.latency_cfg.t_latency_access.value[3:0]; + phy_cfg_o.chip[i].rwds_sample_delay = cfg_hwif_out.chip_2.latency_cfg.rwds_sample_delay.value[3:0]; + phy_cfg_o.chip[i].en_latency_additional = cfg_hwif_out.chip_2.latency_cfg.en_latency_additional.value; + phy_cfg_o.chip[i].t_burst_max = cfg_hwif_out.chip_2.burst_cfg.t_burst_max.value; + phy_cfg_o.chip[i].t_read_write_recovery = cfg_hwif_out.chip_2.chip_timing.t_read_write_recovery.value[3:0]; + phy_cfg_o.chip[i].t_csh_cycles = cfg_hwif_out.chip_2.chip_timing.t_csh_cycles.value[3:0]; + phy_cfg_o.chip[i].csn_to_ck_cycles = cfg_hwif_out.chip_2.chip_timing.csn_to_ck_cycles.value[3:0]; + phy_cfg_o.chip[i].t_rx_clk_delay = cfg_hwif_out.chip_2.rx_delay.value.value; + end + 3: begin + frontend_cfg_o.chip[i].range_base = {cfg_hwif_out.chip_3.range_base.value.value, 22'b0}; + frontend_cfg_o.chip[i].range_bound = {cfg_hwif_out.chip_3.range_bound.value.value, 22'b0}; + frontend_cfg_o.chip[i].address_space = cfg_hwif_out.chip_3.address_cfg.address_space.value[0]; + frontend_cfg_o.chip[i].address_mask_msb = cfg_hwif_out.chip_3.address_cfg.address_mask_msb.value[4:0]; + frontend_cfg_o.chip[i].enable = cfg_hwif_out.chip_3.address_cfg.enable.value; + phy_cfg_o.chip[i].t_latency_access = cfg_hwif_out.chip_3.latency_cfg.t_latency_access.value[3:0]; + phy_cfg_o.chip[i].rwds_sample_delay = cfg_hwif_out.chip_3.latency_cfg.rwds_sample_delay.value[3:0]; + phy_cfg_o.chip[i].en_latency_additional = cfg_hwif_out.chip_3.latency_cfg.en_latency_additional.value; + phy_cfg_o.chip[i].t_burst_max = cfg_hwif_out.chip_3.burst_cfg.t_burst_max.value; + phy_cfg_o.chip[i].t_read_write_recovery = cfg_hwif_out.chip_3.chip_timing.t_read_write_recovery.value[3:0]; + phy_cfg_o.chip[i].t_csh_cycles = cfg_hwif_out.chip_3.chip_timing.t_csh_cycles.value[3:0]; + phy_cfg_o.chip[i].csn_to_ck_cycles = cfg_hwif_out.chip_3.chip_timing.csn_to_ck_cycles.value[3:0]; + phy_cfg_o.chip[i].t_rx_clk_delay = cfg_hwif_out.chip_3.rx_delay.value.value; + end + 4: begin + frontend_cfg_o.chip[i].range_base = {cfg_hwif_out.chip_4.range_base.value.value, 22'b0}; + frontend_cfg_o.chip[i].range_bound = {cfg_hwif_out.chip_4.range_bound.value.value, 22'b0}; + frontend_cfg_o.chip[i].address_space = cfg_hwif_out.chip_4.address_cfg.address_space.value[0]; + frontend_cfg_o.chip[i].address_mask_msb = cfg_hwif_out.chip_4.address_cfg.address_mask_msb.value[4:0]; + frontend_cfg_o.chip[i].enable = cfg_hwif_out.chip_4.address_cfg.enable.value; + phy_cfg_o.chip[i].t_latency_access = cfg_hwif_out.chip_4.latency_cfg.t_latency_access.value[3:0]; + phy_cfg_o.chip[i].rwds_sample_delay = cfg_hwif_out.chip_4.latency_cfg.rwds_sample_delay.value[3:0]; + phy_cfg_o.chip[i].en_latency_additional = cfg_hwif_out.chip_4.latency_cfg.en_latency_additional.value; + phy_cfg_o.chip[i].t_burst_max = cfg_hwif_out.chip_4.burst_cfg.t_burst_max.value; + phy_cfg_o.chip[i].t_read_write_recovery = cfg_hwif_out.chip_4.chip_timing.t_read_write_recovery.value[3:0]; + phy_cfg_o.chip[i].t_csh_cycles = cfg_hwif_out.chip_4.chip_timing.t_csh_cycles.value[3:0]; + phy_cfg_o.chip[i].csn_to_ck_cycles = cfg_hwif_out.chip_4.chip_timing.csn_to_ck_cycles.value[3:0]; + phy_cfg_o.chip[i].t_rx_clk_delay = cfg_hwif_out.chip_4.rx_delay.value.value; + end + 5: begin + frontend_cfg_o.chip[i].range_base = {cfg_hwif_out.chip_5.range_base.value.value, 22'b0}; + frontend_cfg_o.chip[i].range_bound = {cfg_hwif_out.chip_5.range_bound.value.value, 22'b0}; + frontend_cfg_o.chip[i].address_space = cfg_hwif_out.chip_5.address_cfg.address_space.value[0]; + frontend_cfg_o.chip[i].address_mask_msb = cfg_hwif_out.chip_5.address_cfg.address_mask_msb.value[4:0]; + frontend_cfg_o.chip[i].enable = cfg_hwif_out.chip_5.address_cfg.enable.value; + phy_cfg_o.chip[i].t_latency_access = cfg_hwif_out.chip_5.latency_cfg.t_latency_access.value[3:0]; + phy_cfg_o.chip[i].rwds_sample_delay = cfg_hwif_out.chip_5.latency_cfg.rwds_sample_delay.value[3:0]; + phy_cfg_o.chip[i].en_latency_additional = cfg_hwif_out.chip_5.latency_cfg.en_latency_additional.value; + phy_cfg_o.chip[i].t_burst_max = cfg_hwif_out.chip_5.burst_cfg.t_burst_max.value; + phy_cfg_o.chip[i].t_read_write_recovery = cfg_hwif_out.chip_5.chip_timing.t_read_write_recovery.value[3:0]; + phy_cfg_o.chip[i].t_csh_cycles = cfg_hwif_out.chip_5.chip_timing.t_csh_cycles.value[3:0]; + phy_cfg_o.chip[i].csn_to_ck_cycles = cfg_hwif_out.chip_5.chip_timing.csn_to_ck_cycles.value[3:0]; + phy_cfg_o.chip[i].t_rx_clk_delay = cfg_hwif_out.chip_5.rx_delay.value.value; + end + 6: begin + frontend_cfg_o.chip[i].range_base = {cfg_hwif_out.chip_6.range_base.value.value, 22'b0}; + frontend_cfg_o.chip[i].range_bound = {cfg_hwif_out.chip_6.range_bound.value.value, 22'b0}; + frontend_cfg_o.chip[i].address_space = cfg_hwif_out.chip_6.address_cfg.address_space.value[0]; + frontend_cfg_o.chip[i].address_mask_msb = cfg_hwif_out.chip_6.address_cfg.address_mask_msb.value[4:0]; + frontend_cfg_o.chip[i].enable = cfg_hwif_out.chip_6.address_cfg.enable.value; + phy_cfg_o.chip[i].t_latency_access = cfg_hwif_out.chip_6.latency_cfg.t_latency_access.value[3:0]; + phy_cfg_o.chip[i].rwds_sample_delay = cfg_hwif_out.chip_6.latency_cfg.rwds_sample_delay.value[3:0]; + phy_cfg_o.chip[i].en_latency_additional = cfg_hwif_out.chip_6.latency_cfg.en_latency_additional.value; + phy_cfg_o.chip[i].t_burst_max = cfg_hwif_out.chip_6.burst_cfg.t_burst_max.value; + phy_cfg_o.chip[i].t_read_write_recovery = cfg_hwif_out.chip_6.chip_timing.t_read_write_recovery.value[3:0]; + phy_cfg_o.chip[i].t_csh_cycles = cfg_hwif_out.chip_6.chip_timing.t_csh_cycles.value[3:0]; + phy_cfg_o.chip[i].csn_to_ck_cycles = cfg_hwif_out.chip_6.chip_timing.csn_to_ck_cycles.value[3:0]; + phy_cfg_o.chip[i].t_rx_clk_delay = cfg_hwif_out.chip_6.rx_delay.value.value; + end + default: begin + frontend_cfg_o.chip[i].range_base = {cfg_hwif_out.chip_7.range_base.value.value, 22'b0}; + frontend_cfg_o.chip[i].range_bound = {cfg_hwif_out.chip_7.range_bound.value.value, 22'b0}; + frontend_cfg_o.chip[i].address_space = cfg_hwif_out.chip_7.address_cfg.address_space.value[0]; + frontend_cfg_o.chip[i].address_mask_msb = cfg_hwif_out.chip_7.address_cfg.address_mask_msb.value[4:0]; + frontend_cfg_o.chip[i].enable = cfg_hwif_out.chip_7.address_cfg.enable.value; + phy_cfg_o.chip[i].t_latency_access = cfg_hwif_out.chip_7.latency_cfg.t_latency_access.value[3:0]; + phy_cfg_o.chip[i].rwds_sample_delay = cfg_hwif_out.chip_7.latency_cfg.rwds_sample_delay.value[3:0]; + phy_cfg_o.chip[i].en_latency_additional = cfg_hwif_out.chip_7.latency_cfg.en_latency_additional.value; + phy_cfg_o.chip[i].t_burst_max = cfg_hwif_out.chip_7.burst_cfg.t_burst_max.value; + phy_cfg_o.chip[i].t_read_write_recovery = cfg_hwif_out.chip_7.chip_timing.t_read_write_recovery.value[3:0]; + phy_cfg_o.chip[i].t_csh_cycles = cfg_hwif_out.chip_7.chip_timing.t_csh_cycles.value[3:0]; + phy_cfg_o.chip[i].csn_to_ck_cycles = cfg_hwif_out.chip_7.chip_timing.csn_to_ck_cycles.value[3:0]; + phy_cfg_o.chip[i].t_rx_clk_delay = cfg_hwif_out.chip_7.rx_delay.value.value; + end + endcase + end - // The legacy PR34 interface has no destinations for these newer fields. - always_comb begin : proc_unused_cfg_fields - unused_cfg_fields = ^cfg_hwif_out.backend.clock_cfg.divider.value; - unused_cfg_fields |= ^cfg_hwif_out.phy_0.rwds_timing.rwds_oe_setup_cycles.value; - unused_cfg_fields |= ^cfg_hwif_out.phy_1.rwds_timing.rwds_oe_setup_cycles.value; - unused_cfg_fields |= ^cfg_hwif_out.chip_0.address_cfg.enable.value; - unused_cfg_fields |= ^cfg_hwif_out.chip_0.latency_cfg.rwds_sample_delay.value; - unused_cfg_fields |= ^cfg_hwif_out.chip_1.address_cfg.enable.value; - unused_cfg_fields |= ^cfg_hwif_out.chip_1.latency_cfg.rwds_sample_delay.value; - unused_cfg_fields |= ^cfg_hwif_out.chip_2.address_cfg.enable.value; - unused_cfg_fields |= ^cfg_hwif_out.chip_2.latency_cfg.rwds_sample_delay.value; - unused_cfg_fields |= ^cfg_hwif_out.chip_3.address_cfg.enable.value; - unused_cfg_fields |= ^cfg_hwif_out.chip_3.latency_cfg.rwds_sample_delay.value; - unused_cfg_fields |= ^cfg_hwif_out.chip_4.address_cfg.enable.value; - unused_cfg_fields |= ^cfg_hwif_out.chip_4.latency_cfg.rwds_sample_delay.value; - unused_cfg_fields |= ^cfg_hwif_out.chip_5.address_cfg.enable.value; - unused_cfg_fields |= ^cfg_hwif_out.chip_5.latency_cfg.rwds_sample_delay.value; - unused_cfg_fields |= ^cfg_hwif_out.chip_6.address_cfg.enable.value; - unused_cfg_fields |= ^cfg_hwif_out.chip_6.latency_cfg.rwds_sample_delay.value; - unused_cfg_fields |= ^cfg_hwif_out.chip_7.address_cfg.enable.value; - unused_cfg_fields |= ^cfg_hwif_out.chip_7.latency_cfg.rwds_sample_delay.value; + phy_cfg_o.phy[0].tx_delay = cfg_hwif_out.phy_0.tx_delay.value.value; + phy_cfg_o.phy[0].rwds_oe_setup_cycles = cfg_hwif_out.phy_0.rwds_timing.rwds_oe_setup_cycles.value[3:0]; + phy_cfg_o.phy[1].tx_delay = cfg_hwif_out.phy_1.tx_delay.value.value; + phy_cfg_o.phy[1].rwds_oe_setup_cycles = cfg_hwif_out.phy_1.rwds_timing.rwds_oe_setup_cycles.value[3:0]; end - assign chip_rules_o = chip_rules_all; - + always_comb begin : proc_chip_rules + chip_rules_o = '0; + for (int unsigned i = 0; i < hyperbus_pkg::HyperNumChips; i++) begin + chip_rules_o[i].idx = unsigned'(i); + chip_rules_o[i].start_addr = frontend_cfg_o.chip[i].range_base; + chip_rules_o[i].end_addr = frontend_cfg_o.chip[i].range_bound; + end + end endmodule : hyperbus_cfg_regs diff --git a/src/hyperbus_isochronous.sv b/src/hyperbus_isochronous.sv index 74b4d62..468b0bd 100644 --- a/src/hyperbus_isochronous.sv +++ b/src/hyperbus_isochronous.sv @@ -15,6 +15,7 @@ module hyperbus_isochronous #( parameter type axi_req_t = logic, parameter type axi_rsp_t = logic, parameter int unsigned RegDataWidth = -1, + parameter int unsigned RegAddrWidth = 32, parameter type reg_req_t = logic, parameter type reg_rsp_t = logic, parameter type axi_rule_t = logic, @@ -176,10 +177,9 @@ module hyperbus_isochronous #( .reg_req_t ( reg_req_t ), .reg_rsp_t ( reg_rsp_t ), .host_rule_t ( axi_rule_t ), - .RegDataWidth ( RegDataWidth ), - // Isochronous top implements the backend clock divider; decode-error - // status is shared by all top-level clocking variants. - .CapabilityFeatures ( 8'b0010_1000 ) + .RegDataWidth ( RegDataWidth ), + .RegAddrWidth ( RegAddrWidth ), + .ClockDividerImplemented ( 1'b1 ) ) i_cfg_frontend ( .clk_i ( clk_sys_i ), .rst_ni ( rst_sys_ni ), diff --git a/src/hyperbus_midend.sv b/src/hyperbus_midend.sv index 8b25cf2..2b06bbb 100644 --- a/src/hyperbus_midend.sv +++ b/src/hyperbus_midend.sv @@ -216,6 +216,7 @@ module hyperbus_midend #( host_ext_addr_t req_last_addr; host_ext_addr_t req_phy_bytes; host_ext_addr_t cmd_rule_end_addr; + host_ext_addr_t covered_rule_end_addr; hyperbus_pkg::hyper_blen_t req_phy_burst; logic cmd_dec_valid; logic cmd_end_dec_valid; @@ -354,37 +355,30 @@ module hyperbus_midend #( (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 ) - ); + // Decode only enabled rules; common_cells addr_decode has no per-rule enable. + always_comb begin : proc_chip_decode + cmd_chip_sel_idx = '0; + cmd_end_chip_sel_idx = '0; + cmd_dec_valid = 1'b0; + cmd_end_dec_valid = 1'b0; + + for (int unsigned i = 0; i < hyperbus_pkg::HyperNumChips; i++) begin + if (frontend_cfg_i.chip[i].enable && + (req_phy_first_addr >= chip_rules_i[i].start_addr) && + ((req_phy_first_addr < chip_rules_i[i].end_addr) || + (chip_rules_i[i].end_addr == '0))) begin + cmd_chip_sel_idx = chip_sel_idx_t'(i); + cmd_dec_valid = 1'b1; + end + if (frontend_cfg_i.chip[i].enable && + (req_phy_last_addr >= chip_rules_i[i].start_addr) && + ((req_phy_last_addr < chip_rules_i[i].end_addr) || + (chip_rules_i[i].end_addr == '0))) begin + cmd_end_chip_sel_idx = chip_sel_idx_t'(i); + cmd_end_dec_valid = 1'b1; + end + end + end // Software keeps ranges ordered and non-overlapping; transactions may cross contiguous ranges. always_comb begin : proc_req_rule_range @@ -395,13 +389,22 @@ module hyperbus_midend #( host_ext_addr_t'( chip_rules_i[cmd_chip_sel_idx].end_addr); end + covered_rule_end_addr = cmd_rule_end_addr; 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 < 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; + (cmd_end_chip_sel_idx >= cmd_chip_sel_idx) && + frontend_cfg_i.chip[cmd_chip_sel_idx].enable && + frontend_cfg_i.chip[cmd_end_chip_sel_idx].enable; + for (int unsigned i = 0; i < hyperbus_pkg::HyperNumChips; i++) begin + if ((i > cmd_chip_sel_idx) && (i <= cmd_end_chip_sel_idx) && + frontend_cfg_i.chip[i].enable) begin + if (host_ext_addr_t'(chip_rules_i[i].start_addr) != + covered_rule_end_addr) begin + req_range_valid = 1'b0; + end + covered_rule_end_addr = (chip_rules_i[i].end_addr == '0) ? + (host_ext_addr_t'(1) << HostAddrWidth) : + host_ext_addr_t'(chip_rules_i[i].end_addr); end end end @@ -415,13 +418,15 @@ module hyperbus_midend #( 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_space = cmd_dec_valid ? + frontend_cfg_i.chip[cmd_chip_sel_idx].address_space : 1'b0; 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); + ((host_addr_t'(1) << (cmd_dec_valid ? + frontend_cfg_i.chip[cmd_chip_sel_idx].address_mask_msb : 5'd0)) - 1); cmd_phy_address = masked_req_address >> 1; if (NumPhys == 2) begin cmd_phy_address = masked_req_address >> 2; diff --git a/src/hyperbus_pkg.sv b/src/hyperbus_pkg.sv index 589a57a..1c22ab4 100644 --- a/src/hyperbus_pkg.sv +++ b/src/hyperbus_pkg.sv @@ -50,26 +50,39 @@ package hyperbus_pkg; /////////////////// typedef struct packed { - logic [3:0] t_latency_access; - logic en_latency_additional; - logic [15:0] t_burst_max; - logic [3:0] t_read_write_recovery; - logic [7:0] t_rx_clk_delay; - logic [3:0] t_csh_cycles; // CS-high recovery cycles - logic [3:0] csn_to_ck_cycles; // CS assertion to clock-start delay + logic [31:0] range_base; + logic [31:0] range_bound; + logic [4:0] address_mask_msb; + logic address_space; + logic enable; + } chip_frontend_cfg_t; + + typedef struct packed { + logic [3:0] t_latency_access; + logic en_latency_additional; + logic [3:0] rwds_sample_delay; + logic [15:0] t_burst_max; + logic [3:0] t_read_write_recovery; + logic [7:0] t_rx_clk_delay; + logic [3:0] t_csh_cycles; + logic [3:0] csn_to_ck_cycles; } chip_phy_cfg_t; typedef struct packed { - logic [4:0] address_mask_msb; - logic address_space; - logic dual_phy; - logic [7:0] phy_clock_div; + logic [7:0] tx_delay; + logic [3:0] rwds_oe_setup_cycles; + } phy_lane_cfg_t; + + typedef struct packed { + chip_frontend_cfg_t [HyperNumChips-1:0] chip; + logic dual_phy; + logic [7:0] divider; } frontend_cfg_t; typedef struct packed { - chip_phy_cfg_t chip; - logic [7:0] t_tx_clk_delay; - logic dual_phy; + chip_phy_cfg_t [HyperNumChips-1:0] chip; + phy_lane_cfg_t [1:0] phy; + logic dual_phy; } phy_cfg_t; ////////////////////////// diff --git a/src/hyperbus_synchronous.sv b/src/hyperbus_synchronous.sv index fdca6cd..1f1e1ee 100644 --- a/src/hyperbus_synchronous.sv +++ b/src/hyperbus_synchronous.sv @@ -14,6 +14,7 @@ module hyperbus_synchronous #( parameter type axi_req_t = logic, parameter type axi_rsp_t = logic, parameter int unsigned RegDataWidth = -1, + parameter int unsigned RegAddrWidth = 32, parameter type reg_req_t = logic, parameter type reg_rsp_t = logic, parameter type axi_rule_t = logic, @@ -117,7 +118,8 @@ module hyperbus_synchronous #( .reg_req_t ( reg_req_t ), .reg_rsp_t ( reg_rsp_t ), .host_rule_t ( axi_rule_t ), - .RegDataWidth ( RegDataWidth ) + .RegDataWidth ( RegDataWidth ), + .RegAddrWidth ( RegAddrWidth ) ) i_cfg_frontend ( .clk_i ( clk_sys_i ), .rst_ni ( rst_sys_ni ), diff --git a/src/regs/hyperbus_cfg_regs.rdl b/src/regs/hyperbus_cfg_regs.rdl index a81f7f9..6be19e4 100644 --- a/src/regs/hyperbus_cfg_regs.rdl +++ b/src/regs/hyperbus_cfg_regs.rdl @@ -68,7 +68,7 @@ addrmap hyperbus_cfg_regs { field { sw = rw; hw = r; reset = 1'b1; } enable[16:16]; }; reg latency_cfg { - desc = "Chip latency configuration. t_latency_access must be 3 through 15; rwds_sample_delay must be at most 15 and is active when capability.rwds_sample_timing is set. Chip 0 is shared when capability.per_chip_cfg is clear."; + desc = "Chip latency configuration. t_latency_access must be 3 through 15; rwds_sample_delay must be at most t_latency_access minus 2 and is active when capability.rwds_sample_timing is set. Chip 0 is shared when capability.per_chip_cfg is clear."; field { sw = rw; hw = r; reset = 8'd6; } t_latency_access[7:0]; field { sw = rw; hw = r; reset = 8'd0; } rwds_sample_delay[15:8]; field { sw = rw; hw = r; reset = 1'b0; } en_latency_additional[16:16]; diff --git a/test/axi_hyper_tb.sv b/test/axi_hyper_tb.sv index 263eb71..24fa961 100644 --- a/test/axi_hyper_tb.sv +++ b/test/axi_hyper_tb.sv @@ -72,7 +72,6 @@ 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 TbDramDataWidth = 8; @@ -578,6 +577,26 @@ module axi_hyper_tb end endtask + task automatic wait_config_barrier(input reg_bus_master_t reg_drv); + logic [31:0] status; + logic reg_error; + // Allow the generated command pulse to reach the configuration FSM. + repeat (2) @(posedge clk); + for (int unsigned i = 0; i < 10000; i++) begin + reg_drv.send_read(32'h010, status, reg_error); + if (reg_error != 1'b0) $error("configuration STATUS read failed"); + if (!status[1]) return; + end + $error("configuration barrier did not return idle"); + endtask + + task automatic apply_config(input reg_bus_master_t reg_drv); + logic reg_error; + reg_drv.send_write(32'h00c, 32'h2, '1, reg_error); + if (reg_error != 1'b0) $error("configuration APPLY write failed"); + wait_config_barrier(reg_drv); + 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); @@ -706,6 +725,7 @@ module axi_hyper_tb reg_drv.send_write(32'h410, TbSlowBurstMax, '1, reg_error); if (reg_error != 1'b0) $error("unexpected error"); + apply_config(reg_drv); segment_start_snapshot = segment_start_count; axi_write_slow(axi_drv, SlowBaseAddr, TbSlowNumBeats, TbSlowGapCycles); @@ -731,6 +751,7 @@ module axi_hyper_tb reg_drv.send_write(32'h410, saved_t_burst_max, '1, reg_error); if (reg_error != 1'b0) $error("unexpected error"); + apply_config(reg_drv); endtask task automatic check_config_barrier( @@ -740,12 +761,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 @@ -755,19 +776,18 @@ 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'h00c, 32'h1, '1, reg_error); + wait_config_barrier(reg_drv); + 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 @@ -813,13 +833,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'h10, 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'h10, 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'h10, status, reg_error); if ((reg_error != 1'b0) || status[0]) begin $error("[DECODE] Sticky decode-error status did not clear"); end @@ -846,6 +866,7 @@ module axi_hyper_tb if (reg_error != 1'b0) $error("unexpected error"); reg_drv.send_write(32'h440, Boundary, '1, reg_error); if (reg_error != 1'b0) $error("unexpected error"); + apply_config(reg_drv); segment_start_snapshot = segment_start_count; axi_write_slow(axi_drv, BurstAddr, 4, 0); @@ -859,6 +880,34 @@ module axi_hyper_tb if (reg_error != 1'b0) $error("unexpected error"); reg_drv.send_write(32'h404, 32'h8100_0000, '1, reg_error); if (reg_error != 1'b0) $error("unexpected error"); + apply_config(reg_drv); + endtask + + task automatic check_chip_enable( + input axi_ctrl_master_t axi_drv, + input reg_bus_master_t reg_drv + ); + axi_ctrl_master_t::ax_beat_t ax = new(); + axi_ctrl_master_t::r_beat_t r; + logic reg_error; + if (NumConnectedChips < 2) return; + $display("==========================="); + $display("= Per-chip enable ="); + $display("==========================="); + reg_drv.send_write(32'h448, 32'h1900, '1, reg_error); + if (reg_error != 1'b0) $error("chip disable write failed"); + apply_config(reg_drv); + ax.ax_addr = 32'h8100_0000; + ax.ax_id = '0; + ax.ax_len = 0; + ax.ax_size = 3; + ax.ax_burst = axi_pkg::BURST_INCR; + axi_drv.send_ar(ax); + axi_drv.recv_r(r); + if (r.r_resp != axi_pkg::RESP_DECERR) $error("disabled chip accepted a request"); + reg_drv.send_write(32'h448, 32'h11900, '1, reg_error); + if (reg_error != 1'b0) $error("chip enable write failed"); + apply_config(reg_drv); endtask task automatic check_large_rule_distance(input axi_ctrl_master_t axi_drv); @@ -904,6 +953,7 @@ module axi_hyper_tb // A zero bound extends the final rule through the end of the address space. reg_drv.send_write(32'h444, '0, '1, reg_error); if (reg_error != 1'b0) $error("unexpected error"); + apply_config(reg_drv); axi_write_subword(axi_drv, ValidAddr, TestData, 3); axi_check_subword(axi_drv, ValidAddr, TestData, 3); @@ -925,6 +975,7 @@ module axi_hyper_tb reg_drv.send_write(32'h444, 32'h8200_0000, '1, reg_error); if (reg_error != 1'b0) $error("unexpected error"); + apply_config(reg_drv); endtask task automatic check_atomic_add( @@ -986,11 +1037,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'h010, status, reg_error); + reg_drv.send_read(32'h10, 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'h010, 32'h1, '1, reg_error); + reg_drv.send_write(32'h10, 32'h1, '1, reg_error); if (reg_error != 1'b0) $error("unexpected error"); axi_check_subword(axi_drv, AtomicAddr, InitialValue + Addend, 2); @@ -1014,11 +1065,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'h010, status, reg_error); + reg_drv.send_read(32'h10, 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'h010, 32'h1, '1, reg_error); + reg_drv.send_write(32'h10, 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); @@ -1079,6 +1130,7 @@ module axi_hyper_tb // A zero-ended final rule must contain ordinary atomic accesses. reg_drv.send_write(32'h444, '0, '1, reg_error); if (reg_error != 1'b0) $error("unexpected error"); + apply_config(reg_drv); axi_write_subword(axi_drv, ZeroEndAddr, ZeroEndInitial, 2); ax.ax_addr = ZeroEndAddr; ax.ax_size = 2; @@ -1098,6 +1150,7 @@ module axi_hyper_tb axi_check_subword(axi_drv, ZeroEndAddr, ZeroEndInitial + ZeroEndAddend, 2); reg_drv.send_write(32'h444, 32'h8200_0000, '1, reg_error); if (reg_error != 1'b0) $error("unexpected error"); + apply_config(reg_drv); endtask initial begin : proc_sim_crtl @@ -1144,12 +1197,21 @@ module axi_hyper_tb if (s_reg_error != 1'b0) $error("unexpected error"); reg_master.send_write(32'h300, TbTxDelayLineTaps, '1, s_reg_error); if (s_reg_error != 1'b0) $error("unexpected error"); + if (NumPhys == 2) begin + reg_master.send_write(32'h340, TbTxDelayLineTaps + 1, '1, s_reg_error); + end + reg_master.send_read(32'h010, reg_read, s_reg_error); + if (s_reg_error != 1'b0 || !reg_read[2]) $error("staged configuration did not set STATUS.dirty"); + apply_config(reg_master); + reg_master.send_read(32'h010, reg_read, s_reg_error); + if (s_reg_error != 1'b0 || reg_read[2] || reg_read[1]) $error("APPLY did not clear STATUS.dirty/busy"); if (TbDutVariant == 0) begin 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'h200, 8'd2, '1, s_reg_error); if (s_reg_error != 1'b0) $error("unexpected error"); + apply_config(reg_master); end #600350ns; @@ -1158,6 +1220,7 @@ module axi_hyper_tb // The configuration barrier completes only after the divided clock resumes. reg_master.send_write(32'h200, 8'd4, '1, s_reg_error); if (s_reg_error != 1'b0) $error("unexpected error"); + apply_config(reg_master); reg_master.send_read(32'h200, reg_read, s_reg_error); if ((s_reg_error != 1'b0) || (reg_read != 4)) $error("divider update failed"); @@ -1167,6 +1230,7 @@ module axi_hyper_tb reg_master.send_write(32'h200, 8'd2, '1, s_reg_error); if (s_reg_error != 1'b0) $error("unexpected error"); + apply_config(reg_master); reg_master.send_read(32'h200, reg_read, s_reg_error); if ((s_reg_error != 1'b0) || (reg_read != 2)) $error("divider restore failed"); @@ -1185,6 +1249,7 @@ module axi_hyper_tb run_slow_backpressure_test(axi_ctrl_mst, reg_master); check_config_barrier(axi_ctrl_mst, reg_master); check_decode_errors(axi_ctrl_mst, reg_master); + check_chip_enable(axi_ctrl_mst, reg_master); check_cross_chip_burst(axi_ctrl_mst, reg_master); check_large_rule_distance(axi_ctrl_mst); check_range_edges(axi_ctrl_mst, reg_master); @@ -1201,16 +1266,20 @@ module axi_hyper_tb // Keep each data phase near 2 us, leaving margin for CA and access latency. reg_master.send_write(32'h410, 400 / iso_test_dividers[i], '1, s_reg_error); if (s_reg_error != 1'b0) $error("unexpected t_burst_max update error"); + apply_config(reg_master); reg_master.send_write(32'h200, iso_test_dividers[i], '1, s_reg_error); if (s_reg_error != 1'b0) $error("unexpected divider update error"); + apply_config(reg_master); axi_write_slow(axi_ctrl_mst, 32'h8001_0000 + i * 32'h100, 16, 0); axi_read_slow_check(axi_ctrl_mst, 32'h8001_0000 + i * 32'h100, 16, 128); end reg_master.send_write(32'h200, 8'd2, '1, s_reg_error); if (s_reg_error != 1'b0) $error("unexpected divider restore error"); + apply_config(reg_master); reg_master.send_write(32'h410, iso_saved_t_burst_max, '1, s_reg_error); if (s_reg_error != 1'b0) $error("unexpected t_burst_max restore error"); + apply_config(reg_master); end if (NumPhys == 1) begin @@ -1219,16 +1288,18 @@ module axi_hyper_tb if (TbDutVariant == 0) begin // switch memory address space to register space - reg_master.send_write(32'h408, 32'h1_1901, '1, s_reg_error); + reg_master.send_write(32'h408, 32'h11901, '1, s_reg_error); if (s_reg_error != 1'b0) $error("unexpected error"); + apply_config(reg_master); // enable variable latency so we can test RWDS sampling s27ks_cfg0.fixed_latency_enable = 1'b0; axi_write_32(32'h8000_0000 + S27KS_CFG0_REG_OFFSET, (s27ks_cfg0 | s27ks_cfg0 << 16)); // switch back to memory address space - reg_master.send_write(32'h408, 32'h1_1900, '1, s_reg_error); + reg_master.send_write(32'h408, 32'h11900, '1, s_reg_error); if (s_reg_error != 1'b0) $error("unexpected error"); + apply_config(reg_master); end check_consecutive_reads(axi_ctrl_mst); @@ -1254,6 +1325,7 @@ module axi_hyper_tb $display("==========================="); reg_master.send_write(32'h100,1'b0,'1,s_reg_error); + apply_config(reg_master); if (s_reg_error != 1'b0) $error("unexpected error"); axi_rand_mst.reset(); diff --git a/test/axi_pre_aw_drain_tb.sv b/test/axi_pre_aw_drain_tb.sv index 74f3f88..e67db13 100644 --- a/test/axi_pre_aw_drain_tb.sv +++ b/test/axi_pre_aw_drain_tb.sv @@ -13,17 +13,11 @@ module axi_pre_aw_drain_tb; 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; + localparam logic [31:0] BaseAddress = 32'h8000_6800; + localparam logic [31:0] PartialBaseAddress = 32'h8000_7000; + bit partial_w_fifo_full; - bit partial_config_busy; - bit partial_config_done; + bit partial_flush_busy; bit partial_w_done; fixture_hyperbus #( @@ -35,8 +29,49 @@ module axi_pre_aw_drain_tb; .AnnotateSdf ( 1'b1 ) ) fix (); + task automatic apply_default_mapping(); + logic [31:0] status; + logic error; + + fix.i_rmaster.send_write(32'h444, 32'h8200_0000, '1, error); + if (error) $fatal(1, "chip 1 start-address write failed"); + fix.i_rmaster.send_write(32'h440, 32'h8100_0000, '1, error); + if (error) $fatal(1, "chip 1 end-address write failed"); + fix.i_rmaster.send_write(32'h404, 32'h8100_0000, '1, error); + if (error) $fatal(1, "chip 0 end-address write failed"); + fix.i_rmaster.send_write(32'h400, 32'h8000_0000, '1, error); + if (error) $fatal(1, "chip 0 start-address write failed"); + fix.i_rmaster.send_write(32'h00c, 32'h2, '1, error); + if (error) $fatal(1, "configuration APPLY write failed"); + + repeat (4) @(posedge fix.sys_clk); + for (int unsigned poll = 0; poll < 1000; poll++) begin + fix.i_rmaster.send_read(32'h010, status, error); + if (error) $fatal(1, "configuration status read failed"); + if (!status[1]) return; + end + $fatal(1, "configuration APPLY did not become idle"); + endtask + + task automatic wait_barrier_idle(); + logic [31:0] status; + logic error; + + repeat (2) @(posedge fix.sys_clk); + for (int unsigned poll = 0; poll < 1000; poll++) begin + fix.i_rmaster.send_read(32'h010, status, error); + if (error) $fatal(1, "configuration status read failed"); + if (!status[1]) return; + end + $fatal(1, "configuration barrier did not become idle"); + endtask + initial begin + logic [31:0] status; + logic error; + fix.reset_end(); + apply_default_mapping(); // Fill all 16 entries of the 128-byte host W FIFO without presenting AW. for (int unsigned transaction = 0; transaction < NumTransactions; transaction++) begin @@ -47,20 +82,15 @@ module axi_pre_aw_drain_tb; fix.axi_master_drv.send_w(fix.w_beat); end - // Start the automatic configuration barrier, then verify it remains busy until the + // Start the existing flush 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"); + fix.i_rmaster.send_write(32'h00c, 32'h1, '1, error); + if (error) $fatal(1, "configuration FLUSH write failed"); + for (int unsigned poll = 0; poll < 100; poll++) begin + fix.i_rmaster.send_read(32'h010, status, error); + if (error) $fatal(1, "configuration status read failed"); + if (status[1]) break; + if (poll == 99) $fatal(1, "configuration FLUSH did not enter drain state"); end // Pair each AW with its B response to avoid filling the serializer's @@ -81,7 +111,7 @@ module axi_pre_aw_drain_tb; end end - wait (complete_config_done); + wait_barrier_idle(); for (int unsigned transaction = 0; transaction < NumTransactions; transaction++) begin fix.ar_beat.ax_addr = BaseAddress + transaction * AxiDataBytes; @@ -100,11 +130,10 @@ module axi_pre_aw_drain_tb; 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 + // sender must stop at the 16-beat FIFO capacity while the flush 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_flush_busy = 1'b0; partial_w_done = 1'b0; fork begin : send_partial_w_burst @@ -118,7 +147,8 @@ module axi_pre_aw_drain_tb; end partial_w_done = 1'b1; end - begin : config_partial_w_burst + begin : flush_partial_w_burst + logic [31:0] partial_status; logic partial_error; wait (partial_w_fifo_full); @@ -127,14 +157,23 @@ module axi_pre_aw_drain_tb; $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; + fix.i_rmaster.send_write(32'h00c, 32'h1, '1, partial_error); + if (partial_error) $fatal(1, "partial configuration FLUSH write failed"); + for (int unsigned poll = 0; poll < 100; poll++) begin + fix.i_rmaster.send_read(32'h010, partial_status, partial_error); + if (partial_error) $fatal(1, "partial configuration status read failed"); + if (partial_status[1]) begin + partial_flush_busy = 1'b1; + break; + end + end + if (!partial_flush_busy) begin + $fatal(1, "partial configuration FLUSH did not enter drain state"); + end end join_none - wait (partial_config_busy); + wait (partial_flush_busy); fix.aw_beat.ax_addr = PartialBaseAddress; fix.aw_beat.ax_id = 6'd32; fix.aw_beat.ax_len = PartialBurstBeats - 1; @@ -148,7 +187,7 @@ module axi_pre_aw_drain_tb; fix.b_beat.b_resp); end wait (partial_w_done); - wait (partial_config_done); + wait_barrier_idle(); for (int unsigned beat = 0; beat < PartialBurstBeats; beat++) begin fix.ar_beat.ax_addr = PartialBaseAddress + beat * AxiDataBytes; diff --git a/test/dut_if.sv b/test/dut_if.sv index d7485e8..49e893a 100644 --- a/test/dut_if.sv +++ b/test/dut_if.sv @@ -162,6 +162,7 @@ module dut_if .axi_req_t ( axi_req_t ), .axi_rsp_t ( axi_resp_t ), .RegDataWidth ( RegDw ), + .RegAddrWidth ( RegAw ), .reg_req_t ( reg_req_t ), .reg_rsp_t ( reg_rsp_t ), .axi_rule_t ( axi_rule_t ) diff --git a/test/hyperbus_cfg_regs_tb.sv b/test/hyperbus_cfg_regs_tb.sv index ba58dd7..ec96707 100644 --- a/test/hyperbus_cfg_regs_tb.sv +++ b/test/hyperbus_cfg_regs_tb.sv @@ -3,7 +3,6 @@ // SPDX-License-Identifier: SHL-0.51 module hyperbus_cfg_regs_tb; - typedef struct packed { logic [31:0] addr; logic write; @@ -11,176 +10,174 @@ module hyperbus_cfg_regs_tb; logic [3:0] wstrb; logic valid; } reg_req_t; - typedef struct packed { logic [31:0] rdata; logic error; logic ready; } reg_rsp_t; - typedef struct packed { logic [31:0] start_addr; logic [31:0] end_addr; logic [2:0] idx; } addr_rule_t; - logic clk; - logic rst_n; - reg_req_t reg_req; - reg_rsp_t reg_rsp; - hyperbus_pkg::frontend_cfg_t frontend_cfg; - hyperbus_pkg::phy_cfg_t phy_cfg; + logic clk, rst_n; + reg_req_t reg_req; + reg_rsp_t reg_rsp; + logic status_busy, status_dirty, decode_error; + logic command_flush, command_apply; + int unsigned command_pulse_count; + hyperbus_pkg::frontend_cfg_t frontend_cfg; + hyperbus_pkg::phy_cfg_t phy_cfg; addr_rule_t [hyperbus_pkg::HyperNumChips-1:0] chip_rules; - logic decode_error; - task automatic reg_write( - input logic [31:0] addr, - input logic [31:0] data, - input logic inject_decode_error - ); + task automatic reg_write(input logic [31:0] addr, input logic [31:0] data); + begin @(negedge clk); - reg_req.addr = addr; - reg_req.write = 1'b1; - reg_req.wdata = data; - reg_req.wstrb = '1; - reg_req.valid = 1'b1; - decode_error = inject_decode_error; - - do begin - @(posedge clk); - end while (!reg_rsp.ready); - if (reg_rsp.error) begin - $error("Register write to 0x%0h failed", addr); - end - + reg_req <= '{addr: addr, write: 1'b1, wdata: data, wstrb: 4'hf, valid: 1'b1}; + do begin @(posedge clk); end while (!reg_rsp.ready); + if (reg_rsp.error) $fatal(1, "write failed at %h", addr); @(negedge clk); - reg_req.valid = 1'b0; - decode_error = 1'b0; + reg_req.valid <= 1'b0; + end endtask - task automatic reg_read( - input logic [31:0] addr, - output logic [31:0] data - ); + task automatic reg_read(input logic [31:0] addr, output logic [31:0] data); + begin @(negedge clk); - reg_req.addr = addr; - reg_req.write = 1'b0; - reg_req.wdata = '0; - reg_req.wstrb = '0; - reg_req.valid = 1'b1; - - do begin - @(posedge clk); - end while (!reg_rsp.ready); + reg_req <= '{addr: addr, write: 1'b0, wdata: '0, wstrb: '0, valid: 1'b1}; + do begin @(posedge clk); end while (!reg_rsp.ready); data = reg_rsp.rdata; - if (reg_rsp.error) begin - $error("Register read from 0x%0h failed", addr); - end + if (reg_rsp.error) $fatal(1, "read failed at %h", addr); + @(negedge clk); + reg_req.valid <= 1'b0; + end + endtask + task automatic reg_write_error(input logic [31:0] addr, input logic [31:0] data, + input logic [3:0] strb); + begin + @(negedge clk); + reg_req <= '{addr: addr, write: 1'b1, wdata: data, wstrb: strb, valid: 1'b1}; + do begin @(posedge clk); end while (!reg_rsp.ready); + if (!reg_rsp.error) $error("invalid write to %h was accepted", addr); @(negedge clk); - reg_req.valid = 1'b0; + reg_req.valid <= 1'b0; + end endtask - initial begin - clk = 1'b0; - forever #5ns clk = ~clk; - end + task automatic reg_read_error(input logic [31:0] addr); + begin + @(negedge clk); + reg_req <= '{addr: addr, write: 1'b0, wdata: '0, wstrb: '0, valid: 1'b1}; + do begin @(posedge clk); end while (!reg_rsp.ready); + if (!reg_rsp.error) $error("unmapped read from %h was accepted", addr); + @(negedge clk); + reg_req.valid <= 1'b0; + end + endtask - sim_timeout #( - .Cycles ( 100 ) - ) i_sim_timeout ( - .clk_i ( clk ), - .rst_ni ( rst_n ) - ); + initial begin clk = 1'b0; forever #5ns clk = ~clk; end + + always @(posedge clk) begin + if (rst_n && command_flush && command_apply) command_pulse_count++; + end hyperbus_cfg_regs #( - .NumPhys ( 2 ), - .RegDataWidth ( 32 ), - .reg_req_t ( reg_req_t ), - .reg_rsp_t ( reg_rsp_t ), + .NumPhys ( 2 ), + .RegDataWidth ( 32 ), + .RegAddrWidth ( 32 ), + .ClockDividerImplemented ( 1'b1 ), + .reg_req_t ( reg_req_t ), + .reg_rsp_t ( reg_rsp_t ), .addr_rule_t ( addr_rule_t ) ) i_cfg_regs ( - .clk_i ( clk ), - .rst_ni ( rst_n ), - .reg_req_i ( reg_req ), - .reg_rsp_o ( reg_rsp ), - .frontend_cfg_o ( frontend_cfg ), - .phy_cfg_o ( phy_cfg ), - .chip_rules_o ( chip_rules ), - .decode_error_i ( decode_error ) + .clk_i ( clk ), + .rst_ni ( rst_n ), + .reg_req_i ( reg_req ), + .reg_rsp_o ( reg_rsp ), + .status_busy_i ( status_busy ), + .status_dirty_i ( status_dirty ), + .decode_error_i ( decode_error ), + .command_flush_o( command_flush ), + .command_apply_o( command_apply ), + .frontend_cfg_o ( frontend_cfg ), + .phy_cfg_o ( phy_cfg ), + .chip_rules_o ( chip_rules ) ); initial begin : proc_stimulus - logic [31:0] status; - logic [31:0] capability; - logic [31:0] chip_addr; - logic [31:0] clock_div; - logic [31:0] rx_clk_delay; - logic [31:0] tx_clk_delay; - - rst_n = 1'b0; - reg_req = '0; + logic [31:0] data; + rst_n = 1'b0; + reg_req = '0; + status_busy = 1'b0; + status_dirty = 1'b0; decode_error = 1'b0; - repeat (5) @(posedge clk); - @(negedge clk); + command_pulse_count = 0; + repeat (4) @(posedge clk); rst_n = 1'b1; - if ((chip_rules[0].start_addr != 32'h0000_0000) || - (chip_rules[0].end_addr != 32'h0040_0000) || - (chip_rules[7].start_addr != 32'h01c0_0000) || + reg_read(32'h000, data); + if (data != 32'h0000_0900) $fatal(1, "IP version reset mismatch: %h", data); + reg_read(32'h004, data); + if (data != 32'h0001_0000) $error("REG_IF version reset mismatch: %h", data); + reg_read(32'h008, data); + if (data != 32'h003f_0208) $error("capability fields mismatch: %h", data); + reg_read(32'h400, data); + if (data != 32'h0000_0000) $fatal(1, "chip0 base reset mismatch"); + reg_read(32'h418, data); + if (data != 32'h0000_0010) $fatal(1, "chip0 rx delay reset mismatch"); + if ((chip_rules[7].start_addr != 32'h01c0_0000) || (chip_rules[7].end_addr != 32'h0200_0000)) begin - $error("Unexpected reset chip address map"); - end - - reg_read(32'h008, capability); - if (capability != 32'h0020_0208) begin - $error("Unexpected implementation capability bits: %h", capability); - end - - reg_read(32'h200, clock_div); - if ((clock_div != 8) || (frontend_cfg.phy_clock_div != 8)) begin - $error("Unexpected PHY clock divider reset value"); - end - - reg_write(32'h200, 8'd4, 1'b0); - reg_read(32'h200, clock_div); - if ((clock_div != 4) || (frontend_cfg.phy_clock_div != 4)) begin - $error("PHY clock divider write did not take effect"); + $error("chip7 reset range mismatch"); end - // Exercise both the fine [4:0] and coarse [7:5] delay settings. - reg_write(32'h418, 8'hb5, 1'b0); - reg_write(32'h300, 8'h6a, 1'b0); - reg_read(32'h418, rx_clk_delay); - reg_read(32'h300, tx_clk_delay); - if ((rx_clk_delay != 8'hb5) || (tx_clk_delay != 8'h6a) || - (phy_cfg.chip.t_rx_clk_delay != 8'hb5) || - (phy_cfg.t_tx_clk_delay != 8'h6a)) begin - $error("Delay-line configuration fields did not take effect"); + reg_write(32'h440, 32'h8100_0000); + reg_read(32'h440, data); + if (data != 32'h8100_0000) $fatal(1, "chip1 base alignment mismatch: %h", data); + if (frontend_cfg.chip[1].range_base != 32'h8100_0000) begin + $error("chip1 frontend mapping mismatch"); end - - // Only address bits [31:22] are stored; low bits read back as zero. - reg_write(32'h5c0, 32'h8134_5678, 1'b0); - reg_read(32'h5c0, chip_addr); - if ((chip_addr != 32'h8100_0000) || - (chip_rules[7].start_addr != 32'h8100_0000)) begin - $error("Chip address alignment was not enforced"); + reg_write(32'h340, 32'h0000_00a5); + if (phy_cfg.phy[1].tx_delay != 8'ha5 || phy_cfg.phy[0].tx_delay != 8'h10) begin + $error("per-PHY delay mapping mismatch"); end - - // A new hardware event must win over a simultaneous software W1C. - reg_write(32'h010, 32'h1, 1'b1); - reg_read(32'h010, status); - if (!status[0]) begin - $error("Decode error was lost during a simultaneous W1C"); + reg_write(32'h44c, 32'h0001_0307); + if ((phy_cfg.chip[1].t_latency_access != 4'd7) || + (phy_cfg.chip[1].rwds_sample_delay != 4'd3) || + !phy_cfg.chip[1].en_latency_additional) begin + $error("per-chip latency mapping mismatch"); end - reg_write(32'h010, 32'h1, 1'b0); - reg_read(32'h010, status); - if (status[0]) begin - $error("Decode error did not clear without a new hardware event"); + reg_write_error(32'h200, 32'h1, 4'b0001); + reg_read(32'h200, data); + if (data != 32'h8) $error("invalid divider write changed the register: %h", data); + reg_write_error(32'h44c, 32'h0000_0002, 4'b0001); + reg_read(32'h44c, data); + if (data != 32'h0001_0307) $error("invalid latency write changed the register: %h", data); + reg_read_error(32'h104); + reg_read_error(32'h1000); + + reg_write(32'h00c, 32'h3); + repeat (2) @(posedge clk); + #1ns; + if (command_pulse_count != 1) begin + $error("command strobes were sampled %0d times", command_pulse_count); end - + if (command_apply || command_flush) $error("command strobes did not clear"); + + status_busy = 1'b1; + status_dirty = 1'b1; + decode_error = 1'b1; + reg_read(32'h010, data); + if ((data & 32'h7) != 32'h7) $error("live status mismatch: %h", data); + reg_write(32'h010, 32'h1); + reg_read(32'h010, data); + if (!data[0]) $error("hardware set did not win over W1C"); + decode_error = 1'b0; + reg_write(32'h010, 32'h1); + reg_read(32'h010, data); + if (data[0]) $error("decode error did not clear"); $finish; end - endmodule diff --git a/test/hyperbus_test_dut.sv b/test/hyperbus_test_dut.sv index d897c81..39d27a9 100644 --- a/test/hyperbus_test_dut.sv +++ b/test/hyperbus_test_dut.sv @@ -3,19 +3,20 @@ // 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 int unsigned HostWriteBufferBytes = 64, - 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 RegAddrWidth = 32, + 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, @@ -59,6 +60,7 @@ module hyperbus_test_dut #( .axi_req_t ( axi_req_t ), .axi_rsp_t ( axi_rsp_t ), .RegDataWidth ( RegDataWidth ), + .RegAddrWidth ( RegAddrWidth ), .reg_req_t ( reg_req_t ), .reg_rsp_t ( reg_rsp_t ), .axi_rule_t ( axi_rule_t ) @@ -95,6 +97,7 @@ module hyperbus_test_dut #( .axi_req_t ( axi_req_t ), .axi_rsp_t ( axi_rsp_t ), .RegDataWidth ( RegDataWidth ), + .RegAddrWidth ( RegAddrWidth ), .reg_req_t ( reg_req_t ), .reg_rsp_t ( reg_rsp_t ), .axi_rule_t ( axi_rule_t ) @@ -131,6 +134,7 @@ module hyperbus_test_dut #( .axi_req_t ( axi_req_t ), .axi_rsp_t ( axi_rsp_t ), .RegDataWidth ( RegDataWidth ), + .RegAddrWidth ( RegAddrWidth ), .reg_req_t ( reg_req_t ), .reg_rsp_t ( reg_rsp_t ), .axi_rule_t ( axi_rule_t )