diff --git a/.gitignore b/.gitignore index a63d836..e710fc7 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,7 @@ work/ transcript modelsim.ini vsim.wlf +sim_run*.wlf +sim_*.log models/s27ks0641 +wlft* diff --git a/Bender.yml b/Bender.yml index b3bc15e..c074308 100644 --- a/Bender.yml +++ b/Bender.yml @@ -11,6 +11,9 @@ package: - "Paul Scheffler " - "Luca Valente " # current maintainer +export_include_dirs: + - include + dependencies: common_cells: { git: "https://github.com/pulp-platform/common_cells.git", version: 1.21.0 } axi: { git: "https://github.com/pulp-platform/axi.git", version: 0.39.1 } @@ -39,31 +42,39 @@ sources: - target: any(test,all(not(hyperbus_exclude_dline),not(all(xilinx,hyperbus_dline)))) files: - - src/hyperbus_delay.sv + - src/backend/hyperbus_delay.sv - src/regs/hyperbus_cfg_regblock_pkg.sv - src/hyperbus_pkg.sv - - src/hyperbus_clk_gen.sv - - src/hyperbus_clock_diff_out.sv - - src/hyperbus_w2phy.sv - - src/hyperbus_phy2r.sv - - src/hyperbus_ddr_out.sv - - src/hyperbus_trx.sv + - src/backend/hyperbus_tx_clk_delay.sv + - src/backend/hyperbus_clock_diff_out.sv + - src/hyperbus_write_adapter.sv + - src/hyperbus_read_adapter.sv + - src/backend/hyperbus_ddr_out.sv + - src/backend/hyperbus_trx.sv - src/regs/hyperbus_cfg_regblock.sv - src/hyperbus_cfg_regs.sv - - src/hyperbus_phy.sv - - src/hyperbus_phy_if.sv + - src/backend/hyperbus_phy.sv - src/hyperbus_axi.sv + - src/hyperbus_atomic_handler.sv + - src/hyperbus_midend.sv + - src/hyperbus_cfg_frontend.sv + - src/hyperbus_async_bridge.sv + - src/hyperbus_iso_bridge.sv + - src/backend/hyperbus_backend.sv + - src/hyperbus_synchronous.sv + - src/hyperbus_asynchronous.sv + - src/hyperbus_isochronous.sv - target: hyper_test files: # Device models. TODO: extend - models/s27ks0641/s27ks0641.v # Testbench + - test/hyperbus_test_dut.sv - test/fixture_hyperbus.sv - test/hyperbus_tb.sv - test/dut_if.sv - test/hyperbus_tb_pkg.sv - test/axi_hyper_tb.sv - test/hyperbus_cfg_regs_tb.sv - - src/hyperbus.sv diff --git a/include/hyperbus/typedef.svh b/include/hyperbus/typedef.svh new file mode 100644 index 0000000..9474b9f --- /dev/null +++ b/include/hyperbus/typedef.svh @@ -0,0 +1,106 @@ +// Copyright 2026 ETH Zurich and University of Bologna. +// Solderpad Hardware License, Version 0.51, see LICENSE for details. +// SPDX-License-Identifier: SHL-0.51 + +`ifndef HYPERBUS_TYPEDEF_SVH_ +`define HYPERBUS_TYPEDEF_SVH_ + +`define HYPERBUS_TYPEDEF_HOST_CMD_T(__name, __addr_t) \ + typedef struct packed { \ + logic write; \ + __addr_t addr; \ + hyperbus_pkg::hyper_blen_t beats; \ + hyperbus_pkg::hyper_host_size_t size; \ + hyperbus_pkg::hyper_host_burst_e burst; \ + hyperbus_pkg::hyper_atomic_op_e atomic_op; \ + logic atomic_return; \ + logic ordered; \ + } __name; + +`define HYPERBUS_TYPEDEF_HOST_W_T(__name, __data_t, __strb_t) \ + typedef struct packed { \ + __data_t data; \ + __strb_t strb; \ + logic last; \ + } __name; + +`define HYPERBUS_TYPEDEF_HOST_R_T(__name, __data_t) \ + typedef struct packed { \ + __data_t data; \ + hyperbus_pkg::hyper_resp_e resp; \ + logic last; \ + logic atomic_ok; \ + } __name; + +`define HYPERBUS_TYPEDEF_HOST_WRSP_T(__name) \ + typedef struct packed { \ + hyperbus_pkg::hyper_resp_e resp; \ + logic atomic_ok; \ + } __name; + +`define HYPERBUS_TYPEDEF_HOST_REQ_T(__name, __cmd_t, __w_t) \ + typedef struct packed { \ + __cmd_t cmd; \ + logic cmd_valid; \ + __w_t w; \ + logic w_valid; \ + logic r_ready; \ + logic wrsp_ready; \ + } __name; + +`define HYPERBUS_TYPEDEF_HOST_RSP_T(__name, __r_t, __wrsp_t) \ + typedef struct packed { \ + logic cmd_ready; \ + logic w_ready; \ + __r_t r; \ + logic r_valid; \ + __wrsp_t wrsp; \ + logic wrsp_valid; \ + } __name; + +`define HYPERBUS_TYPEDEF_HOST_ALL_CT(__name, __addr_t, __data_t, __strb_t) \ + `HYPERBUS_TYPEDEF_HOST_CMD_T(__name``_cmd_t, __addr_t) \ + `HYPERBUS_TYPEDEF_HOST_W_T(__name``_w_t, __data_t, __strb_t) \ + `HYPERBUS_TYPEDEF_HOST_R_T(__name``_r_t, __data_t) \ + `HYPERBUS_TYPEDEF_HOST_WRSP_T(__name``_wrsp_t) \ + `HYPERBUS_TYPEDEF_HOST_REQ_T(__name``_req_t, __name``_cmd_t, \ + __name``_w_t) \ + `HYPERBUS_TYPEDEF_HOST_RSP_T(__name``_rsp_t, __name``_r_t, \ + __name``_wrsp_t) + +`define HYPERBUS_TYPEDEF_LINK_ALL_CT(__name, __num_phys, __num_chips) \ + typedef struct packed { \ + logic [(16*__num_phys)-1:0] data; \ + logic last; \ + logic [(2*__num_phys)-1:0] strb; \ + } __name``_tx_t; \ + typedef struct packed { \ + logic [(16*__num_phys)-1:0] data; \ + logic last; \ + logic error; \ + } __name``_rx_t; \ + typedef struct packed { \ + logic error; \ + } __name``_wrsp_t; \ + typedef struct packed { \ + hyperbus_pkg::hyper_tf_t trans; \ + logic [__num_chips-1:0] cs; \ + } __name``_cmd_t; \ + typedef struct packed { \ + __name``_cmd_t cmd; \ + logic cmd_valid; \ + __name``_tx_t tx; \ + logic tx_valid; \ + logic rx_ready; \ + logic wrsp_ready; \ + } __name``_req_t; \ + typedef struct packed { \ + logic cmd_ready; \ + logic tx_ready; \ + __name``_rx_t rx; \ + logic rx_valid; \ + __name``_wrsp_t wrsp; \ + logic wrsp_valid; \ + } __name``_rsp_t; + +`endif diff --git a/scripts/start.tcl b/scripts/start.tcl index f4408ad..e1256cc 100644 --- a/scripts/start.tcl +++ b/scripts/start.tcl @@ -15,13 +15,13 @@ proc run_test {top_name wlf_name} { file delete -force $transcript_name transcript file $transcript_name - vsim $top_name -wlf $wlf_name -t 1ps -voptargs=+acc -classdebug + # Questa 10.7 rejects enum assignments in common_cells clearable CDC models. + vsim $top_name -wlf $wlf_name -t 1ps -voptargs=+acc -classdebug -suppress 8386 onfinish stop set StdArithNoWarnings 1 set NumericStdNoWarnings 1 log -r /* - catch {delete wave *} run -all @@ -37,7 +37,10 @@ proc run_test {top_name wlf_name} { } } -run_test axi_hyper_tb sim_run_axi.wlf -run_test hyperbus_cfg_regs_tb sim_run_cfg_regs.wlf +run_test axi_hyper_tb_isochronous sim_run_isochronous.wlf +run_test axi_hyper_tb_synchronous sim_run_synchronous.wlf +run_test axi_hyper_tb_asynchronous sim_run_asynchronous.wlf +run_test axi_hyper_tb_synchronous_one_phy sim_run_synchronous_one_phy.wlf +run_test hyperbus_cfg_regs_tb sim_run_cfg_regs.wlf quit -code $regression_failed -f diff --git a/src/backend/hyperbus_backend.sv b/src/backend/hyperbus_backend.sv new file mode 100644 index 0000000..f7349c8 --- /dev/null +++ b/src/backend/hyperbus_backend.sv @@ -0,0 +1,255 @@ +// Copyright 2023 ETH Zurich and University of Bologna. +// Solderpad Hardware License, Version 0.51, see LICENSE for details. +// SPDX-License-Identifier: SHL-0.51 + +`include "common_cells/assertions.svh" +`include "common_cells/registers.svh" + +module hyperbus_backend #( + parameter int unsigned NumChips = -1, + parameter int unsigned NumPhys = 2, + parameter int unsigned StartupCycles = 60000, + parameter int unsigned SyncStages = 2, + parameter type hyper_rx_t = logic, + parameter type hyper_tx_t = logic, + parameter type hyper_req_t = logic, + parameter type hyper_rsp_t = logic +) ( + input logic clk_i, + input logic rst_ni, +`ifdef TARGET_XILINX + input logic clk_ref200_i, +`endif + input logic test_mode_i, + + input hyperbus_pkg::phy_cfg_t cfg_apply_i, + input logic cfg_apply_valid_i, + output logic cfg_apply_ready_o, + + output logic busy_o, + + input hyper_req_t req_i, + output hyper_rsp_t rsp_o, + + output logic [NumPhys-1:0][NumChips-1:0] hyper_cs_no, + output logic [NumPhys-1:0] hyper_ck_o, + output logic [NumPhys-1:0] hyper_ck_no, + output logic [NumPhys-1:0] hyper_rwds_o, + input logic [NumPhys-1:0] hyper_rwds_i, + output logic [NumPhys-1:0] hyper_rwds_oe_o, + input logic [NumPhys-1:0][7:0] hyper_dq_i, + output logic [NumPhys-1:0][7:0] hyper_dq_o, + output logic [NumPhys-1:0] hyper_dq_oe_o, + output logic [NumPhys-1:0] hyper_reset_no +); + + hyperbus_pkg::phy_cfg_t cfg_q; + logic cfg_apply_accepted; + logic phy_busy_any; + logic clk_tx; + + `ASSERT_INIT(NumChipsValid, NumChips >= 1 && NumChips <= 8) + `ASSERT_INIT(NumPhysValid, NumPhys == 1 || NumPhys == 2) + `ASSERT_INIT(SyncStagesValid, SyncStages >= 2) + + assign cfg_apply_ready_o = ~phy_busy_any; + assign cfg_apply_accepted = cfg_apply_valid_i && cfg_apply_ready_o; + assign busy_o = phy_busy_any || cfg_apply_valid_i; + + `FFLARN(cfg_q, cfg_apply_i, cfg_apply_accepted, '0, clk_i, rst_ni) + + hyperbus_tx_clk_delay i_tx_clk_delay ( + .rst_ni, +`ifdef TARGET_XILINX + .clk_ref200_i, +`endif + .clk_i, + .in_i ( clk_i ), + .delay_i ( cfg_q.t_tx_clk_delay ), + .out_o ( clk_tx ) + ); + + if (NumPhys == 2) begin : gen_dual_phy + hyperbus_pkg::phy_rx_t [NumPhys-1:0] phy_rx; + hyperbus_pkg::phy_rx_t [NumPhys-1:0] buffered_rx; + logic [NumPhys-1:0] phy_rx_valid; + logic [NumPhys-1:0] phy_rx_ready; + logic [NumPhys-1:0] buffered_rx_valid; + logic [NumPhys-1:0][1:0] buffered_rx_usage; + logic [NumPhys-1:0] phy_tx_ready; + logic [NumPhys-1:0] phy_tx_valid; + logic [NumPhys-1:0] phy_cmd_ready; + logic [NumPhys-1:0] phy_cmd_valid; + logic [NumPhys-1:0] phy_wrsp_valid; + logic [NumPhys-1:0] phy_wrsp_error; + logic [NumPhys-1:0] phy_wrsp_ready; + logic [NumPhys-1:0] phy_enable; + logic [NumPhys-1:0] phy_busy; + logic [NumPhys-1:0] phy_active_q; + logic [NumPhys-1:0] tx_fork_valid; + logic [NumPhys-1:0] cmd_fork_valid; + logic [NumPhys-1:0] rx_join_ready; + logic active_change; + + assign phy_enable = cfg_q.dual_phy ? '1 : 2'b01; + assign active_change = phy_active_q != phy_enable; + `FFLARN(phy_active_q, phy_enable | phy_busy, + active_change && (buffered_rx_usage == '0), '1, clk_i, rst_ni) + + assign phy_busy_any = |phy_busy; + assign rsp_o.rx.error = |({buffered_rx[1].error, buffered_rx[0].error} & + phy_active_q); + assign rsp_o.rx.last = &({buffered_rx[1].last, buffered_rx[0].last} | + ~phy_active_q); + assign rsp_o.wrsp.error = |(phy_wrsp_error & phy_active_q); + + stream_fork #( + .N_OUP ( NumPhys ) + ) i_tx_fork ( + .clk_i, + .rst_ni, + .valid_i ( req_i.tx_valid ), + .ready_o ( rsp_o.tx_ready ), + .valid_o ( tx_fork_valid ), + .ready_i ( phy_tx_ready | ~phy_active_q ) + ); + + assign phy_tx_valid = tx_fork_valid & phy_active_q; + + stream_fork #( + .N_OUP ( NumPhys ) + ) i_cmd_fork ( + .clk_i, + .rst_ni, + .valid_i ( req_i.cmd_valid && !active_change ), + .ready_o ( rsp_o.cmd_ready ), + .valid_o ( cmd_fork_valid ), + .ready_i ( phy_cmd_ready | ~phy_active_q ) + ); + + assign phy_cmd_valid = cmd_fork_valid & phy_active_q; + + stream_join #( + .N_INP ( NumPhys ) + ) i_rx_join ( + .inp_valid_i ( buffered_rx_valid | ~phy_active_q ), + .inp_ready_o ( rx_join_ready ), + .oup_valid_o ( rsp_o.rx_valid ), + .oup_ready_i ( req_i.rx_ready ) + ); + + stream_join #( + .N_INP ( NumPhys ) + ) i_wrsp_join ( + .inp_valid_i ( phy_wrsp_valid | ~phy_active_q ), + .inp_ready_o ( phy_wrsp_ready ), + .oup_valid_o ( rsp_o.wrsp_valid ), + .oup_ready_i ( req_i.wrsp_ready ) + ); + + for (genvar i = 0; i < NumPhys; i++) begin : gen_phy + assign rsp_o.rx.data[i*16 +: 16] = buffered_rx[i].data; + + stream_fifo #( + .FALL_THROUGH ( 1'b0 ), + .DEPTH ( 4 ), + .T ( hyperbus_pkg::phy_rx_t ) + ) i_rx_fifo ( + .clk_i, + .rst_ni, + .flush_i ( 1'b0 ), + .testmode_i ( 1'b0 ), + .usage_o ( buffered_rx_usage[i] ), + .data_i ( phy_rx[i] ), + .valid_i ( phy_rx_valid[i] ), + .ready_o ( phy_rx_ready[i] ), + .data_o ( buffered_rx[i] ), + .valid_o ( buffered_rx_valid[i] ), + .ready_i ( rx_join_ready[i] ) + ); + + hyperbus_phy #( + .NumChips ( NumChips ), + .StartupCycles ( StartupCycles ), + .NumPhys ( NumPhys ), + .SyncStages ( SyncStages ) + ) i_phy ( + .clk_i, + .clk_tx_i ( clk_tx ), + .rst_ni, + .test_mode_i, + .cfg_i ( cfg_q ), + .busy_o ( phy_busy[i] ), + .rx_data_o ( phy_rx[i].data ), + .rx_last_o ( phy_rx[i].last ), + .rx_error_o ( phy_rx[i].error ), + .rx_valid_o ( phy_rx_valid[i] ), + .rx_ready_i ( phy_rx_ready[i] ), + .tx_data_i ( req_i.tx.data[16*i +: 16] ), + .tx_strb_i ( req_i.tx.strb[2*i +: 2] ), + .tx_last_i ( req_i.tx.last ), + .tx_valid_i ( phy_tx_valid[i] ), + .tx_ready_o ( phy_tx_ready[i] ), + .b_error_o ( phy_wrsp_error[i] ), + .b_valid_o ( phy_wrsp_valid[i] ), + .b_ready_i ( phy_wrsp_ready[i] ), + .trans_i ( req_i.cmd.trans ), + .trans_cs_i ( req_i.cmd.cs ), + .trans_valid_i ( phy_cmd_valid[i] ), + .trans_ready_o ( phy_cmd_ready[i] ), + .hyper_cs_no ( hyper_cs_no[i] ), + .hyper_ck_o ( hyper_ck_o[i] ), + .hyper_ck_no ( hyper_ck_no[i] ), + .hyper_rwds_o ( hyper_rwds_o[i] ), + .hyper_rwds_i ( hyper_rwds_i[i] ), + .hyper_rwds_oe_o( hyper_rwds_oe_o[i] ), + .hyper_dq_i ( hyper_dq_i[i] ), + .hyper_dq_o ( hyper_dq_o[i] ), + .hyper_dq_oe_o ( hyper_dq_oe_o[i] ), + .hyper_reset_no ( hyper_reset_no[i] ) + ); + end + end else begin : gen_single_phy + hyperbus_phy #( + .NumChips ( NumChips ), + .StartupCycles ( StartupCycles ), + .NumPhys ( NumPhys ), + .SyncStages ( SyncStages ) + ) i_phy ( + .clk_i, + .clk_tx_i ( clk_tx ), + .rst_ni, + .test_mode_i, + .cfg_i ( cfg_q ), + .busy_o ( phy_busy_any ), + .rx_data_o ( rsp_o.rx.data ), + .rx_last_o ( rsp_o.rx.last ), + .rx_error_o ( rsp_o.rx.error ), + .rx_valid_o ( rsp_o.rx_valid ), + .rx_ready_i ( req_i.rx_ready ), + .tx_data_i ( req_i.tx.data ), + .tx_strb_i ( req_i.tx.strb ), + .tx_last_i ( req_i.tx.last ), + .tx_valid_i ( req_i.tx_valid ), + .tx_ready_o ( rsp_o.tx_ready ), + .b_error_o ( rsp_o.wrsp.error ), + .b_valid_o ( rsp_o.wrsp_valid ), + .b_ready_i ( req_i.wrsp_ready ), + .trans_i ( req_i.cmd.trans ), + .trans_cs_i ( req_i.cmd.cs ), + .trans_valid_i ( req_i.cmd_valid ), + .trans_ready_o ( rsp_o.cmd_ready ), + .hyper_cs_no ( hyper_cs_no ), + .hyper_ck_o ( hyper_ck_o ), + .hyper_ck_no ( hyper_ck_no ), + .hyper_rwds_o ( hyper_rwds_o ), + .hyper_rwds_i ( hyper_rwds_i ), + .hyper_rwds_oe_o( hyper_rwds_oe_o ), + .hyper_dq_i ( hyper_dq_i ), + .hyper_dq_o ( hyper_dq_o ), + .hyper_dq_oe_o ( hyper_dq_oe_o ), + .hyper_reset_no ( hyper_reset_no ) + ); + end + +endmodule diff --git a/src/hyperbus_clock_diff_out.sv b/src/backend/hyperbus_clock_diff_out.sv similarity index 100% rename from src/hyperbus_clock_diff_out.sv rename to src/backend/hyperbus_clock_diff_out.sv diff --git a/src/hyperbus_ddr_out.sv b/src/backend/hyperbus_ddr_out.sv similarity index 100% rename from src/hyperbus_ddr_out.sv rename to src/backend/hyperbus_ddr_out.sv diff --git a/src/hyperbus_delay.sv b/src/backend/hyperbus_delay.sv similarity index 100% rename from src/hyperbus_delay.sv rename to src/backend/hyperbus_delay.sv diff --git a/src/hyperbus_phy.sv b/src/backend/hyperbus_phy.sv similarity index 90% rename from src/hyperbus_phy.sv rename to src/backend/hyperbus_phy.sv index 11c9f84..c531e67 100644 --- a/src/hyperbus_phy.sv +++ b/src/backend/hyperbus_phy.sv @@ -18,7 +18,7 @@ module hyperbus_phy import hyperbus_pkg::*; #( parameter int unsigned StartupCycles = 300 /*us*/ * 200 /*MHz*/ // Conservative maximum frequency estimate )( input logic clk_i, - input logic clk_i_90, + input logic clk_tx_i, input logic rst_ni, input logic test_mode_i, // Config registers @@ -59,9 +59,17 @@ module hyperbus_phy import hyperbus_pkg::*; #( output logic hyper_reset_no ); - logic [1:0] phys_in_use; + localparam int unsigned RxFifoDepth = 2 ** RxFifoLogDepth; + // r_outstand_q includes samples still crossing the RWDS CDC, so its limit + // must not include the synchronizer depth again. Reserve two entries for + // stopping CK and the final RWDS capture at the clock-gating boundary. + localparam int unsigned RxFifoStopMargin = 2; + localparam int unsigned RxOutstandingLimit = (RxFifoDepth > RxFifoStopMargin) ? + (RxFifoDepth - RxFifoStopMargin) : 1; - assign phys_in_use = (NumPhys==2) ? (cfg_i.phys_in_use + 1) : 1; + logic [1:0] words_per_beat; + + assign words_per_beat = (NumPhys == 2 && cfg_i.dual_phy) ? 2 : 1; // PHY state hyper_phy_state_t state_d, state_q; @@ -92,6 +100,7 @@ module hyperbus_phy import hyperbus_pkg::*; #( logic ctl_rclk_ena; logic ctl_rcnt_ena; logic ctl_wclk_ena; + logic rx_outstanding_room; // Command-address hyper_phy_ca_t ca; @@ -121,7 +130,7 @@ module hyperbus_phy import hyperbus_pkg::*; #( .SyncStages ( SyncStages ) ) i_trx ( .clk_i, - .clk_i_90, + .clk_tx_i, .rst_ni, .test_mode_i, .cs_i ( cs_q ), @@ -201,10 +210,12 @@ module hyperbus_phy import hyperbus_pkg::*; #( assign trx_rx_ready = rx_ready_i; assign rx_valid_o = trx_rx_valid & (r_outstand_q != '0); - // Suspend clock one cycle for every stall caused by upstream. - // This ensures that a sufficiently large RX FIFO will not overflow. - assign ctl_rclk_ena = ~(rx_valid_o & ~rx_ready_i); - // Disable incoming RWDS capture once all launched read words have drained. + assign rx_outstanding_room = r_outstand_q < RxOutstandingLimit; + // Suspend CK for visible downstream stalls and before the RWDS CDC FIFO can + // fill with already-launched, not-yet-drained read words. + assign ctl_rclk_ena = rx_outstanding_room & ~(rx_valid_o & ~rx_ready_i); + // Keep RWDS sampling enabled until all read words launched before a CS break + // have crossed back into the PHY clock domain. assign trx_rx_clk_reset = (state_q != Read) & (r_outstand_q == '0); // Counter for outstanding R responses @@ -224,7 +235,7 @@ module hyperbus_phy import hyperbus_pkg::*; #( // cfg_i.chip.en_latency_additional overwrites the sampled RWDS value. assign ctl_add_latency = trx_rwds_sample | cfg_i.chip.en_latency_additional; - assign ctl_tf_burst_last = (tf_q.burst == 1) || (tf_q.burst == phys_in_use); + assign ctl_tf_burst_last = (tf_q.burst == 1) || (tf_q.burst == words_per_beat); assign ctl_tf_burst_done = (tf_q.burst == 0); assign ctl_timer_rwr_done = (timer_q <= 3); @@ -265,9 +276,10 @@ module hyperbus_phy import hyperbus_pkg::*; #( Idle: begin trx_cs_ena = 1'b0; timer_d = timer_q; - // Signal ready for, pop next transfer if Write response sent - trans_ready_o = 1'b1; - if (trans_valid_i & ~b_pending_q & r_outstand_q == '0) begin + // Accept the next transfer only after pending responses and + // read samples from the previous segment have drained. + trans_ready_o = ~b_pending_q & (r_outstand_q == '0); + if (trans_valid_i & trans_ready_o) begin tf_d = trans_i; cs_d = trans_cs_i; add_latency_d = 1'b0; @@ -373,7 +385,7 @@ module hyperbus_phy import hyperbus_pkg::*; #( if (ctl_rclk_ena) begin trx_clk_ena = 1'b1; r_outstand_inc = 1'b1; - tf_d.burst = tf_q.burst - phys_in_use; + 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; @@ -394,7 +406,7 @@ module hyperbus_phy import hyperbus_pkg::*; #( // Dataflow handled outside FSM if (ctl_wclk_ena) begin trx_clk_ena = 1'b1; - tf_d.burst = tf_q.burst - phys_in_use; + tf_d.burst = tf_q.burst - words_per_beat; tf_d.address = tf_q.address + 1; if (ctl_tf_burst_last) begin b_pending_set = 1'b1; @@ -421,6 +433,10 @@ module hyperbus_phy import hyperbus_pkg::*; #( if (ctl_timer_rwr_done) begin if (ctl_tf_burst_done) begin state_d = Idle; + end else if (!tf_q.write && (r_outstand_q != '0)) begin + // Before starting the next read segment, let all samples + // from the previous segment drain and reset RWDS sampling. + timer_d = timer_q; end else begin state_d = SendCA; // Re-enable the io driver if we immediately start the @@ -434,7 +450,7 @@ module hyperbus_phy import hyperbus_pkg::*; #( // PHY state registers, including timer and transfer always_ff @(posedge clk_i or negedge rst_ni) begin : proc_ff_phy - if (~rst_ni) begin + if (!rst_ni) begin state_q <= Startup; timer_q <= StartupCycles; tf_q <= hyper_tf_t'{burst_type: 1'b1, default:'0}; diff --git a/src/hyperbus_trx.sv b/src/backend/hyperbus_trx.sv similarity index 82% rename from src/hyperbus_trx.sv rename to src/backend/hyperbus_trx.sv index cc4c79b..73fc128 100644 --- a/src/hyperbus_trx.sv +++ b/src/backend/hyperbus_trx.sv @@ -6,6 +6,8 @@ // Armin Berger // Stephan Keck +`include "common_cells/assertions.svh" + module hyperbus_trx #( parameter int unsigned NumChips = 2, parameter int unsigned RxFifoLogDepth = 3, @@ -13,7 +15,7 @@ module hyperbus_trx #( )( // Global signals input logic clk_i, - input logic clk_i_90, + input logic clk_tx_i, input logic rst_ni, input logic test_mode_i, @@ -48,9 +50,7 @@ module hyperbus_trx #( output logic hyper_reset_no ); - // 90-degree-shifted clocks generated with delay line logic tx_clk_ena_q; - logic tx_clk_90; logic rx_rwds_90; // Delayed clock enable synchronous with data @@ -59,7 +59,8 @@ module hyperbus_trx #( logic rx_rwds_clk_ena; logic rx_rwds_clk_orig; logic rx_rwds_clk; - logic rx_rwds_soft_rst; + logic rx_rwds_clk_n; + logic rx_capture_rst; logic [15:0] rx_rwds_fifo_in; logic rx_rwds_fifo_valid; logic rx_rwds_fifo_ready; @@ -71,14 +72,10 @@ module hyperbus_trx #( // TX + control // ================= - // Shift clock by 90 degrees - assign tx_clk_90 = clk_i_90; - - // 90deg-shifted differential output clock, sampling output bytes centrally - // TODO: tx_clk_ena_q to tx_clk_90 may need a constraint at the pins of this module - // specifically tx_clk_ena_q must arrive BEFORE tx_clk_90 otherwise the gating may fail + // The delayed differential output clock samples output bytes centrally. + // TODO: tx_clk_ena_q must arrive before clk_tx_i to avoid disturbing clock gating. hyperbus_clock_diff_out i_clock_diff_out ( - .in_i ( tx_clk_90 ), + .in_i ( clk_tx_i ), .en_i ( tx_clk_ena_q ), .out_o ( hyper_ck_o ), .out_no ( hyper_ck_no ) @@ -86,7 +83,7 @@ module hyperbus_trx #( // Synchronize output chip select to shifted differential output clock always_ff @(negedge clk_i or negedge rst_ni) begin : proc_ff_tx_shift90 - if (~rst_ni) hyper_cs_no <= '1; + if (!rst_ni) hyper_cs_no <= '1; else hyper_cs_no <= cs_ena_i ? ~cs_i : '1; end @@ -117,7 +114,7 @@ module hyperbus_trx #( // Delay output, clock enables to be synchronous with DDR-converted data // The delayed clock also ensures t_CSS is respected at the start, end of CS always_ff @(posedge clk_i or negedge rst_ni) begin : proc_ff_tx_delay - if(~rst_ni) begin + if (!rst_ni) begin hyper_rwds_oe_o <= 1'b0; hyper_dq_oe_o <= 1'b0; tx_clk_ena_q <= 1'b0; @@ -134,13 +131,13 @@ module hyperbus_trx #( // Sample RWDS for extra latency determination. always_ff @(posedge clk_i or negedge rst_ni) begin : proc_ff_rwds_sample - if (~rst_ni) rwds_sample_o <= '0; + if (!rst_ni) rwds_sample_o <= '0; else if (rwds_sample_ena_i) rwds_sample_o <= hyper_rwds_i; end // Set and Reset RX clock enable always_ff @(posedge clk_i or negedge rst_ni) begin : proc_ff_rx_delay - if (~rst_ni) rx_rwds_clk_ena <= 1'b0; + if (!rst_ni) rx_rwds_clk_ena <= 1'b0; else if (rx_clk_set_i) rx_rwds_clk_ena <= 1'b1; else if (rx_clk_reset_i) rx_rwds_clk_ena <= 1'b0; end @@ -148,7 +145,7 @@ module hyperbus_trx #( // Shift RWDS clock by 90 degrees `ifdef TARGET_XILINX hyperbus_rwds_delay i_delay_rx_rwds_90 ( - .rst_i ( ~rst_ni ), + .rst_i ( !rst_ni ), .clk_i, .in_i ( hyper_rwds_i ), .delay_i ( rx_clk_delay_i ), @@ -174,12 +171,12 @@ module hyperbus_trx #( // Reset RX state on async reset or on gated clock (whenever inactive) // TODO: is this safe? Replace with tech cells? - assign rx_rwds_soft_rst = ~rst_ni | (~rx_rwds_clk_ena & ~test_mode_i); + assign rx_capture_rst = !rst_ni || (!rx_rwds_clk_ena && !test_mode_i); // RX data is valid one cycle after each RX soft reset - always_ff @(posedge rx_rwds_clk or posedge rx_rwds_soft_rst) begin : proc_read_in_valid - if (rx_rwds_soft_rst) rx_rwds_fifo_valid <= 1'b0; - else rx_rwds_fifo_valid <= 1'b1; + always_ff @(posedge rx_rwds_clk or posedge rx_capture_rst) begin : proc_read_in_valid + if (rx_capture_rst) rx_rwds_fifo_valid <= 1'b0; + else rx_rwds_fifo_valid <= 1'b1; end // If testing, replace gated RWDS clock with primary (PHY) clock; @@ -197,9 +194,9 @@ module hyperbus_trx #( // Data input DDR conversion assign rx_rwds_fifo_in[7:0] = hyper_dq_i; - always @(posedge rx_rwds_clk or posedge rx_rwds_soft_rst) begin : proc_ff_ddr_in - if(rx_rwds_soft_rst) rx_rwds_fifo_in[15:8] <= '0; - else rx_rwds_fifo_in[15:8] <= hyper_dq_i; + always @(posedge rx_rwds_clk or posedge rx_capture_rst) begin : proc_ff_ddr_in + if (rx_capture_rst) rx_rwds_fifo_in[15:8] <= '0; + else rx_rwds_fifo_in[15:8] <= hyper_dq_i; end tc_clk_inverter i_rwds_clk_inverter ( @@ -227,10 +224,7 @@ module hyperbus_trx #( .dst_ready_i ( rx_ready_i ) ); - // assert that the FIFO does not drop data in simulation - `ifndef SYNTHESIS - always @(negedge rx_rwds_fifo_ready) assert(rx_rwds_fifo_ready) - else $error("%m: HyperBus RX FIFO must always be ready to receive data"); - `endif + `ASSERT(RxRwdsFifoReady, rx_rwds_fifo_ready, rx_rwds_clk_n, !rst_ni) + `ASSERT(RxCaptureControlExclusive, !(rx_clk_set_i && rx_clk_reset_i)) endmodule diff --git a/src/backend/hyperbus_tx_clk_delay.sv b/src/backend/hyperbus_tx_clk_delay.sv new file mode 100644 index 0000000..05c4393 --- /dev/null +++ b/src/backend/hyperbus_tx_clk_delay.sv @@ -0,0 +1,33 @@ +// Copyright 2026 ETH Zurich and University of Bologna. +// Solderpad Hardware License, Version 0.51, see LICENSE for details. +// SPDX-License-Identifier: SHL-0.51 + +module hyperbus_tx_clk_delay ( + input logic rst_ni, +`ifdef TARGET_XILINX + input logic clk_ref200_i, +`endif + input logic clk_i, + input logic in_i, + input logic [7:0] delay_i, + output logic out_o +); + +`ifdef TARGET_XILINX + hyperbus_clk_delay i_delay_tx_clk_90 ( + .rst_i ( ~rst_ni ), + .clk_ref200_i ( clk_ref200_i ), + .clk_i ( clk_i ), + .in_i ( in_i ), + .delay_i ( delay_i ), + .out_o ( out_o ) + ); +`else + hyperbus_delay i_delay_tx_clk_90 ( + .in_i ( in_i ), + .delay_i ( delay_i ), + .out_o ( out_o ) + ); +`endif + +endmodule diff --git a/src/hyperbus.sv b/src/hyperbus.sv deleted file mode 100644 index e0889db..0000000 --- a/src/hyperbus.sv +++ /dev/null @@ -1,312 +0,0 @@ -// Copyright 2023 ETH Zurich and University of Bologna. -// Solderpad Hardware License, Version 0.51, see LICENSE for details. -// SPDX-License-Identifier: SHL-0.51 -// -// Thomas Benz -// Paul Scheffler -// Luca Valente - -module hyperbus #( - parameter int unsigned NumChips = -1, - parameter int unsigned NumPhys = 2, - parameter bit UsePhyClkDivider = 1, - 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 type axi_w_chan_t = logic, - parameter type axi_b_chan_t = logic, - parameter type axi_ar_chan_t = logic, - parameter type axi_r_chan_t = logic, - parameter type axi_aw_chan_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, - // The below have sensible defaults, but should be set on integration! - parameter int unsigned RxFifoLogDepth = 3, - parameter int unsigned TxFifoLogDepth = 3, - parameter int unsigned PhyStartupCycles = 300 * 200, /* us*MHz */ // Conservative maximum frequency estimate - parameter int unsigned SyncStages = 2 -) ( - input logic clk_phy_i, -`ifdef TARGET_XILINX - input logic clk_ref200_i, // only used for Xilinx delay lines -`endif - input logic rst_phy_ni, - input logic clk_sys_i, - input logic rst_sys_ni, - input logic test_mode_i, - // AXI bus - input axi_req_t axi_req_i, - output axi_rsp_t axi_rsp_o, - // Reg bus - input reg_req_t reg_req_i, - output reg_rsp_t reg_rsp_o, - // Physical interace: facing HyperBus PADs - output logic [NumPhys-1:0][NumChips-1:0] hyper_cs_no, - output logic [NumPhys-1:0] hyper_ck_o, - output logic [NumPhys-1:0] hyper_ck_no, - output logic [NumPhys-1:0] hyper_rwds_o, - input logic [NumPhys-1:0] hyper_rwds_i, - output logic [NumPhys-1:0] hyper_rwds_oe_o, - input logic [NumPhys-1:0][7:0] hyper_dq_i, - output logic [NumPhys-1:0][7:0] hyper_dq_o, - output logic [NumPhys-1:0] hyper_dq_oe_o, - output logic [NumPhys-1:0] hyper_reset_no -); - - typedef struct packed { - logic [(16*NumPhys)-1:0] data; - logic last; - logic [(2*NumPhys)-1:0] strb; // mask data - } hyper_tx_t; - - typedef struct packed { - logic [(16*NumPhys)-1:0] data; - logic last; - logic error; - } hyper_rx_t; - - // Combined transfer type for CDC - typedef struct packed { - hyperbus_pkg::hyper_tf_t trans; - logic [NumChips-1:0] cs; - } tf_cdc_t; - - - logic clk_phy_0, clk_phy_90, rst_phy; - - // Register file - hyperbus_pkg::frontend_cfg_t frontend_cfg; - hyperbus_pkg::phy_cfg_t phy_cfg; - axi_rule_t [NumChips-1:0] chip_rules; - logic trans_active; - - // AXI slave - hyper_rx_t axi_rx; - logic axi_rx_valid; - logic axi_rx_ready; - hyper_tx_t axi_tx; - logic axi_tx_valid; - logic axi_tx_ready; - logic axi_b_error; - logic axi_b_valid; - logic axi_b_ready; - tf_cdc_t axi_tf_cdc; - logic axi_trans_valid; - logic axi_trans_ready; - - // PHY - hyper_rx_t phy_rx; - logic phy_rx_valid; - logic phy_rx_ready; - hyper_tx_t phy_tx; - logic phy_tx_valid; - logic phy_tx_ready; - logic phy_b_error; - logic phy_b_valid; - logic phy_b_ready; - tf_cdc_t phy_tf_cdc; - logic phy_trans_valid; - logic phy_trans_ready; - - // Config register File - hyperbus_cfg_regs #( - .NumChips ( NumChips ), - .NumPhys ( NumPhys ), - .RegDataWidth ( RegDataWidth ), - .RegAddrWidth ( RegAddrWidth ), - .CapabilityFeatures ( '0 ), - .reg_req_t ( reg_req_t ), - .reg_rsp_t ( reg_rsp_t ), - .rule_t ( axi_rule_t ) - ) i_cfg_regs ( - .clk_i ( clk_sys_i ), - .rst_ni ( rst_sys_ni ), - .reg_req_i ( reg_req_i ), - .reg_rsp_o ( reg_rsp_o ), - .frontend_cfg_o ( frontend_cfg ), - .phy_cfg_o ( phy_cfg ), - .chip_rules_o ( chip_rules ), - .trans_active_i ( trans_active ), - .cfg_busy_i ( trans_active ), - .decode_error_i ( 1'b0 ), - .cfg_dirty_i ( 1'b0 ), - .flush_req_o ( ), - .apply_req_o ( ) - ); - - // AXI slave interfacing PHY - hyperbus_axi #( - .AxiDataWidth ( AxiDataWidth ), - .AxiAddrWidth ( AxiAddrWidth ), - .AxiIdWidth ( AxiIdWidth ), - .AxiUserWidth ( AxiUserWidth ), - .axi_req_t ( axi_req_t ), - .axi_rsp_t ( axi_rsp_t ), - .NumChips ( NumChips ), - .NumPhys ( NumPhys ), - .hyper_rx_t ( hyper_rx_t ), - .hyper_tx_t ( hyper_tx_t ), - .rule_t ( axi_rule_t ) - ) i_axi_slave ( - .clk_i ( clk_sys_i ), - .rst_ni ( rst_sys_ni ), - - .axi_req_i ( axi_req_i ), - .axi_rsp_o ( axi_rsp_o ), - - .rx_i ( axi_rx ), - .rx_valid_i ( axi_rx_valid ), - .rx_ready_o ( axi_rx_ready ), - .tx_o ( axi_tx ), - .tx_valid_o ( axi_tx_valid ), - .tx_ready_i ( axi_tx_ready ), - .b_error_i ( axi_b_error ), - .b_valid_i ( axi_b_valid ), - .b_ready_o ( axi_b_ready ), - .trans_o ( axi_tf_cdc.trans ), - .trans_cs_o ( axi_tf_cdc.cs ), - .trans_valid_o ( axi_trans_valid ), - .trans_ready_i ( axi_trans_ready ), - - .frontend_cfg_i ( frontend_cfg ), - .chip_rules_i ( chip_rules ), - .trans_active_o ( trans_active ) - ); - - if(UsePhyClkDivider == 1'b1) begin : clock_generator - hyperbus_clk_gen ddr_clk ( - .clk_i ( clk_phy_i ), - .rst_ni ( rst_phy_ni ), - .clk0_o ( clk_phy_0 ), - .clk90_o ( clk_phy_90 ), - .clk180_o ( ), - .clk270_o ( ), - .rst_no ( rst_phy ) - ); - end else begin - assign clk_phy_0 = clk_phy_i; - assign clk_phy_90 = '0; - assign rst_phy = rst_phy_ni; - end - - hyperbus_phy_if #( - .UsePhyClkDivider ( UsePhyClkDivider ), - .NumChips ( NumChips ), - .NumPhys ( NumPhys ), - .StartupCycles ( PhyStartupCycles ), - .hyper_rx_t ( hyper_rx_t ), - .hyper_tx_t ( hyper_tx_t ), - .SyncStages ( SyncStages ) - ) i_phy ( - .clk_phy_i ( clk_phy_0 ), - .clk_phy_i_90 ( clk_phy_90 ), -`ifdef TARGET_XILINX - .clk_ref200_i ( clk_ref200_i ), -`endif - .rst_phy_ni ( rst_phy ), - .test_mode_i ( test_mode_i ), - - .cfg_i ( phy_cfg ), - - .rx_o ( phy_rx ), - .rx_valid_o ( phy_rx_valid ), - .rx_ready_i ( phy_rx_ready ), - .tx_i ( phy_tx ), - .tx_valid_i ( phy_tx_valid ), - .tx_ready_o ( phy_tx_ready ), - .b_error_o ( phy_b_error ), - .b_valid_o ( phy_b_valid ), - .b_ready_i ( phy_b_ready ), - .trans_i ( phy_tf_cdc.trans ), - .trans_cs_i ( phy_tf_cdc.cs ), - .trans_valid_i ( phy_trans_valid ), - .trans_ready_o ( phy_trans_ready ), - - .hyper_cs_no ( hyper_cs_no ), - .hyper_ck_o ( hyper_ck_o ), - .hyper_ck_no ( hyper_ck_no ), - .hyper_rwds_o ( hyper_rwds_o ), - .hyper_rwds_i ( hyper_rwds_i ), - .hyper_rwds_oe_o( hyper_rwds_oe_o ), - .hyper_dq_i ( hyper_dq_i ), - .hyper_dq_o ( hyper_dq_o ), - .hyper_dq_oe_o ( hyper_dq_oe_o ), - .hyper_reset_no ( hyper_reset_no ) - ); - - cdc_2phase #( - .T ( tf_cdc_t ) - ) i_cdc_2phase_trans ( - .src_rst_ni ( rst_sys_ni ), - .src_clk_i ( clk_sys_i ), - .src_data_i ( axi_tf_cdc ), - .src_valid_i ( axi_trans_valid ), - .src_ready_o ( axi_trans_ready ), - - .dst_rst_ni ( rst_phy ), - .dst_clk_i ( clk_phy_0 ), - .dst_data_o ( phy_tf_cdc ), - .dst_valid_o ( phy_trans_valid ), - .dst_ready_i ( phy_trans_ready ) - ); - - cdc_2phase #( - .T ( logic ) - ) i_cdc_2phase_b ( - .src_rst_ni ( rst_phy ), - .src_clk_i ( clk_phy_0 ), - .src_data_i ( phy_b_error ), - .src_valid_i ( phy_b_valid ), - .src_ready_o ( phy_b_ready ), - - .dst_rst_ni ( rst_sys_ni ), - .dst_clk_i ( clk_sys_i ), - .dst_data_o ( axi_b_error ), - .dst_valid_o ( axi_b_valid ), - .dst_ready_i ( axi_b_ready ) - ); - - // Write data, TX CDC FIFO - cdc_fifo_gray #( - .T ( hyper_tx_t ), - .LOG_DEPTH ( TxFifoLogDepth ) - ) i_cdc_fifo_tx ( - .src_rst_ni ( rst_sys_ni ), - .src_clk_i ( clk_sys_i ), - .src_data_i ( axi_tx ), - .src_valid_i ( axi_tx_valid ), - .src_ready_o ( axi_tx_ready ), - - .dst_rst_ni ( rst_phy ), - .dst_clk_i ( clk_phy_0 ), - .dst_data_o ( phy_tx ), - .dst_valid_o ( phy_tx_valid ), - .dst_ready_i ( phy_tx_ready ) - ); - - // Read data, RX CDC FIFO - cdc_fifo_gray #( - .T ( hyper_rx_t ), - .LOG_DEPTH ( RxFifoLogDepth ) - ) i_cdc_fifo_rx ( - .src_rst_ni ( rst_phy ), - .src_clk_i ( clk_phy_0 ), - .src_data_i ( phy_rx ), - .src_valid_i ( phy_rx_valid ), - .src_ready_o ( phy_rx_ready ), - - .dst_rst_ni ( rst_sys_ni ), - .dst_clk_i ( clk_sys_i ), - .dst_data_o ( axi_rx ), - .dst_valid_o ( axi_rx_valid ), - .dst_ready_i ( axi_rx_ready ) - ); - - -endmodule : hyperbus diff --git a/src/hyperbus_async_bridge.sv b/src/hyperbus_async_bridge.sv new file mode 100644 index 0000000..9d97ed2 --- /dev/null +++ b/src/hyperbus_async_bridge.sv @@ -0,0 +1,166 @@ +// Copyright 2023 ETH Zurich and University of Bologna. +// Solderpad Hardware License, Version 0.51, see LICENSE for details. +// SPDX-License-Identifier: SHL-0.51 + +`include "common_cells/assertions.svh" +`include "common_cells/registers.svh" + +module hyperbus_async_bridge #( + parameter int unsigned RxFifoLogDepth = 3, + parameter int unsigned TxFifoLogDepth = 3, + parameter int unsigned CdcSyncStages = 3, + parameter type hyper_rx_t = logic, + parameter type hyper_tx_t = logic, + parameter type hyper_wrsp_t = logic, + parameter type hyper_cmd_t = logic, + parameter type hyper_req_t = logic, + parameter type hyper_rsp_t = logic +) ( + input logic clk_sys_i, + input logic rst_sys_ni, + input logic clk_phy_i, + input logic rst_phy_ni, + + input hyperbus_pkg::phy_cfg_t cfg_apply_i, + input logic frontend_cfg_apply_valid_i, + output logic frontend_cfg_apply_ready_o, + output logic frontend_cfg_apply_done_o, + + input hyper_req_t frontend_req_i, + output hyper_rsp_t frontend_rsp_o, + output hyper_req_t backend_req_o, + input hyper_rsp_t backend_rsp_i, + + output hyperbus_pkg::phy_cfg_t cfg_apply_o, + output logic cfg_apply_valid_o, + input logic cfg_apply_ready_i +); + + logic cfg_apply_accepted; + logic cfg_apply_pending_d, cfg_apply_pending_q; + + `ASSERT_INIT(CdcSyncStagesValid, CdcSyncStages >= 3) + // Preserve FIFO elasticity beyond the round-trip pointer synchronization latency. + `ASSERT_INIT(TxCdcFifoDepthValid, (1 << TxFifoLogDepth) > (2 * CdcSyncStages)) + `ASSERT_INIT(RxCdcFifoDepthValid, (1 << RxFifoLogDepth) > (2 * CdcSyncStages)) + + assign cfg_apply_accepted = + frontend_cfg_apply_valid_i && frontend_cfg_apply_ready_o; + assign frontend_cfg_apply_done_o = + cfg_apply_pending_q && frontend_cfg_apply_ready_o; + + always_comb begin : proc_cfg_apply_pending + cfg_apply_pending_d = cfg_apply_pending_q; + + if (cfg_apply_accepted) begin + cfg_apply_pending_d = 1'b1; + end else if (frontend_cfg_apply_done_o) begin + cfg_apply_pending_d = 1'b0; + end + end + + `FFARN(cfg_apply_pending_q, cfg_apply_pending_d, 1'b0, clk_sys_i, rst_sys_ni) + + cdc_2phase_clearable #( + .T ( hyperbus_pkg::phy_cfg_t ), + .SYNC_STAGES ( CdcSyncStages ) + ) i_cdc_cfg ( + .src_rst_ni ( rst_sys_ni ), + .src_clk_i ( clk_sys_i ), + .src_clear_i ( 1'b0 ), + .src_clear_pending_o ( ), + .src_data_i ( cfg_apply_i ), + .src_valid_i ( frontend_cfg_apply_valid_i ), + .src_ready_o ( frontend_cfg_apply_ready_o ), + .dst_rst_ni ( rst_phy_ni ), + .dst_clk_i ( clk_phy_i ), + .dst_clear_i ( 1'b0 ), + .dst_clear_pending_o ( ), + .dst_data_o ( cfg_apply_o ), + .dst_valid_o ( cfg_apply_valid_o ), + .dst_ready_i ( cfg_apply_ready_i ) + ); + + cdc_2phase_clearable #( + .T ( hyper_cmd_t ), + .SYNC_STAGES ( CdcSyncStages ) + ) i_cdc_cmd ( + .src_rst_ni ( rst_sys_ni ), + .src_clk_i ( clk_sys_i ), + .src_clear_i ( 1'b0 ), + .src_clear_pending_o ( ), + .src_data_i ( frontend_req_i.cmd ), + .src_valid_i ( frontend_req_i.cmd_valid ), + .src_ready_o ( frontend_rsp_o.cmd_ready ), + .dst_rst_ni ( rst_phy_ni ), + .dst_clk_i ( clk_phy_i ), + .dst_clear_i ( 1'b0 ), + .dst_clear_pending_o ( ), + .dst_data_o ( backend_req_o.cmd ), + .dst_valid_o ( backend_req_o.cmd_valid ), + .dst_ready_i ( backend_rsp_i.cmd_ready ) + ); + + cdc_2phase_clearable #( + .T ( hyper_wrsp_t ), + .SYNC_STAGES ( CdcSyncStages ) + ) i_cdc_wrsp ( + .src_rst_ni ( rst_phy_ni ), + .src_clk_i ( clk_phy_i ), + .src_clear_i ( 1'b0 ), + .src_clear_pending_o ( ), + .src_data_i ( backend_rsp_i.wrsp ), + .src_valid_i ( backend_rsp_i.wrsp_valid ), + .src_ready_o ( backend_req_o.wrsp_ready ), + .dst_rst_ni ( rst_sys_ni ), + .dst_clk_i ( clk_sys_i ), + .dst_clear_i ( 1'b0 ), + .dst_clear_pending_o ( ), + .dst_data_o ( frontend_rsp_o.wrsp ), + .dst_valid_o ( frontend_rsp_o.wrsp_valid ), + .dst_ready_i ( frontend_req_i.wrsp_ready ) + ); + + cdc_fifo_gray_clearable #( + .T ( hyper_tx_t ), + .LOG_DEPTH ( TxFifoLogDepth ), + .SYNC_STAGES ( CdcSyncStages ) + ) i_cdc_fifo_tx ( + .src_rst_ni ( rst_sys_ni ), + .src_clk_i ( clk_sys_i ), + .src_clear_i ( 1'b0 ), + .src_clear_pending_o ( ), + .src_data_i ( frontend_req_i.tx ), + .src_valid_i ( frontend_req_i.tx_valid ), + .src_ready_o ( frontend_rsp_o.tx_ready ), + .dst_rst_ni ( rst_phy_ni ), + .dst_clk_i ( clk_phy_i ), + .dst_clear_i ( 1'b0 ), + .dst_clear_pending_o ( ), + .dst_data_o ( backend_req_o.tx ), + .dst_valid_o ( backend_req_o.tx_valid ), + .dst_ready_i ( backend_rsp_i.tx_ready ) + ); + + cdc_fifo_gray_clearable #( + .T ( hyper_rx_t ), + .LOG_DEPTH ( RxFifoLogDepth ), + .SYNC_STAGES ( CdcSyncStages ) + ) i_cdc_fifo_rx ( + .src_rst_ni ( rst_phy_ni ), + .src_clk_i ( clk_phy_i ), + .src_clear_i ( 1'b0 ), + .src_clear_pending_o ( ), + .src_data_i ( backend_rsp_i.rx ), + .src_valid_i ( backend_rsp_i.rx_valid ), + .src_ready_o ( backend_req_o.rx_ready ), + .dst_rst_ni ( rst_sys_ni ), + .dst_clk_i ( clk_sys_i ), + .dst_clear_i ( 1'b0 ), + .dst_clear_pending_o ( ), + .dst_data_o ( frontend_rsp_o.rx ), + .dst_valid_o ( frontend_rsp_o.rx_valid ), + .dst_ready_i ( frontend_req_i.rx_ready ) + ); + +endmodule diff --git a/src/hyperbus_asynchronous.sv b/src/hyperbus_asynchronous.sv new file mode 100644 index 0000000..376890a --- /dev/null +++ b/src/hyperbus_asynchronous.sv @@ -0,0 +1,247 @@ +// Copyright 2023 ETH Zurich and University of Bologna. +// Solderpad Hardware License, Version 0.51, see LICENSE for details. +// SPDX-License-Identifier: SHL-0.51 + +`include "hyperbus/typedef.svh" +`include "common_cells/assertions.svh" + +module hyperbus_asynchronous #( + parameter int unsigned NumChips = -1, + parameter int unsigned NumPhys = 2, + parameter int unsigned AxiAddrWidth = -1, + parameter int unsigned AxiDataWidth = -1, + parameter int unsigned AxiIdWidth = -1, + parameter int unsigned AxiUserWidth = -1, + parameter type axi_req_t = logic, + parameter type axi_rsp_t = logic, + parameter int unsigned RegDataWidth = -1, + parameter type reg_req_t = logic, + parameter type reg_rsp_t = logic, + parameter type axi_rule_t = logic, + parameter int unsigned RxFifoLogDepth = 3, + parameter int unsigned TxFifoLogDepth = 3, + parameter int unsigned PhyStartupCycles = 300 * 200, + parameter int unsigned SyncStages = 2, + parameter int unsigned CdcSyncStages = 3 +) ( + input logic clk_sys_i, + input logic rst_sys_ni, + input logic clk_phy_i, + input logic rst_phy_ni, +`ifdef TARGET_XILINX + input logic clk_ref200_i, +`endif + input logic test_mode_i, + + input axi_req_t axi_req_i, + output axi_rsp_t axi_rsp_o, + + input reg_req_t reg_req_i, + output reg_rsp_t reg_rsp_o, + + output logic [NumPhys-1:0][NumChips-1:0] hyper_cs_no, + output logic [NumPhys-1:0] hyper_ck_o, + output logic [NumPhys-1:0] hyper_ck_no, + output logic [NumPhys-1:0] hyper_rwds_o, + input logic [NumPhys-1:0] hyper_rwds_i, + output logic [NumPhys-1:0] hyper_rwds_oe_o, + input logic [NumPhys-1:0][7:0] hyper_dq_i, + output logic [NumPhys-1:0][7:0] hyper_dq_o, + output logic [NumPhys-1:0] hyper_dq_oe_o, + output logic [NumPhys-1:0] hyper_reset_no +); + + `ASSERT_INIT(AxiAddrWidthValid, AxiAddrWidth >= $clog2(AxiDataWidth / 8)) + + typedef logic [AxiAddrWidth-1:0] host_addr_t; + typedef logic [AxiDataWidth-1:0] host_data_t; + typedef logic [AxiDataWidth/8-1:0] host_strb_t; + `HYPERBUS_TYPEDEF_HOST_ALL_CT(host, host_addr_t, host_data_t, host_strb_t) + `HYPERBUS_TYPEDEF_LINK_ALL_CT(hyper, NumPhys, NumChips) + + logic clk_backend; + logic rst_backend_n; + hyperbus_pkg::phy_cfg_t frontend_cfg_apply; + logic frontend_cfg_apply_valid; + logic frontend_cfg_apply_ready; + logic frontend_cfg_apply_done; + logic frontend_clock_div_apply_valid; + logic frontend_drain; + logic host_idle; + hyperbus_pkg::phy_cfg_t backend_cfg_apply; + logic backend_cfg_apply_valid; + logic backend_cfg_apply_ready; + + hyperbus_pkg::frontend_cfg_t frontend_cfg; + axi_rule_t [NumChips-1:0] frontend_chip_rules; + + host_req_t host_req; + host_rsp_t host_rsp; + hyper_req_t midend_req; + hyper_rsp_t midend_rsp; + logic midend_trans_active; + logic midend_decode_error; + hyper_req_t backend_req; + hyper_rsp_t backend_rsp; + + logic rst_phy_async_n; + + // Clearable CDCs permit an independent PHY reset without creating phantom + // transfers. Such a reset can discard a pending transfer and is therefore + // only a supported software operation while the controller is idle. + assign rst_phy_async_n = rst_sys_ni & rst_phy_ni; + + assign clk_backend = clk_phy_i; + + rstgen i_rstgen_phy ( + .clk_i ( clk_backend ), + .rst_ni ( rst_phy_async_n ), + .test_mode_i ( test_mode_i ), + .rst_no ( rst_backend_n ), + .init_no ( ) + ); + + hyperbus_cfg_frontend #( + .NumChips ( NumChips ), + .NumPhys ( NumPhys ), + .reg_req_t ( reg_req_t ), + .reg_rsp_t ( reg_rsp_t ), + .host_rule_t ( axi_rule_t ), + .RegDataWidth ( RegDataWidth ) + ) i_cfg_frontend ( + .clk_i ( clk_sys_i ), + .rst_ni ( rst_sys_ni ), + .reg_req_i ( reg_req_i ), + .reg_rsp_o ( reg_rsp_o ), + .drain_o ( frontend_drain ), + .host_idle_i ( host_idle ), + .trans_active_i ( midend_trans_active ), + .cfg_apply_o ( frontend_cfg_apply ), + .cfg_apply_valid_o ( frontend_cfg_apply_valid ), + .cfg_apply_ready_i ( frontend_cfg_apply_ready ), + .cfg_apply_done_i ( frontend_cfg_apply_done ), + .clock_div_apply_o ( ), + .clock_div_apply_valid_o ( frontend_clock_div_apply_valid ), + .clock_div_apply_ready_i ( 1'b1 ), + .clock_div_apply_done_i ( frontend_clock_div_apply_valid ), + .frontend_cfg_o ( frontend_cfg ), + .chip_rules_o ( frontend_chip_rules ), + .decode_error_i ( midend_decode_error ) + ); + + hyperbus_axi_frontend #( + .AxiDataWidth ( AxiDataWidth ), + .AxiAddrWidth ( AxiAddrWidth ), + .AxiIdWidth ( AxiIdWidth ), + .AxiUserWidth ( AxiUserWidth ), + .axi_req_t ( axi_req_t ), + .axi_rsp_t ( axi_rsp_t ), + .host_req_t ( host_req_t ), + .host_rsp_t ( host_rsp_t ) + ) i_axi_frontend ( + .clk_i ( clk_sys_i ), + .rst_ni ( rst_sys_ni ), + .drain_i ( frontend_drain ), + .idle_o ( host_idle ), + .axi_req_i ( axi_req_i ), + .axi_rsp_o ( axi_rsp_o ), + .host_req_o ( host_req ), + .host_rsp_i ( host_rsp ) + ); + + hyperbus_midend #( + .HostAddrWidth ( AxiAddrWidth ), + .HostDataWidth ( AxiDataWidth ), + .NumChips ( NumChips ), + .NumPhys ( NumPhys ), + .host_cmd_t ( host_cmd_t ), + .host_w_t ( host_w_t ), + .host_r_t ( host_r_t ), + .host_wrsp_t ( host_wrsp_t ), + .host_req_t ( host_req_t ), + .host_rsp_t ( host_rsp_t ), + .hyper_rx_t ( hyper_rx_t ), + .hyper_tx_t ( hyper_tx_t ), + .hyper_cmd_t ( hyper_cmd_t ), + .hyper_req_t ( hyper_req_t ), + .hyper_rsp_t ( hyper_rsp_t ), + .rule_t ( axi_rule_t ) + ) i_midend ( + .clk_i ( clk_sys_i ), + .rst_ni ( rst_sys_ni ), + .host_link_req_i ( host_req ), + .host_link_rsp_o ( host_rsp ), + .frontend_cfg_i ( frontend_cfg ), + .chip_rules_i ( frontend_chip_rules ), + .trans_active_o ( midend_trans_active ), + .decode_error_o ( midend_decode_error ), + .hyper_link_req_o ( midend_req ), + .hyper_link_rsp_i ( midend_rsp ) + ); + + hyperbus_async_bridge #( + .RxFifoLogDepth ( RxFifoLogDepth ), + .TxFifoLogDepth ( TxFifoLogDepth ), + .CdcSyncStages ( CdcSyncStages ), + .hyper_rx_t ( hyper_rx_t ), + .hyper_tx_t ( hyper_tx_t ), + .hyper_wrsp_t ( hyper_wrsp_t ), + .hyper_cmd_t ( hyper_cmd_t ), + .hyper_req_t ( hyper_req_t ), + .hyper_rsp_t ( hyper_rsp_t ) + ) i_bridge ( + .clk_sys_i ( clk_sys_i ), + .rst_sys_ni ( rst_sys_ni ), + .clk_phy_i ( clk_backend ), + .rst_phy_ni ( rst_backend_n ), + .cfg_apply_i ( frontend_cfg_apply ), + .frontend_cfg_apply_valid_i ( frontend_cfg_apply_valid ), + .frontend_cfg_apply_ready_o ( frontend_cfg_apply_ready ), + .frontend_cfg_apply_done_o ( frontend_cfg_apply_done ), + .frontend_req_i ( midend_req ), + .frontend_rsp_o ( midend_rsp ), + .backend_req_o ( backend_req ), + .backend_rsp_i ( backend_rsp ), + .cfg_apply_o ( backend_cfg_apply ), + .cfg_apply_valid_o ( backend_cfg_apply_valid ), + .cfg_apply_ready_i ( backend_cfg_apply_ready ) + ); + + hyperbus_backend #( + .NumChips ( NumChips ), + .NumPhys ( NumPhys ), + .StartupCycles ( PhyStartupCycles ), + .SyncStages ( SyncStages ), + .hyper_rx_t ( hyper_rx_t ), + .hyper_tx_t ( hyper_tx_t ), + .hyper_req_t ( hyper_req_t ), + .hyper_rsp_t ( hyper_rsp_t ) + ) i_backend ( + .clk_i ( clk_backend ), + .rst_ni ( rst_backend_n ), +`ifdef TARGET_XILINX + .clk_ref200_i ( clk_ref200_i ), +`endif + .test_mode_i ( test_mode_i ), + .cfg_apply_i ( backend_cfg_apply ), + .cfg_apply_valid_i ( backend_cfg_apply_valid ), + .cfg_apply_ready_o ( backend_cfg_apply_ready ), + .busy_o ( ), + .req_i ( backend_req ), + .rsp_o ( backend_rsp ), + .hyper_cs_no ( hyper_cs_no ), + .hyper_ck_o ( hyper_ck_o ), + .hyper_ck_no ( hyper_ck_no ), + .hyper_rwds_o ( hyper_rwds_o ), + .hyper_rwds_i ( hyper_rwds_i ), + .hyper_rwds_oe_o ( hyper_rwds_oe_o ), + .hyper_dq_i ( hyper_dq_i ), + .hyper_dq_o ( hyper_dq_o ), + .hyper_dq_oe_o ( hyper_dq_oe_o ), + .hyper_reset_no ( hyper_reset_no ) + ); + + `ASSERT(PhyResetOnlyWhenIdle, !rst_phy_ni |-> !midend_trans_active, + clk_sys_i, !rst_sys_ni) + +endmodule diff --git a/src/hyperbus_atomic_handler.sv b/src/hyperbus_atomic_handler.sv new file mode 100644 index 0000000..4daddea --- /dev/null +++ b/src/hyperbus_atomic_handler.sv @@ -0,0 +1,279 @@ +// Copyright 2026 ETH Zurich and University of Bologna. +// Solderpad Hardware License, Version 0.51, see LICENSE for details. +// SPDX-License-Identifier: SHL-0.51 + +`include "common_cells/registers.svh" + +module hyperbus_atomic_handler #( + parameter int unsigned HostAddrWidth = -1, + parameter int unsigned HostDataWidth = -1, + parameter type host_cmd_t = logic, + parameter type host_w_t = logic, + parameter type host_r_t = logic, + parameter type host_wrsp_t = logic +) ( + input logic clk_i, + input logic rst_ni, + + input logic start_i, + input logic request_valid_i, + input host_cmd_t request_i, + output logic active_o, + output logic completed_o, + + output host_cmd_t command_o, + output logic command_valid_o, + input logic command_ready_i, + + input host_w_t host_w_i, + input logic host_w_valid_i, + output logic host_w_ready_o, + output host_r_t host_r_o, + output logic host_r_valid_o, + input logic host_r_ready_i, + output host_wrsp_t host_wrsp_o, + output logic host_wrsp_valid_o, + input logic host_wrsp_ready_i, + + input host_r_t read_i, + input logic read_valid_i, + output logic read_ready_o, + output host_w_t write_o, + output logic write_valid_o, + input logic write_ready_i, + input logic write_rsp_error_i, + input logic write_rsp_valid_i, + output logic write_rsp_ready_o +); + + localparam int unsigned HostDataBytes = HostDataWidth / 8; + localparam int unsigned HostBusAddrWidth = $clog2(HostDataBytes); + + typedef logic [HostAddrWidth-1:0] host_addr_t; + typedef logic [HostDataWidth-1:0] host_data_t; + + typedef enum logic [2:0] { + Idle, + WaitWriteData, + ReadCommand, + ReadData, + WriteCommand, + WriteData, + WriteResponse, + ReturnResponse + } atomic_state_e; + + typedef struct packed { + host_addr_t addr; + hyperbus_pkg::hyper_host_size_t size; + hyperbus_pkg::hyper_atomic_op_e atomic_op; + logic atomic_return; + } atomic_request_t; + + atomic_state_e state_d, state_q; + atomic_request_t request_d, request_q; + host_data_t operand_d, operand_q; + host_data_t read_data_d, read_data_q; + hyperbus_pkg::hyper_resp_e response_d, response_q; + logic read_pending_d, read_pending_q; + logic write_pending_d, write_pending_q; + + host_data_t value_mask; + host_data_t old_value; + host_data_t operand_value; + host_data_t swap_value; + host_data_t result_value; + host_data_t write_data; + int unsigned byte_offset; + int unsigned operand_bytes; + int unsigned operand_bits; + + assign active_o = state_q != Idle; + assign command_valid_o = (state_q == ReadCommand) || (state_q == WriteCommand); + assign completed_o = (state_q == ReturnResponse) && + (!read_pending_q || (host_r_valid_o && host_r_ready_i)) && + (!write_pending_q || (host_wrsp_valid_o && host_wrsp_ready_i)); + + always_comb begin : proc_command + command_o = '0; + command_o.write = state_q == WriteCommand; + command_o.addr = request_q.addr; + command_o.beats = 1; + command_o.size = request_q.size; + command_o.burst = hyperbus_pkg::HyperBurstIncr; + command_o.atomic_op = hyperbus_pkg::HyperAtomicNone; + command_o.atomic_return = 1'b0; + command_o.ordered = 1'b1; + if ((request_q.atomic_op == hyperbus_pkg::HyperAtomicCompare) && + (request_q.size != '0)) begin + command_o.size = request_q.size - 1'b1; + end + end + + always_comb begin : proc_alu + byte_offset = 0; + operand_bytes = 1; + operand_bits = 8; + value_mask = '1; + old_value = read_data_q; + operand_value = operand_q; + swap_value = operand_q >> 8; + result_value = old_value; + write_data = read_data_q; + + if (response_q == hyperbus_pkg::HyperRespOkay) begin + byte_offset = unsigned'(request_q.addr[HostBusAddrWidth-1:0]); + operand_bytes = 1 << ((request_q.atomic_op == hyperbus_pkg::HyperAtomicCompare) ? + (request_q.size - 1'b1) : request_q.size); + operand_bits = operand_bytes * 8; + if (operand_bits < HostDataWidth) begin + value_mask = value_mask >> (HostDataWidth - operand_bits); + end + + old_value = read_data_q >> (byte_offset * 8); + operand_value = operand_q >> (byte_offset * 8); + swap_value = operand_value >> operand_bits; + result_value = old_value; + + unique case (request_q.atomic_op) + hyperbus_pkg::HyperAtomicSwap: result_value = operand_value; + hyperbus_pkg::HyperAtomicCompare: begin + if ((old_value & value_mask) == (operand_value & value_mask)) begin + result_value = swap_value; + end + end + hyperbus_pkg::HyperAtomicAdd: result_value = old_value + operand_value; + hyperbus_pkg::HyperAtomicClear: result_value = old_value & ~operand_value; + hyperbus_pkg::HyperAtomicXor: result_value = old_value ^ operand_value; + hyperbus_pkg::HyperAtomicSet: result_value = old_value | operand_value; + hyperbus_pkg::HyperAtomicSignedMax: begin + if ((old_value[operand_bits-1] && !operand_value[operand_bits-1]) || + ((old_value[operand_bits-1] == operand_value[operand_bits-1]) && + ((old_value & value_mask) < (operand_value & value_mask)))) begin + result_value = operand_value; + end + end + hyperbus_pkg::HyperAtomicSignedMin: begin + if ((!old_value[operand_bits-1] && operand_value[operand_bits-1]) || + ((old_value[operand_bits-1] == operand_value[operand_bits-1]) && + ((old_value & value_mask) > (operand_value & value_mask)))) begin + result_value = operand_value; + end + end + hyperbus_pkg::HyperAtomicUnsignedMax: begin + if ((old_value & value_mask) < (operand_value & value_mask)) begin + result_value = operand_value; + end + end + hyperbus_pkg::HyperAtomicUnsignedMin: begin + if ((old_value & value_mask) > (operand_value & value_mask)) begin + result_value = operand_value; + end + end + default:; + endcase + + write_data = read_data_q & ~(value_mask << (byte_offset * 8)); + write_data |= (result_value & value_mask) << (byte_offset * 8); + end + end + + always_comb begin : proc_outputs + host_w_ready_o = state_q == WaitWriteData; + host_r_o = '0; + host_r_o.data = read_data_q; + host_r_o.resp = response_q; + host_r_o.last = 1'b1; + host_r_o.atomic_ok = response_q == hyperbus_pkg::HyperRespOkay; + host_r_valid_o = (state_q == ReturnResponse) && read_pending_q; + host_wrsp_o = '0; + host_wrsp_o.resp = response_q; + host_wrsp_o.atomic_ok = response_q == hyperbus_pkg::HyperRespOkay; + host_wrsp_valid_o = (state_q == ReturnResponse) && write_pending_q; + read_ready_o = state_q == ReadData; + write_o = '0; + write_o.data = write_data; + write_o.last = 1'b1; + for (int unsigned i = 0; i < HostDataBytes; i++) begin + write_o.strb[i] = (i >= byte_offset) && (i < byte_offset + operand_bytes); + end + write_valid_o = state_q == WriteData; + write_rsp_ready_o = state_q == WriteResponse; + end + + always_comb begin : proc_state + state_d = state_q; + request_d = request_q; + operand_d = operand_q; + read_data_d = read_data_q; + response_d = response_q; + read_pending_d = read_pending_q; + write_pending_d = write_pending_q; + + unique case (state_q) + Idle: begin + if (start_i) begin + request_d.addr = request_i.addr; + request_d.size = request_i.size; + request_d.atomic_op = request_i.atomic_op; + request_d.atomic_return = request_i.atomic_return; + response_d = request_valid_i ? hyperbus_pkg::HyperRespOkay : + hyperbus_pkg::HyperRespAtomicError; + state_d = WaitWriteData; + end + end + WaitWriteData: begin + if (host_w_valid_i && host_w_ready_o) begin + operand_d = host_w_i.data; + if (response_q == hyperbus_pkg::HyperRespOkay) begin + state_d = ReadCommand; + end else if (host_w_i.last) begin + read_pending_d = request_q.atomic_return; + write_pending_d = 1'b1; + state_d = ReturnResponse; + end + end + end + ReadCommand: if (command_valid_o && command_ready_i) state_d = ReadData; + ReadData: begin + if (read_valid_i && read_ready_o) begin + read_data_d = read_i.data; + if (read_i.resp == hyperbus_pkg::HyperRespOkay) begin + state_d = WriteCommand; + end else begin + response_d = read_i.resp; + read_pending_d = request_q.atomic_return; + write_pending_d = 1'b1; + state_d = ReturnResponse; + end + end + end + WriteCommand: if (command_valid_o && command_ready_i) state_d = WriteData; + WriteData: if (write_valid_o && write_ready_i) state_d = WriteResponse; + WriteResponse: begin + if (write_rsp_valid_i && write_rsp_ready_o) begin + response_d = write_rsp_error_i ? hyperbus_pkg::HyperRespAccessError : + hyperbus_pkg::HyperRespOkay; + read_pending_d = request_q.atomic_return; + write_pending_d = 1'b1; + state_d = ReturnResponse; + end + end + ReturnResponse: begin + if (host_r_valid_o && host_r_ready_i) read_pending_d = 1'b0; + if (host_wrsp_valid_o && host_wrsp_ready_i) write_pending_d = 1'b0; + if (completed_o) state_d = Idle; + end + default: state_d = Idle; + endcase + end + + `FFARN(state_q, state_d, Idle, clk_i, rst_ni) + `FFARN(request_q, request_d, '0, clk_i, rst_ni) + `FFARN(operand_q, operand_d, '0, clk_i, rst_ni) + `FFARN(read_data_q, read_data_d, '0, clk_i, rst_ni) + `FFARN(response_q, response_d, hyperbus_pkg::HyperRespOkay, clk_i, rst_ni) + `FFARN(read_pending_q, read_pending_d, 1'b0, clk_i, rst_ni) + `FFARN(write_pending_q, write_pending_d, 1'b0, clk_i, rst_ni) + +endmodule : hyperbus_atomic_handler diff --git a/src/hyperbus_axi.sv b/src/hyperbus_axi.sv index bac8778..74282b5 100644 --- a/src/hyperbus_axi.sv +++ b/src/hyperbus_axi.sv @@ -1,170 +1,174 @@ // Copyright 2023 ETH Zurich and University of Bologna. // Solderpad Hardware License, Version 0.51, see LICENSE for details. // SPDX-License-Identifier: SHL-0.51 -// -// Thomas Benz -// Paul Scheffler `include "axi/typedef.svh" `include "common_cells/registers.svh" - -module hyperbus_axi #( - parameter int unsigned AxiDataWidth = -1, - parameter int unsigned AxiAddrWidth = -1, - parameter int unsigned AxiIdWidth = -1, - parameter int unsigned AxiUserWidth = -1, - parameter type axi_req_t = logic, - parameter type axi_rsp_t = logic, - parameter int unsigned NumChips = -1, - parameter int unsigned NumPhys = -1, - parameter type hyper_tx_t = logic, - parameter type hyper_rx_t = logic, - parameter type rule_t = logic +`include "common_cells/assertions.svh" + +module hyperbus_axi_frontend #( + parameter int unsigned AxiDataWidth = -1, + parameter int unsigned AxiAddrWidth = -1, + parameter int unsigned AxiIdWidth = -1, + parameter int unsigned AxiUserWidth = -1, + parameter type axi_req_t = logic, + parameter type axi_rsp_t = logic, + parameter type host_req_t = logic, + parameter type host_rsp_t = logic ) ( - input logic clk_i, - input logic rst_ni, - // AXI port - input axi_req_t axi_req_i, - output axi_rsp_t axi_rsp_o, - // PHI port - input hyper_rx_t rx_i, - input logic rx_valid_i, - output logic rx_ready_o, - - output hyper_tx_t tx_o, - output logic tx_valid_o, - input logic tx_ready_i, - - input logic b_error_i, - input logic b_valid_i, - output logic b_ready_o, - - output hyperbus_pkg::hyper_tf_t trans_o, - output logic [NumChips-1:0] trans_cs_o, - output logic trans_valid_o, - input logic trans_ready_i, - - input hyperbus_pkg::frontend_cfg_t frontend_cfg_i, - input rule_t [NumChips-1:0] chip_rules_i, - output logic trans_active_o + input logic clk_i, + input logic rst_ni, + + input logic drain_i, + output logic idle_o, + + input axi_req_t axi_req_i, + output axi_rsp_t axi_rsp_o, + + output host_req_t host_req_o, + input host_rsp_t host_rsp_i ); - localparam AxiDataBytes = AxiDataWidth/8; - localparam AxiBusAddrWidth = $clog2(AxiDataBytes); - localparam PhyDataWidth = NumPhys*16; - localparam PhyDataBytes = PhyDataWidth/8; - localparam ChipSelWidth = cf_math_pkg::idx_width(NumChips); - localparam ByteCntWidth = cf_math_pkg::idx_width(AxiDataBytes); + localparam int unsigned AxiDataBytes = AxiDataWidth / 8; + + `ASSERT_INIT(AxiAddrWidthValid, AxiAddrWidth >= 1) + `ASSERT_INIT(AxiDataWidthValid, + AxiDataWidth >= 16 && AxiDataWidth <= 1024 && + (AxiDataWidth & (AxiDataWidth - 1)) == 0) + `ASSERT_INIT(AxiIdWidthValid, AxiIdWidth >= 1) + `ASSERT_INIT(AxiUserWidthValid, AxiUserWidth >= 1) typedef logic [AxiAddrWidth-1:0] axi_addr_t; - typedef logic [ByteCntWidth-1:0] byte_cnt_t; - typedef logic [ByteCntWidth-3:0] word_cnt_t; typedef logic [AxiDataWidth-1:0] axi_data_t; - typedef logic [ChipSelWidth-1:0] chip_sel_idx_t; + typedef logic [AxiIdWidth-1:0] axi_id_t; + typedef logic [AxiDataBytes-1:0] axi_strb_t; + typedef logic [AxiUserWidth-1:0] axi_user_t; - `AXI_TYPEDEF_ALL_CT(axi_fifo,axi_fifo_req,axi_fifo_rsp,axi_addr_t,logic[AxiIdWidth-1:0],axi_data_t,logic[AxiDataBytes-1:0],logic[AxiUserWidth-1:0]) + `AXI_TYPEDEF_ALL_CT(axi_fifo, axi_fifo_req, axi_fifo_rsp, axi_addr_t, axi_id_t, + axi_data_t, axi_strb_t, axi_user_t) - // No need to track ID: serializer buffers it for us + // IDs stay in the AXI serializer; the neutral stream is single-outstanding. typedef struct packed { - axi_addr_t addr; - axi_pkg::len_t len; - axi_pkg::burst_t burst; - axi_pkg::size_t size; + axi_addr_t addr; + axi_pkg::len_t len; + axi_pkg::burst_t burst; + axi_pkg::size_t size; + axi_pkg::atop_t atop; } axi_ax_t; typedef struct packed { - axi_ax_t ax_data; - logic write; + axi_ax_t ax_data; + logic write; } ax_channel_spill_t; typedef struct packed { - logic valid; - axi_data_t data; - logic error; - logic last; - } axi_r_t; - - typedef struct packed { - logic [AxiDataWidth/8-1:0] strb; - axi_data_t data; - logic [AxiUserWidth-1:0] user; - logic last; + axi_strb_t strb; + axi_data_t data; + axi_user_t user; + logic last; } axi_w_chan_t; - typedef struct packed { - logic [7:0] data; - logic strb; - } axi_wbyte_t; - - // AXI FIFO downstream - axi_req_t fifo_out_req; - axi_rsp_t fifo_out_rsp; - - // Atomics Filter downstream - axi_req_t atop_out_req; - axi_rsp_t atop_out_rsp; - - // ID serializer downstream - axi_req_t ser_out_req; - axi_rsp_t ser_out_rsp; - axi_ax_t ser_out_req_aw; - axi_ax_t ser_out_req_ar; - - // AX arbiter downstream - axi_ax_t rr_out_req_ax; - logic rr_out_req_write; - logic spill_ax_valid, spill_ax_ready; - axi_ax_t spill_rr_out_req_ax; - logic spill_rr_out_req_write; - ax_channel_spill_t spill_ax_channel_in, spill_ax_channel_out; - - // AX handling - logic trans_handshake; - logic ax_valid, ax_ready; - axi_pkg::size_t ax_size_d, ax_size_q; - chip_sel_idx_t ax_chip_sel_idx; - hyperbus_pkg::hyper_blen_t ax_blen_postinc; - logic ax_blen_inc; - - // R channel - axi_r_t s_r_split; - - // R channel merge when phys_in_use != NumPhys - logic [PhyDataWidth-1:0] s_rx_data; - logic [PhyDataWidth/2-1:0] s_rx_data_lower_d, s_rx_data_lower_q; - logic s_rx_error; - logic s_rx_last; - logic s_rx_valid; - logic s_rx_ready; - logic merge_r_d, merge_r_q; - - // W channel - logic w_data_valid; - logic w_data_ready; + axi_req_t fifo_in_req; + axi_rsp_t fifo_in_rsp; + axi_req_t fifo_out_req; + axi_rsp_t fifo_out_rsp; + axi_req_t ser_out_req; + axi_rsp_t ser_out_rsp; + + axi_ax_t ser_out_req_aw; + axi_ax_t ser_out_req_ar; + axi_ax_t rr_out_req_ax; + axi_ax_t spill_rr_out_req_ax; + + ax_channel_spill_t spill_ax_channel_in; + ax_channel_spill_t spill_ax_channel_out; + axi_w_chan_t w_data_fifo; axi_w_chan_t w_data_fifo_in; - // W channel split when phys_in_use != NumPhys - logic [PhyDataWidth-1:0] s_tx_data; - logic [PhyDataBytes-1:0] s_tx_strb; - logic s_tx_last; - logic s_tx_valid; - logic s_tx_ready; - logic split_w_d, split_w_q; + logic spill_ax_valid; + logic spill_ax_ready; + logic spill_rr_out_req_write; + logic rr_out_req_write; + logic w_data_valid; + logic w_data_ready; - // Whether a transfer is currently active - logic trans_active_d, trans_active_q; - logic trans_active_set, trans_active_reset; - logic trans_wready_d, trans_wready_q; - logic trans_wready_set, trans_wready_reset; + localparam int unsigned PendingWidth = 8; + typedef logic [PendingWidth-1:0] pending_cnt_t; + typedef logic signed [PendingWidth-1:0] write_balance_t; + + pending_cnt_t read_pending_d, read_pending_q; + pending_cnt_t write_pending_d, write_pending_q; + write_balance_t write_balance_d, write_balance_q; + logic w_partial_d, w_partial_q; + logic allow_aw, allow_w; + logic axi_ar_accepted, axi_aw_accepted, axi_w_accepted; + logic axi_atomic_read_started; + logic axi_r_completed, axi_b_accepted; + + always_comb begin : proc_axi_drain + allow_aw = !drain_i; + allow_w = !drain_i; + + if (drain_i) begin + // Complete only channel fragments accepted before the barrier. + allow_aw = !w_partial_q && (write_balance_q < 0); + allow_w = w_partial_q || (write_balance_q > 0); + end - logic [1:0] phys_in_use; + fifo_in_req = axi_req_i; + fifo_in_req.ar_valid = axi_req_i.ar_valid && !drain_i; + fifo_in_req.aw_valid = axi_req_i.aw_valid && allow_aw; + fifo_in_req.w_valid = axi_req_i.w_valid && allow_w; - assign phys_in_use = (NumPhys==2) ? (frontend_cfg_i.phys_in_use + 1) : 1; + axi_rsp_o = fifo_in_rsp; + axi_rsp_o.ar_ready = fifo_in_rsp.ar_ready && !drain_i; + axi_rsp_o.aw_ready = fifo_in_rsp.aw_ready && allow_aw; + axi_rsp_o.w_ready = fifo_in_rsp.w_ready && allow_w; + end - // ============================ - // Serialize requests - // ============================ + assign axi_ar_accepted = axi_req_i.ar_valid && axi_rsp_o.ar_ready; + assign axi_aw_accepted = axi_req_i.aw_valid && axi_rsp_o.aw_ready; + assign axi_w_accepted = axi_req_i.w_valid && axi_rsp_o.w_ready; + assign axi_atomic_read_started = axi_aw_accepted && + axi_req_i.aw.atop[axi_pkg::ATOP_R_RESP]; + assign axi_r_completed = axi_rsp_o.r_valid && axi_req_i.r_ready && axi_rsp_o.r.last; + assign axi_b_accepted = axi_rsp_o.b_valid && axi_req_i.b_ready; + + always_comb begin : proc_pending_counts + read_pending_d = read_pending_q; + write_pending_d = write_pending_q; + write_balance_d = write_balance_q; + w_partial_d = w_partial_q; + + read_pending_d = read_pending_q + pending_cnt_t'(axi_ar_accepted) + + pending_cnt_t'(axi_atomic_read_started) - + pending_cnt_t'(axi_r_completed); + + unique case ({axi_aw_accepted, axi_b_accepted}) + 2'b10: write_pending_d = write_pending_q + 1'b1; + 2'b01: write_pending_d = write_pending_q - 1'b1; + default:; + endcase + + if (axi_aw_accepted) begin + write_balance_d = write_balance_d + 1'b1; + end + if (axi_w_accepted && axi_req_i.w.last) begin + write_balance_d = write_balance_d - 1'b1; + end + if (axi_w_accepted) begin + w_partial_d = !axi_req_i.w.last; + end + end + + `FFARN(read_pending_q, read_pending_d, '0, clk_i, rst_ni) + `FFARN(write_pending_q, write_pending_d, '0, clk_i, rst_ni) + `FFARN(write_balance_q, write_balance_d, '0, clk_i, rst_ni) + `FFARN(w_partial_q, w_partial_d, 1'b0, clk_i, rst_ni) + + assign idle_o = (read_pending_q == '0) && (write_pending_q == '0) && + (write_balance_q == '0) && !w_partial_q; axi_fifo #( .Depth ( 8 ), @@ -179,239 +183,129 @@ module hyperbus_axi #( ) i_axi_fifo ( .clk_i, .rst_ni, - .test_i ( 1'b0 ), - .slv_req_i ( axi_req_i ), - .slv_resp_o ( axi_rsp_o ), - .mst_req_o ( fifo_out_req ), - .mst_resp_i ( fifo_out_rsp ) - ); - - - // Block unsupported atomics - axi_atop_filter #( - .AxiIdWidth ( AxiIdWidth ), - .AxiMaxWriteTxns ( 1 ), - .axi_req_t ( axi_req_t ), - .axi_resp_t ( axi_rsp_t ) - ) i_axi_atop_filter ( - .clk_i, - .rst_ni, - .slv_req_i ( fifo_out_req ), - .slv_resp_o ( fifo_out_rsp ), - .mst_req_o ( atop_out_req ), - .mst_resp_i ( atop_out_rsp ) + .test_i ( 1'b0 ), + .slv_req_i ( fifo_in_req ), + .slv_resp_o ( fifo_in_rsp ), + .mst_req_o ( fifo_out_req ), + .mst_resp_i ( fifo_out_rsp ) ); - // Ensure we only handle one ID (master) at a time axi_serializer #( - .MaxReadTxns ( 1 ), - .MaxWriteTxns ( 1 ), - .AxiIdWidth ( AxiIdWidth ), - .axi_req_t ( axi_req_t ), - .axi_resp_t ( axi_rsp_t ) + .MaxReadTxns ( 1 ), + .MaxWriteTxns ( 1 ), + .AxiIdWidth ( AxiIdWidth ), + .axi_req_t ( axi_req_t ), + .axi_resp_t ( axi_rsp_t ) ) i_axi_serializer ( .clk_i, .rst_ni, - .slv_req_i ( atop_out_req ), - .slv_resp_o ( atop_out_rsp ), - .mst_req_o ( ser_out_req ), - .mst_resp_i ( ser_out_rsp ) + .slv_req_i ( fifo_out_req ), + .slv_resp_o ( fifo_out_rsp ), + .mst_req_o ( ser_out_req ), + .mst_resp_i ( ser_out_rsp ) ); - // Round-robin-arbitrate between AR and AW channels (HyperBus is simplex) assign ser_out_req_ar.addr = ser_out_req.ar.addr; assign ser_out_req_ar.len = ser_out_req.ar.len; assign ser_out_req_ar.burst = ser_out_req.ar.burst; assign ser_out_req_ar.size = ser_out_req.ar.size; + assign ser_out_req_ar.atop = '0; assign ser_out_req_aw.addr = ser_out_req.aw.addr; assign ser_out_req_aw.len = ser_out_req.aw.len; assign ser_out_req_aw.burst = ser_out_req.aw.burst; assign ser_out_req_aw.size = ser_out_req.aw.size; + assign ser_out_req_aw.atop = ser_out_req.aw.atop; rr_arb_tree #( - .NumIn ( 2 ), - .DataType ( axi_ax_t ), - .AxiVldRdy ( 1 ), - .ExtPrio ( 1'b1 ) + .NumIn ( 2 ), + .DataType ( axi_ax_t ), + .AxiVldRdy ( 1 ), + .ExtPrio ( 1'b1 ) ) i_rr_arb_tree_ax ( .clk_i, .rst_ni, - .flush_i ( 1'b0 ), - .rr_i ( '0 ), - .req_i ( { ser_out_req.aw_valid, ser_out_req.ar_valid } ), - .gnt_o ( { ser_out_rsp.aw_ready, ser_out_rsp.ar_ready } ), - .data_i ( { ser_out_req_aw, ser_out_req_ar } ), - .req_o ( spill_ax_valid ), - .gnt_i ( spill_ax_ready ), - .data_o ( spill_rr_out_req_ax ), - .idx_o ( spill_rr_out_req_write ) + .flush_i ( 1'b0 ), + .rr_i ( '0 ), + .req_i ( {ser_out_req.aw_valid, ser_out_req.ar_valid} ), + .gnt_o ( {ser_out_rsp.aw_ready, ser_out_rsp.ar_ready} ), + .data_i ( {ser_out_req_aw, ser_out_req_ar} ), + .req_o ( spill_ax_valid ), + .gnt_i ( spill_ax_ready ), + .data_o ( spill_rr_out_req_ax ), + .idx_o ( spill_rr_out_req_write ) ); - // Cut paths between serializer and rr arb tree assign spill_ax_channel_in.ax_data = spill_rr_out_req_ax; - assign spill_ax_channel_in.write = spill_rr_out_req_write; + assign spill_ax_channel_in.write = spill_rr_out_req_write; spill_register #( - .T ( ax_channel_spill_t ) - ) ax_spill_register ( - .clk_i, - .rst_ni, - .valid_i (spill_ax_valid), - .ready_o (spill_ax_ready), - .data_i (spill_ax_channel_in), - .valid_o (ax_valid), - .ready_i (ax_ready), - .data_o (spill_ax_channel_out) - ); - - assign rr_out_req_ax = spill_ax_channel_out.ax_data; - assign rr_out_req_write = spill_ax_channel_out.write; - - assign trans_valid_o = ax_valid & ~trans_active_q; - assign ax_ready = trans_ready_i & ~trans_active_q; - - assign trans_handshake = trans_valid_o & trans_ready_i; - - // ============================ - // AX channel: handle - // ============================ - - // Handle address mapping to chip select - addr_decode #( - .NoIndices ( NumChips ), - .NoRules ( NumChips ), - .addr_t ( axi_addr_t ), - .rule_t ( rule_t ) - ) i_addr_decode_chip_sel ( - .addr_i ( rr_out_req_ax.addr ), - .addr_map_i ( chip_rules_i ), - .idx_o ( ax_chip_sel_idx ), - .dec_valid_o ( ), - .dec_error_o ( ), - .en_default_idx_i ( 1'b1 ), - .default_idx_i ( '0 ) - ); - - // Chip select binary to one hot decoding - always_comb begin : proc_comb_trans_cs - trans_cs_o = '0; - trans_cs_o[ax_chip_sel_idx] = 1'b1; - end - - // AX channel: forward, converting unmasked byte to masked word addresses - assign trans_o.write = rr_out_req_write; - assign trans_o.burst_type = 1'b1; // Wrapping bursts not (yet) supported - assign trans_o.address_space = frontend_cfg_i.address_space; - assign trans_o.address = (NumPhys == 2) ? - (frontend_cfg_i.phys_in_use ? - ((rr_out_req_ax.addr & ((32'b1 << frontend_cfg_i.address_mask_msb) - 1)) >> 2) : - (((rr_out_req_ax.addr & ((32'b1 << frontend_cfg_i.address_mask_msb) - 1)) >> 2) << 1)) : - ((rr_out_req_ax.addr & ((32'b1 << frontend_cfg_i.address_mask_msb) - 1)) >> 1); - - // Convert burst length from decremented, unaligned beats to non-decremented, aligned 16-bit words - always_comb begin - trans_o.burst= NumPhys; - ax_blen_inc = 1'b1; - if (rr_out_req_ax.size > NumPhys) begin - ax_blen_inc = 1'b1; - if( ((rr_out_req_ax.addr>>rr_out_req_ax.size)<>rr_out_req_ax.size)<> NumPhys ) + 1 ) << (NumPhys-1); - end else begin - trans_o.burst = ( ( ( rr_out_req_ax.addr[NumPhys-1:0] + (ax_blen_postinc<> NumPhys ) + 1 ) << (NumPhys-1); - end - end - end - end - - assign ax_blen_postinc = rr_out_req_ax.len + hyperbus_pkg::hyper_blen_t'(ax_blen_inc) ; - - // ============================ - // R channel - // ============================ - - assign ser_out_rsp.r.data = s_r_split.data; - assign ser_out_rsp.r.last = s_r_split.last; - assign ser_out_rsp.r.resp = s_r_split.error ? axi_pkg::RESP_SLVERR : axi_pkg::RESP_OKAY; - assign ser_out_rsp.r.id = '0; - assign ser_out_rsp.r.user = '0; - - always_comb begin - s_rx_valid = rx_valid_i; - rx_ready_o = s_rx_ready; - s_rx_data = rx_i.data; - s_rx_last = rx_i.last; - s_rx_error = rx_i.error; - s_rx_data_lower_d = s_rx_data_lower_q; - merge_r_d = merge_r_q; - if( (NumPhys==2) & (~frontend_cfg_i.phys_in_use) ) begin - if(rx_valid_i & s_rx_ready) begin - merge_r_d = merge_r_q + 1; - end - if(~frontend_cfg_i.which_phy) - s_rx_data = { rx_i.data[PhyDataWidth/2-1:0] , s_rx_data_lower_q }; - else - s_rx_data = { rx_i.data[PhyDataWidth-1:PhyDataWidth/2] , s_rx_data_lower_q }; - s_rx_valid = rx_valid_i & merge_r_q; - rx_ready_o = s_rx_ready; - s_rx_last = rx_i.last & merge_r_q; - s_rx_error = rx_i.error; - if(~merge_r_q) begin - if(~frontend_cfg_i.which_phy) - s_rx_data_lower_d = rx_i.data[PhyDataWidth/2-1:0]; - else - s_rx_data_lower_d = rx_i.data[PhyDataWidth-1:PhyDataWidth/2]; - end - end - end - - hyperbus_phy2r #( - .AxiDataWidth ( AxiDataWidth ), - .BurstLength ( hyperbus_pkg::HyperBurstWidth ), - .T ( axi_r_t ), - .NumPhys ( NumPhys ) - ) i_hyperbus_phy2r ( + .T ( ax_channel_spill_t ) + ) i_ax_spill_register ( .clk_i, .rst_ni, - .size ( rr_out_req_ax.size ), - .trans_handshake ( trans_handshake ), - .start_addr ( rr_out_req_ax.addr[AxiBusAddrWidth-1:0] ), - .burst_len ( ax_blen_postinc ), - .is_a_read ( !rr_out_req_write ), - .phy_valid_i ( s_rx_valid ), - .phy_ready_o ( s_rx_ready ), - .data_i ( s_rx_data ), - .last_i ( s_rx_last ), - .error_i ( s_rx_error ), - .axi_valid_o ( ser_out_rsp.r_valid ), - .axi_ready_i ( ser_out_req.r_ready ), - .data_o ( s_r_split ) + .valid_i ( spill_ax_valid ), + .ready_o ( spill_ax_ready ), + .data_i ( spill_ax_channel_in ), + .valid_o ( host_req_o.cmd_valid ), + .ready_i ( host_rsp_i.cmd_ready ), + .data_o ( spill_ax_channel_out ) ); - `FFARN(merge_r_q,merge_r_d,1'b0,clk_i,rst_ni) - `FFARN(s_rx_data_lower_q,s_rx_data_lower_d,'0,clk_i,rst_ni) + assign rr_out_req_ax = spill_ax_channel_out.ax_data; + assign rr_out_req_write = spill_ax_channel_out.write; - // ========================================================= - // W channel: Buffer. Cuts path and upsamples when needed - // ========================================================= + assign host_req_o.cmd.write = rr_out_req_write; + assign host_req_o.cmd.addr = rr_out_req_ax.addr; + assign host_req_o.cmd.beats = hyperbus_pkg::hyper_blen_t'(rr_out_req_ax.len) + + hyperbus_pkg::hyper_blen_t'(1); + assign host_req_o.cmd.size = rr_out_req_ax.size; + assign host_req_o.cmd.burst = (rr_out_req_ax.burst == axi_pkg::BURST_FIXED) ? + hyperbus_pkg::HyperBurstFixed : + hyperbus_pkg::HyperBurstIncr; + always_comb begin : proc_atomic_decode + host_req_o.cmd.atomic_op = (rr_out_req_ax.atop == '0) ? + hyperbus_pkg::HyperAtomicNone : + hyperbus_pkg::HyperAtomicInvalid; + unique case (rr_out_req_ax.atop) + axi_pkg::ATOP_ATOMICSWAP: + host_req_o.cmd.atomic_op = hyperbus_pkg::HyperAtomicSwap; + axi_pkg::ATOP_ATOMICCMP: + host_req_o.cmd.atomic_op = hyperbus_pkg::HyperAtomicCompare; + default: begin + if ((rr_out_req_ax.atop[5:4] == axi_pkg::ATOP_ATOMICSTORE) || + (rr_out_req_ax.atop[5:4] == axi_pkg::ATOP_ATOMICLOAD)) begin + unique case (rr_out_req_ax.atop[2:0]) + axi_pkg::ATOP_ADD: + host_req_o.cmd.atomic_op = hyperbus_pkg::HyperAtomicAdd; + axi_pkg::ATOP_CLR: + host_req_o.cmd.atomic_op = hyperbus_pkg::HyperAtomicClear; + axi_pkg::ATOP_EOR: + host_req_o.cmd.atomic_op = hyperbus_pkg::HyperAtomicXor; + axi_pkg::ATOP_SET: + host_req_o.cmd.atomic_op = hyperbus_pkg::HyperAtomicSet; + axi_pkg::ATOP_SMAX: + host_req_o.cmd.atomic_op = hyperbus_pkg::HyperAtomicSignedMax; + axi_pkg::ATOP_SMIN: + host_req_o.cmd.atomic_op = hyperbus_pkg::HyperAtomicSignedMin; + axi_pkg::ATOP_UMAX: + host_req_o.cmd.atomic_op = hyperbus_pkg::HyperAtomicUnsignedMax; + axi_pkg::ATOP_UMIN: + host_req_o.cmd.atomic_op = hyperbus_pkg::HyperAtomicUnsignedMin; + default:; + endcase + // AXI defines bit 3 as endianness for arithmetic atomics. + if (rr_out_req_ax.atop[3] && + ((rr_out_req_ax.atop[2:0] == axi_pkg::ATOP_ADD) || + (rr_out_req_ax.atop[2:0] >= axi_pkg::ATOP_SMAX))) begin + host_req_o.cmd.atomic_op = hyperbus_pkg::HyperAtomicInvalid; + end + end + end + endcase + host_req_o.cmd.atomic_return = rr_out_req_ax.atop[axi_pkg::ATOP_R_RESP]; + host_req_o.cmd.ordered = rr_out_req_ax.atop != '0; + end assign w_data_fifo_in.data = ser_out_req.w.data; assign w_data_fifo_in.strb = ser_out_req.w.strb; @@ -421,8 +315,8 @@ module hyperbus_axi #( stream_fifo #( .FALL_THROUGH ( 1'b0 ), .T ( axi_w_chan_t ), - .DEPTH ( 16 ) - ) wchan_stream_fifo ( + .DEPTH ( 16 ) + ) i_wchan_stream_fifo ( .clk_i, .rst_ni, .flush_i ( 1'b0 ), @@ -434,120 +328,47 @@ module hyperbus_axi #( .data_o ( w_data_fifo ), .valid_o ( w_data_valid ), .ready_i ( w_data_ready ) - ); - - hyperbus_w2phy #( - .AxiDataWidth ( AxiDataWidth ), - .BurstLength ( hyperbus_pkg::HyperBurstWidth ), - .T ( axi_w_chan_t ), - .NumPhys ( NumPhys ) - ) i_hyperbus_w2phy ( - .clk_i, - .rst_ni, - .size ( rr_out_req_ax.size ), - .len ( ax_blen_postinc ), - .is_a_write ( rr_out_req_write ), - .trans_handshake ( trans_handshake ), - .start_addr ( rr_out_req_ax.addr[AxiBusAddrWidth-1:0] ), - .data_i ( w_data_fifo ), - .axi_valid_i ( w_data_valid ), - .axi_ready_o ( w_data_ready ), - .data_o ( s_tx_data ), - .last_o ( s_tx_last ), - .strb_o ( s_tx_strb ), - .phy_valid_o ( s_tx_valid ), - .phy_ready_i ( s_tx_ready ) ); - always_comb begin - tx_o.data = s_tx_data; - tx_o.strb = s_tx_strb; - tx_o.last = s_tx_last; - tx_valid_o = s_tx_valid; - s_tx_ready = tx_ready_i; - split_w_d = split_w_q; - if( (NumPhys==2) & (~frontend_cfg_i.phys_in_use) ) begin - if(s_tx_valid & tx_ready_i) begin - split_w_d = split_w_q+1; - end - tx_o.last = s_tx_last & split_w_q; - tx_valid_o = s_tx_valid; - s_tx_ready = tx_ready_i & split_w_q; - tx_o.data = { s_tx_data[PhyDataWidth/2-1:0] , s_tx_data[PhyDataWidth/2-1:0] }; - tx_o.strb = { s_tx_strb[NumPhys-1:0] , s_tx_strb[NumPhys-1:0] }; - if(split_w_q) begin - tx_o.data = { s_tx_data[PhyDataWidth-1:PhyDataWidth/2] , s_tx_data[PhyDataWidth-1:PhyDataWidth/2] }; - tx_o.strb = { s_tx_strb[NumPhys*2-1:NumPhys] , s_tx_strb[NumPhys*2-1:NumPhys] }; - end - end - end - - `FFARN(split_w_q,split_w_d,1'b0,clk_i,rst_ni) - - // ============================ - // B channel: passthrough - // ============================ - - assign ser_out_rsp.b.resp = b_error_i ? axi_pkg::RESP_SLVERR : axi_pkg::RESP_OKAY; - assign ser_out_rsp.b.user = '0; - assign ser_out_rsp.b.id = '0; - assign ser_out_rsp.b_valid = b_valid_i; - assign b_ready_o = ser_out_req.b_ready; - - // ============================ - // Transfer status - // ============================ - - assign trans_active_o = trans_active_q; - - assign trans_active_set = trans_handshake; - assign trans_active_reset = (rx_valid_i & rx_ready_o & rx_i.last) | (b_valid_i & b_ready_o); - - // Allow W transfers iff currently engaged in write (AW already received) - assign trans_wready_set = trans_active_set & rr_out_req_write; - assign trans_wready_reset = (tx_valid_o & tx_ready_i & tx_o.last); - - // Set overrules reset as a transfer must start before it finishes - always_comb begin : proc_comb_trans_active - trans_active_d = trans_active_q; - trans_wready_d = trans_wready_q; - if (trans_active_reset) trans_active_d = 1'b0; - if (trans_active_set) trans_active_d = 1'b1; - if (trans_wready_reset) trans_wready_d = 1'b0; - if (trans_wready_set) trans_wready_d = 1'b1; - end + assign host_req_o.w.data = w_data_fifo.data; + assign host_req_o.w.strb = w_data_fifo.strb; + assign host_req_o.w.last = w_data_fifo.last; + assign host_req_o.w_valid = w_data_valid; + assign w_data_ready = host_rsp_i.w_ready; + + assign ser_out_rsp.r.data = host_rsp_i.r.data; + assign ser_out_rsp.r.last = host_rsp_i.r.last; + always_comb begin : proc_host_resp + ser_out_rsp.r.resp = axi_pkg::RESP_OKAY; + ser_out_rsp.b.resp = axi_pkg::RESP_OKAY; + + if (host_rsp_i.r.resp == hyperbus_pkg::HyperRespDecodeError) begin + ser_out_rsp.r.resp = axi_pkg::RESP_DECERR; + end else if (host_rsp_i.r.resp != hyperbus_pkg::HyperRespOkay) begin + ser_out_rsp.r.resp = axi_pkg::RESP_SLVERR; + end - // ========================= - // Registers - // ========================= - - always_ff @(posedge clk_i or negedge rst_ni) begin : proc_ff - if(~rst_ni) begin - trans_active_q <= '0; - trans_wready_q <= '0; - end else begin - trans_active_q <= trans_active_d; - trans_wready_q <= trans_wready_d; + if (host_rsp_i.wrsp.resp == hyperbus_pkg::HyperRespDecodeError) begin + ser_out_rsp.b.resp = axi_pkg::RESP_DECERR; + end else if (host_rsp_i.wrsp.resp != hyperbus_pkg::HyperRespOkay) begin + ser_out_rsp.b.resp = axi_pkg::RESP_SLVERR; end end - // ========================= - // Assertions - // ========================= - - // pragma translate_off - `ifndef VERILATOR - initial assert (AxiDataWidth >= 16 && AxiDataWidth <= 1024) - else $error("AxiDatawidth must be a power of two within [16, 1024]."); - -// access_16b_align : assert property( -// @(posedge clk_i) trans_handshake & (rr_out_req_ax.size != '0) |-> (rr_out_req_ax.addr[0] == 1'b0)) -// else $fatal (1, "The address of a non-byte-size access must be 2-byte aligned."); - - burst_type : assert property( - @(posedge clk_i) trans_handshake |-> ( (rr_out_req_ax.burst == axi_pkg::BURST_INCR) || ((rr_out_req_ax.burst == axi_pkg::BURST_FIXED) && (rr_out_req_ax.len == '0)) ) ) - else $fatal (1, "Non-incremental burst passed; this is currently not supported."); - `endif - // pragma translate_on + assign ser_out_rsp.r.id = '0; + assign ser_out_rsp.r.user = '0; + assign ser_out_rsp.r_valid = host_rsp_i.r_valid; + assign host_req_o.r_ready = ser_out_req.r_ready; + + assign ser_out_rsp.b.user = '0; + assign ser_out_rsp.b.id = '0; + assign ser_out_rsp.b_valid = host_rsp_i.wrsp_valid; + assign host_req_o.wrsp_ready = ser_out_req.b_ready; + + `ASSERT(AxiBurstType, (host_req_o.cmd_valid && host_rsp_i.cmd_ready) |-> + ((rr_out_req_ax.burst == axi_pkg::BURST_INCR) || + ((rr_out_req_ax.burst == axi_pkg::BURST_FIXED) && (rr_out_req_ax.len == '0)))) + `ASSERT(ReadResponsePending, axi_r_completed |-> (read_pending_q != '0)) + `ASSERT(WriteResponsePending, axi_b_accepted |-> (write_pending_q != '0)) endmodule diff --git a/src/hyperbus_cfg_frontend.sv b/src/hyperbus_cfg_frontend.sv new file mode 100644 index 0000000..1fc343c --- /dev/null +++ b/src/hyperbus_cfg_frontend.sv @@ -0,0 +1,199 @@ +// Copyright 2023 ETH Zurich and University of Bologna. +// Solderpad Hardware License, Version 0.51, see LICENSE for details. +// SPDX-License-Identifier: SHL-0.51 + +`include "common_cells/registers.svh" +`include "common_cells/assertions.svh" + +module hyperbus_cfg_frontend #( + parameter int unsigned NumChips = -1, + parameter int unsigned NumPhys = 2, + parameter type reg_req_t = logic, + parameter type reg_rsp_t = logic, + parameter type host_rule_t = logic, + parameter int unsigned RegDataWidth = -1, + parameter logic [7:0] CapabilityFeatures = 8'b0010_0000 +) ( + 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 [NumChips-1:0] chip_rules_o, + input logic decode_error_i +); + + typedef enum logic [2:0] { + CfgIdle, + CfgDrain, + CfgCommit, + CfgObserve, + CfgClockSend, + CfgClockWaitAck, + CfgPhySend, + CfgPhyWaitAck + } cfg_state_e; + + 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; + + `ASSERT_INIT(NumChipsValid, NumChips >= 1 && NumChips <= 8) + `ASSERT_INIT(NumPhysValid, NumPhys == 1 || NumPhys == 2) + `ASSERT_INIT(RegDataWidthValid, RegDataWidth == 32) + + reg_req_t cfg_reg_req; + reg_rsp_t cfg_reg_rsp; + + 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; + + 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 + cfg_reg_req.valid = 1'b0; + reg_rsp_o.ready = 1'b0; + reg_rsp_o.error = 1'b0; + reg_rsp_o.rdata = '0; + end + end + + 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; + + unique case (cfg_state_q) + CfgIdle: begin + if (cfg_changed) begin + cfg_write_pending_d = 1'b0; + cfg_state_d = CfgDrain; + end else if (reg_req_i.valid && reg_req_i.write) begin + cfg_write_pending_d = 1'b1; + cfg_state_d = CfgDrain; + end + end + CfgDrain: begin + if (host_idle_i && !trans_active_i) begin + cfg_state_d = cfg_write_pending_q ? CfgCommit : CfgObserve; + end + end + CfgCommit: begin + if (cfg_reg_rsp.ready) begin + cfg_write_pending_d = 1'b0; + cfg_state_d = CfgObserve; + end + end + 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 + CfgClockSend: begin + if (clock_div_apply_ready_i) begin + if (clock_div_apply_done_i) begin + clock_div_applied_d = frontend_cfg_o.phy_clock_div; + cfg_state_d = phy_cfg_changed ? CfgPhySend : CfgIdle; + end else begin + cfg_state_d = CfgClockWaitAck; + end + end + end + CfgClockWaitAck: begin + if (clock_div_apply_done_i) begin + clock_div_applied_d = frontend_cfg_o.phy_clock_div; + cfg_state_d = phy_cfg_changed ? CfgPhySend : CfgIdle; + end + end + CfgPhySend: begin + if (cfg_apply_ready_i) begin + if (cfg_apply_done_i) begin + cfg_applied_d = phy_cfg; + cfg_state_d = CfgIdle; + end else begin + cfg_state_d = CfgPhyWaitAck; + end + end + end + CfgPhyWaitAck: begin + if (cfg_apply_done_i) begin + cfg_applied_d = phy_cfg; + cfg_state_d = CfgIdle; + end + end + default: begin + cfg_state_d = CfgIdle; + end + endcase + end + + `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) + + hyperbus_cfg_regs #( + .NumChips ( NumChips ), + .NumPhys ( NumPhys ), + .RegDataWidth ( RegDataWidth ), + .CapabilityFeatures ( CapabilityFeatures ), + .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 ) + ); + +endmodule diff --git a/src/hyperbus_cfg_regs.sv b/src/hyperbus_cfg_regs.sv index 22162c5..3dde319 100644 --- a/src/hyperbus_cfg_regs.sv +++ b/src/hyperbus_cfg_regs.sv @@ -11,10 +11,12 @@ module hyperbus_cfg_regs #( parameter int unsigned NumPhys = -1, parameter int unsigned RegDataWidth = -1, parameter int unsigned RegAddrWidth = 32, - parameter logic [7:0] CapabilityFeatures = '0, + // 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 rule_t = logic + parameter type addr_rule_t = logic ) ( input logic clk_i, input logic rst_ni, @@ -24,13 +26,8 @@ module hyperbus_cfg_regs #( output hyperbus_pkg::frontend_cfg_t frontend_cfg_o, output hyperbus_pkg::phy_cfg_t phy_cfg_o, - output rule_t [NumChips-1:0] chip_rules_o, - input logic trans_active_i, - input logic cfg_busy_i, - input logic decode_error_i, - input logic cfg_dirty_i, - output logic flush_req_o, - output logic apply_req_o + output addr_rule_t [NumChips-1:0] chip_rules_o, + input logic decode_error_i ); `include "common_cells/registers.svh" @@ -76,7 +73,7 @@ module hyperbus_cfg_regs #( hyperbus_cfg_regblock_pkg::hyperbus_cfg_regs__out_t cfg_hwif_out; hyperbus_cfg_regblock_pkg::hyperbus_cfg_regs__in_t cfg_hwif_in; - rule_t [NumChipsMax-1:0] chip_rules_all; + addr_rule_t [NumChipsMax-1:0] chip_rules_all; cfg_addr_t cfg_addr; logic cfg_addr_in_window; @@ -123,7 +120,9 @@ module hyperbus_cfg_regs #( (cfg_addr == (12'h418 + i * 12'h40))); end end - assign cfg_access_open = ~trans_active_i | cfg_access_active_q | cfg_status_access; + // 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; @@ -223,10 +222,10 @@ module hyperbus_cfg_regs #( 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 = cfg_busy_i; - assign cfg_hwif_in.global_cfg.status.dirty.next = cfg_dirty_i; - assign flush_req_o = cfg_hwif_out.global_cfg.command.flush.value; - assign apply_req_o = cfg_hwif_out.global_cfg.command.apply.value; + 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. reg_to_apb #( .reg_req_t ( cfg_reg_req_t ), @@ -265,9 +264,9 @@ module hyperbus_cfg_regs #( 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.phys_in_use = (NumPhys == 2) && + frontend_cfg_o.dual_phy = (NumPhys == 2) && cfg_hwif_out.frontend.frontend_cfg.dual_phy.value; - frontend_cfg_o.which_phy = 1'b0; + frontend_cfg_o.phy_clock_div = 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; @@ -277,9 +276,8 @@ module hyperbus_cfg_regs #( 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.phys_in_use = (NumPhys == 2) && + phy_cfg_o.dual_phy = (NumPhys == 2) && cfg_hwif_out.frontend.frontend_cfg.dual_phy.value; - phy_cfg_o.which_phy = 1'b0; end // The legacy PR34 interface has no destinations for these newer fields. diff --git a/src/hyperbus_clk_gen.sv b/src/hyperbus_clk_gen.sv deleted file mode 100644 index 26400c8..0000000 --- a/src/hyperbus_clk_gen.sv +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright 2023 ETH Zurich and University of Bologna. -// Solderpad Hardware License, Version 0.51, see LICENSE for details. -// SPDX-License-Identifier: SHL-0.51 -// -// Hayate Okuhara - -/// Generates 4 phase shifted clocks out of one faster clock -(* no_ungroup *) -(* no_boundary_optimization *) -(* keep_hierarchy = "yes" *) -module hyperbus_clk_gen ( - input logic clk_i, // input clock - input logic rst_ni, - output logic clk0_o, // have the input clock - 0deg phase shift - output logic clk90_o, // have the input clock - 90deg phase shift - output logic clk180_o, // have the input clock - 180deg phase shift - output logic clk270_o, // have the input clock - 270deg phase shift - output logic rst_no -); - - - logic r_clk0_o; - logic r_clk90_o; - logic r_clk180_o; - logic r_clk270_o; - - logic s_clk0_n; - - assign clk0_o = r_clk0_o; - assign clk90_o = r_clk90_o; - assign clk180_o = r_clk180_o; - assign clk270_o = r_clk270_o; - assign rst_no = rst_ni; - - tc_clk_inverter i_clk0_inverter ( - .clk_i (r_clk0_o), - .clk_o (s_clk0_n) - ); - - - always_ff @(posedge clk_i or negedge rst_ni) begin - if(~rst_ni) begin - r_clk0_o <= 0; - r_clk180_o <= 1; - end else begin - r_clk0_o <= s_clk0_n; - r_clk180_o <= r_clk90_o; - end - end - - always_ff @(negedge clk_i or negedge rst_ni) begin - if (~rst_ni) begin - r_clk90_o <= 0; - r_clk270_o <= 1; - end else begin - r_clk90_o <= r_clk0_o; - r_clk270_o <= r_clk180_o; - end - end - -endmodule diff --git a/src/hyperbus_iso_bridge.sv b/src/hyperbus_iso_bridge.sv new file mode 100644 index 0000000..91db8fc --- /dev/null +++ b/src/hyperbus_iso_bridge.sv @@ -0,0 +1,206 @@ +// Copyright 2023 ETH Zurich and University of Bologna. +// Solderpad Hardware License, Version 0.51, see LICENSE for details. +// SPDX-License-Identifier: SHL-0.51 + +`include "common_cells/registers.svh" + +module hyperbus_iso_bridge #( + parameter int unsigned RxFifoLogDepth = 8, + parameter int unsigned TxFifoLogDepth = 3, + parameter type hyper_rx_t = logic, + parameter type hyper_tx_t = logic, + parameter type hyper_wrsp_t = logic, + parameter type hyper_cmd_t = logic, + parameter type hyper_req_t = logic, + parameter type hyper_rsp_t = logic +) ( + input logic clk_sys_i, + input logic rst_sys_ni, + input logic clk_phy_i, + input logic rst_phy_ni, + + input hyperbus_pkg::phy_cfg_t cfg_apply_i, + input logic frontend_cfg_apply_valid_i, + output logic frontend_cfg_apply_ready_o, + output logic frontend_cfg_apply_done_o, + + input hyper_req_t frontend_req_i, + output hyper_rsp_t frontend_rsp_o, + output hyper_req_t backend_req_o, + input hyper_rsp_t backend_rsp_i, + + output hyperbus_pkg::phy_cfg_t cfg_apply_o, + output logic cfg_apply_valid_o, + input logic cfg_apply_ready_i +); + + hyperbus_pkg::phy_cfg_t cfg_apply_data_q; + logic cfg_apply_accepted; + logic cfg_apply_pending_d; + logic cfg_apply_pending_q; + hyper_cmd_t cmd_data_q; + logic cmd_accepted; + hyper_wrsp_t wrsp_data_q; + logic wrsp_accepted; + hyper_tx_t tx_fifo_data; + logic tx_fifo_valid; + logic tx_fifo_ready; + hyper_rx_t rx_src_fifo_data; + logic rx_src_fifo_valid; + logic rx_src_fifo_ready; + logic rx_iso_valid; + logic rx_iso_ready; + hyper_rx_t rx_iso_data; + hyper_rx_t rx_fifo_data; + logic rx_fifo_valid; + logic rx_fifo_ready; + + assign cfg_apply_accepted = frontend_cfg_apply_valid_i && frontend_cfg_apply_ready_o; + assign frontend_cfg_apply_done_o = + cfg_apply_pending_q && frontend_cfg_apply_ready_o; + assign cmd_accepted = frontend_req_i.cmd_valid && frontend_rsp_o.cmd_ready; + assign wrsp_accepted = backend_rsp_i.wrsp_valid && backend_req_o.wrsp_ready; + + always_comb begin : proc_cfg_apply_pending + cfg_apply_pending_d = cfg_apply_pending_q; + + if (cfg_apply_accepted) begin + cfg_apply_pending_d = 1'b1; + end else if (frontend_cfg_apply_done_o) begin + cfg_apply_pending_d = 1'b0; + end + end + + `FFLARN(cfg_apply_data_q, cfg_apply_i, cfg_apply_accepted, '0, clk_sys_i, rst_sys_ni) + `FFARN(cfg_apply_pending_q, cfg_apply_pending_d, 1'b0, clk_sys_i, rst_sys_ni) + `FFLARN(cmd_data_q, frontend_req_i.cmd, cmd_accepted, '0, clk_sys_i, rst_sys_ni) + `FFLARN(wrsp_data_q, backend_rsp_i.wrsp, wrsp_accepted, '0, clk_phy_i, rst_phy_ni) + + isochronous_4phase_handshake i_iso_cfg_apply ( + .src_clk_i ( clk_sys_i ), + .src_rst_ni ( rst_sys_ni ), + .src_valid_i ( frontend_cfg_apply_valid_i ), + .src_ready_o ( frontend_cfg_apply_ready_o ), + .dst_clk_i ( clk_phy_i ), + .dst_rst_ni ( rst_phy_ni ), + .dst_valid_o ( cfg_apply_valid_o ), + .dst_ready_i ( cfg_apply_ready_i ) + ); + + assign cfg_apply_o = cfg_apply_data_q; + + isochronous_4phase_handshake i_iso_cmd ( + .src_clk_i ( clk_sys_i ), + .src_rst_ni ( rst_sys_ni ), + .src_valid_i ( frontend_req_i.cmd_valid ), + .src_ready_o ( frontend_rsp_o.cmd_ready ), + .dst_clk_i ( clk_phy_i ), + .dst_rst_ni ( rst_phy_ni ), + .dst_valid_o ( backend_req_o.cmd_valid ), + .dst_ready_i ( backend_rsp_i.cmd_ready ) + ); + + assign backend_req_o.cmd = cmd_data_q; + + isochronous_4phase_handshake i_iso_wrsp ( + .src_clk_i ( clk_phy_i ), + .src_rst_ni ( rst_phy_ni ), + .src_valid_i ( backend_rsp_i.wrsp_valid ), + .src_ready_o ( backend_req_o.wrsp_ready ), + .dst_clk_i ( clk_sys_i ), + .dst_rst_ni ( rst_sys_ni ), + .dst_valid_o ( frontend_rsp_o.wrsp_valid ), + .dst_ready_i ( frontend_req_i.wrsp_ready ) + ); + + assign frontend_rsp_o.wrsp = wrsp_data_q; + + stream_fifo #( + .FALL_THROUGH ( 1'b0 ), + .DEPTH ( 1 << TxFifoLogDepth ), + .T ( hyper_tx_t ) + ) i_tx_fifo ( + .clk_i ( clk_sys_i ), + .rst_ni ( rst_sys_ni ), + .flush_i ( 1'b0 ), + .testmode_i ( 1'b0 ), + .usage_o ( ), + .data_i ( frontend_req_i.tx ), + .valid_i ( frontend_req_i.tx_valid ), + .ready_o ( frontend_rsp_o.tx_ready ), + .data_o ( tx_fifo_data ), + .valid_o ( tx_fifo_valid ), + .ready_i ( tx_fifo_ready ) + ); + + isochronous_spill_register #( + .T ( hyper_tx_t ) + ) i_iso_tx ( + .src_clk_i ( clk_sys_i ), + .src_rst_ni ( rst_sys_ni ), + .src_valid_i ( tx_fifo_valid ), + .src_ready_o ( tx_fifo_ready ), + .src_data_i ( tx_fifo_data ), + .dst_clk_i ( clk_phy_i ), + .dst_rst_ni ( rst_phy_ni ), + .dst_valid_o ( backend_req_o.tx_valid ), + .dst_ready_i ( backend_rsp_i.tx_ready ), + .dst_data_o ( backend_req_o.tx ) + ); + + stream_fifo #( + .FALL_THROUGH ( 1'b0 ), + .DEPTH ( 1 << RxFifoLogDepth ), + .T ( hyper_rx_t ) + ) i_rx_src_fifo ( + .clk_i ( clk_phy_i ), + .rst_ni ( rst_phy_ni ), + .flush_i ( 1'b0 ), + .testmode_i ( 1'b0 ), + .usage_o ( ), + .data_i ( backend_rsp_i.rx ), + .valid_i ( backend_rsp_i.rx_valid ), + .ready_o ( backend_req_o.rx_ready ), + .data_o ( rx_src_fifo_data ), + .valid_o ( rx_src_fifo_valid ), + .ready_i ( rx_src_fifo_ready ) + ); + + isochronous_spill_register #( + .T ( hyper_rx_t ) + ) i_iso_rx ( + .src_clk_i ( clk_phy_i ), + .src_rst_ni ( rst_phy_ni ), + .src_valid_i ( rx_src_fifo_valid ), + .src_ready_o ( rx_src_fifo_ready ), + .src_data_i ( rx_src_fifo_data ), + .dst_clk_i ( clk_sys_i ), + .dst_rst_ni ( rst_sys_ni ), + .dst_valid_o ( rx_iso_valid ), + .dst_ready_i ( rx_iso_ready ), + .dst_data_o ( rx_iso_data ) + ); + + stream_fifo #( + .FALL_THROUGH ( 1'b0 ), + .DEPTH ( 1 << RxFifoLogDepth ), + .T ( hyper_rx_t ) + ) i_rx_fifo ( + .clk_i ( clk_sys_i ), + .rst_ni ( rst_sys_ni ), + .flush_i ( 1'b0 ), + .testmode_i ( 1'b0 ), + .usage_o ( ), + .data_i ( rx_iso_data ), + .valid_i ( rx_iso_valid ), + .ready_o ( rx_iso_ready ), + .data_o ( rx_fifo_data ), + .valid_o ( rx_fifo_valid ), + .ready_i ( rx_fifo_ready ) + ); + + assign frontend_rsp_o.rx = rx_fifo_data; + assign frontend_rsp_o.rx_valid = rx_fifo_valid; + assign rx_fifo_ready = frontend_req_i.rx_ready; + +endmodule diff --git a/src/hyperbus_isochronous.sv b/src/hyperbus_isochronous.sv new file mode 100644 index 0000000..fa4c7cc --- /dev/null +++ b/src/hyperbus_isochronous.sv @@ -0,0 +1,292 @@ +// Copyright 2023 ETH Zurich and University of Bologna. +// Solderpad Hardware License, Version 0.51, see LICENSE for details. +// SPDX-License-Identifier: SHL-0.51 + +`include "hyperbus/typedef.svh" +`include "common_cells/assertions.svh" +`include "common_cells/registers.svh" + +module hyperbus_isochronous #( + parameter int unsigned NumChips = -1, + parameter int unsigned NumPhys = 2, + parameter int unsigned AxiAddrWidth = -1, + parameter int unsigned AxiDataWidth = -1, + parameter int unsigned AxiIdWidth = -1, + parameter int unsigned AxiUserWidth = -1, + parameter type axi_req_t = logic, + parameter type axi_rsp_t = logic, + parameter int unsigned RegDataWidth = -1, + parameter type reg_req_t = logic, + parameter type reg_rsp_t = logic, + parameter type axi_rule_t = logic, + parameter int unsigned RxFifoLogDepth = 8, + parameter int unsigned TxFifoLogDepth = 3, + parameter int unsigned PhyStartupCycles = 300 * 200, + parameter int unsigned SyncStages = 2 +) ( + input logic clk_sys_i, + input logic rst_sys_ni, +`ifdef TARGET_XILINX + input logic clk_ref200_i, +`endif + input logic test_mode_i, + + input axi_req_t axi_req_i, + output axi_rsp_t axi_rsp_o, + + input reg_req_t reg_req_i, + output reg_rsp_t reg_rsp_o, + + output logic [NumPhys-1:0][NumChips-1:0] hyper_cs_no, + output logic [NumPhys-1:0] hyper_ck_o, + output logic [NumPhys-1:0] hyper_ck_no, + output logic [NumPhys-1:0] hyper_rwds_o, + input logic [NumPhys-1:0] hyper_rwds_i, + output logic [NumPhys-1:0] hyper_rwds_oe_o, + input logic [NumPhys-1:0][7:0] hyper_dq_i, + output logic [NumPhys-1:0][7:0] hyper_dq_o, + output logic [NumPhys-1:0] hyper_dq_oe_o, + output logic [NumPhys-1:0] hyper_reset_no +); + + `ASSERT_INIT(AxiAddrWidthValid, AxiAddrWidth >= $clog2(AxiDataWidth / 8)) + + typedef logic [AxiAddrWidth-1:0] host_addr_t; + typedef logic [AxiDataWidth-1:0] host_data_t; + typedef logic [AxiDataWidth/8-1:0] host_strb_t; + `HYPERBUS_TYPEDEF_HOST_ALL_CT(host, host_addr_t, host_data_t, host_strb_t) + `HYPERBUS_TYPEDEF_LINK_ALL_CT(hyper, NumPhys, NumChips) + + logic clk_backend; + logic rst_backend_n; + hyperbus_pkg::phy_cfg_t frontend_cfg_apply; + logic frontend_cfg_apply_valid; + logic frontend_cfg_apply_ready; + logic frontend_cfg_apply_done; + logic [7:0] frontend_clock_div_apply; + logic frontend_clock_div_apply_valid; + logic frontend_clock_div_apply_ready; + logic frontend_clock_div_apply_done; + logic frontend_drain; + logic host_idle; + hyperbus_pkg::phy_cfg_t backend_cfg_apply; + logic backend_cfg_apply_valid; + logic backend_cfg_apply_ready; + + hyperbus_pkg::frontend_cfg_t frontend_cfg; + axi_rule_t [NumChips-1:0] frontend_chip_rules; + + host_req_t host_req; + host_rsp_t host_rsp; + hyper_req_t midend_req; + hyper_rsp_t midend_rsp; + logic midend_trans_active; + logic midend_decode_error; + hyper_req_t backend_req; + hyper_rsp_t backend_rsp; + + logic clock_div_req_valid; + logic clock_div_update_accepted; + logic clock_div_update_pending_d; + logic clock_div_update_pending_q; + logic clock_alive_ready; + + assign clock_div_req_valid = frontend_clock_div_apply_valid && + clock_alive_ready && !clock_div_update_pending_q; + assign frontend_clock_div_apply_ready = clock_div_req_valid && + clock_div_update_accepted; + assign frontend_clock_div_apply_done = clock_div_update_pending_q && clock_alive_ready; + + always_comb begin : proc_clock_div_update_pending + clock_div_update_pending_d = clock_div_update_pending_q; + + if (clock_div_update_accepted) begin + clock_div_update_pending_d = 1'b1; + end else if (frontend_clock_div_apply_done) begin + clock_div_update_pending_d = 1'b0; + end + end + + `FFARN(clock_div_update_pending_q, clock_div_update_pending_d, 1'b0, + clk_sys_i, rst_sys_ni) + + clk_int_div #( + .DIV_VALUE_WIDTH ( 8 ), + .DEFAULT_DIV_VALUE ( 8 ) + ) i_clk_div ( + .clk_i ( clk_sys_i ), + .rst_ni ( rst_sys_ni ), + .en_i ( 1'b1 ), + .test_mode_en_i ( test_mode_i ), + .div_i ( frontend_clock_div_apply ), + .div_valid_i ( clock_div_req_valid ), + .div_ready_o ( clock_div_update_accepted ), + .clk_o ( clk_backend ), + .cycl_count_o ( ) + ); + + isochronous_4phase_handshake i_clock_alive ( + .src_clk_i ( clk_sys_i ), + .src_rst_ni ( rst_sys_ni ), + .src_valid_i ( clock_div_req_valid && clock_div_update_accepted ), + .src_ready_o ( clock_alive_ready ), + .dst_clk_i ( clk_backend ), + .dst_rst_ni ( rst_backend_n ), + .dst_valid_o ( ), + .dst_ready_i ( 1'b1 ) + ); + + `ASSERT(PhyClockDivValid, frontend_clock_div_apply_valid |-> + (frontend_clock_div_apply >= 2), clk_sys_i, !rst_sys_ni) + + rstgen i_rstgen_backend ( + .clk_i ( clk_backend ), + .rst_ni ( rst_sys_ni ), + .test_mode_i ( test_mode_i ), + .rst_no ( rst_backend_n ), + .init_no ( ) + ); + + hyperbus_cfg_frontend #( + .NumChips ( NumChips ), + .NumPhys ( NumPhys ), + .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 ) + ) i_cfg_frontend ( + .clk_i ( clk_sys_i ), + .rst_ni ( rst_sys_ni ), + .reg_req_i ( reg_req_i ), + .reg_rsp_o ( reg_rsp_o ), + .drain_o ( frontend_drain ), + .host_idle_i ( host_idle ), + .trans_active_i ( midend_trans_active ), + .cfg_apply_o ( frontend_cfg_apply ), + .cfg_apply_valid_o ( frontend_cfg_apply_valid ), + .cfg_apply_ready_i ( frontend_cfg_apply_ready ), + .cfg_apply_done_i ( frontend_cfg_apply_done ), + .clock_div_apply_o ( frontend_clock_div_apply ), + .clock_div_apply_valid_o ( frontend_clock_div_apply_valid ), + .clock_div_apply_ready_i ( frontend_clock_div_apply_ready ), + .clock_div_apply_done_i ( frontend_clock_div_apply_done ), + .frontend_cfg_o ( frontend_cfg ), + .chip_rules_o ( frontend_chip_rules ), + .decode_error_i ( midend_decode_error ) + ); + + hyperbus_axi_frontend #( + .AxiDataWidth ( AxiDataWidth ), + .AxiAddrWidth ( AxiAddrWidth ), + .AxiIdWidth ( AxiIdWidth ), + .AxiUserWidth ( AxiUserWidth ), + .axi_req_t ( axi_req_t ), + .axi_rsp_t ( axi_rsp_t ), + .host_req_t ( host_req_t ), + .host_rsp_t ( host_rsp_t ) + ) i_axi_frontend ( + .clk_i ( clk_sys_i ), + .rst_ni ( rst_sys_ni ), + .drain_i ( frontend_drain ), + .idle_o ( host_idle ), + .axi_req_i ( axi_req_i ), + .axi_rsp_o ( axi_rsp_o ), + .host_req_o ( host_req ), + .host_rsp_i ( host_rsp ) + ); + + hyperbus_midend #( + .HostAddrWidth ( AxiAddrWidth ), + .HostDataWidth ( AxiDataWidth ), + .NumChips ( NumChips ), + .NumPhys ( NumPhys ), + .host_cmd_t ( host_cmd_t ), + .host_w_t ( host_w_t ), + .host_r_t ( host_r_t ), + .host_wrsp_t ( host_wrsp_t ), + .host_req_t ( host_req_t ), + .host_rsp_t ( host_rsp_t ), + .hyper_rx_t ( hyper_rx_t ), + .hyper_tx_t ( hyper_tx_t ), + .hyper_cmd_t ( hyper_cmd_t ), + .hyper_req_t ( hyper_req_t ), + .hyper_rsp_t ( hyper_rsp_t ), + .rule_t ( axi_rule_t ) + ) i_midend ( + .clk_i ( clk_sys_i ), + .rst_ni ( rst_sys_ni ), + .host_link_req_i ( host_req ), + .host_link_rsp_o ( host_rsp ), + .frontend_cfg_i ( frontend_cfg ), + .chip_rules_i ( frontend_chip_rules ), + .trans_active_o ( midend_trans_active ), + .decode_error_o ( midend_decode_error ), + .hyper_link_req_o ( midend_req ), + .hyper_link_rsp_i ( midend_rsp ) + ); + + hyperbus_iso_bridge #( + .RxFifoLogDepth ( RxFifoLogDepth ), + .TxFifoLogDepth ( TxFifoLogDepth ), + .hyper_rx_t ( hyper_rx_t ), + .hyper_tx_t ( hyper_tx_t ), + .hyper_wrsp_t ( hyper_wrsp_t ), + .hyper_cmd_t ( hyper_cmd_t ), + .hyper_req_t ( hyper_req_t ), + .hyper_rsp_t ( hyper_rsp_t ) + ) i_bridge ( + .clk_sys_i ( clk_sys_i ), + .rst_sys_ni ( rst_sys_ni ), + .clk_phy_i ( clk_backend ), + .rst_phy_ni ( rst_backend_n ), + .cfg_apply_i ( frontend_cfg_apply ), + .frontend_cfg_apply_valid_i ( frontend_cfg_apply_valid ), + .frontend_cfg_apply_ready_o ( frontend_cfg_apply_ready ), + .frontend_cfg_apply_done_o ( frontend_cfg_apply_done ), + .frontend_req_i ( midend_req ), + .frontend_rsp_o ( midend_rsp ), + .backend_req_o ( backend_req ), + .backend_rsp_i ( backend_rsp ), + .cfg_apply_o ( backend_cfg_apply ), + .cfg_apply_valid_o ( backend_cfg_apply_valid ), + .cfg_apply_ready_i ( backend_cfg_apply_ready ) + ); + + hyperbus_backend #( + .NumChips ( NumChips ), + .NumPhys ( NumPhys ), + .StartupCycles ( PhyStartupCycles ), + .SyncStages ( SyncStages ), + .hyper_rx_t ( hyper_rx_t ), + .hyper_tx_t ( hyper_tx_t ), + .hyper_req_t ( hyper_req_t ), + .hyper_rsp_t ( hyper_rsp_t ) + ) i_backend ( + .clk_i ( clk_backend ), + .rst_ni ( rst_backend_n ), +`ifdef TARGET_XILINX + .clk_ref200_i ( clk_ref200_i ), +`endif + .test_mode_i ( test_mode_i ), + .cfg_apply_i ( backend_cfg_apply ), + .cfg_apply_valid_i ( backend_cfg_apply_valid ), + .cfg_apply_ready_o ( backend_cfg_apply_ready ), + .busy_o ( ), + .req_i ( backend_req ), + .rsp_o ( backend_rsp ), + .hyper_cs_no ( hyper_cs_no ), + .hyper_ck_o ( hyper_ck_o ), + .hyper_ck_no ( hyper_ck_no ), + .hyper_rwds_o ( hyper_rwds_o ), + .hyper_rwds_i ( hyper_rwds_i ), + .hyper_rwds_oe_o ( hyper_rwds_oe_o ), + .hyper_dq_i ( hyper_dq_i ), + .hyper_dq_o ( hyper_dq_o ), + .hyper_dq_oe_o ( hyper_dq_oe_o ), + .hyper_reset_no ( hyper_reset_no ) + ); + +endmodule diff --git a/src/hyperbus_midend.sv b/src/hyperbus_midend.sv new file mode 100644 index 0000000..73f78cd --- /dev/null +++ b/src/hyperbus_midend.sv @@ -0,0 +1,558 @@ +// Copyright 2026 ETH Zurich and University of Bologna. +// Solderpad Hardware License, Version 0.51, see LICENSE for details. +// SPDX-License-Identifier: SHL-0.51 + +`include "common_cells/registers.svh" +`include "common_cells/assertions.svh" + +module hyperbus_midend #( + parameter int unsigned HostAddrWidth = -1, + parameter int unsigned HostDataWidth = -1, + parameter int unsigned NumChips = -1, + parameter int unsigned NumPhys = -1, + parameter type host_cmd_t = logic, + parameter type host_w_t = logic, + parameter type host_r_t = logic, + parameter type host_wrsp_t = logic, + parameter type host_req_t = logic, + parameter type host_rsp_t = logic, + parameter type hyper_rx_t = logic, + parameter type hyper_tx_t = logic, + parameter type hyper_cmd_t = logic, + parameter type hyper_req_t = logic, + parameter type hyper_rsp_t = logic, + parameter type rule_t = logic +) ( + input logic clk_i, + input logic rst_ni, + + input host_req_t host_link_req_i, + output host_rsp_t host_link_rsp_o, + + input hyperbus_pkg::frontend_cfg_t frontend_cfg_i, + input rule_t [NumChips-1:0] chip_rules_i, + output logic trans_active_o, + output logic decode_error_o, + + output hyper_req_t hyper_link_req_o, + input hyper_rsp_t hyper_link_rsp_i +); + + localparam int unsigned HostDataBytes = HostDataWidth / 8; + localparam int unsigned HostBusAddrWidth = $clog2(HostDataBytes); + localparam int unsigned PhyDataWidth = NumPhys * 16; + localparam int unsigned ChipSelWidth = cf_math_pkg::idx_width(NumChips); + + `ASSERT_INIT(NumChipsValid, NumChips >= 1 && NumChips <= 8) + `ASSERT_INIT(NumPhysValid, NumPhys == 1 || NumPhys == 2) + `ASSERT_INIT(HostAddrWidthValid, HostAddrWidth >= HostBusAddrWidth) + `ASSERT_INIT(HostDataWidthValid, + HostDataWidth >= PhyDataWidth && HostDataWidth <= 1024 && + (HostDataWidth & (HostDataWidth - 1)) == 0 && + (HostDataWidth % PhyDataWidth) == 0) + + typedef logic [HostAddrWidth-1:0] host_addr_t; + typedef logic [HostAddrWidth:0] host_ext_addr_t; + typedef logic [ChipSelWidth-1:0] chip_sel_idx_t; + + // Unpack the aggregate links at the midend boundary. + host_cmd_t host_cmd_i; + logic host_req_valid_i; + logic host_req_ready_o; + host_w_t host_w_i; + logic host_w_valid_i; + logic host_w_ready_o; + host_r_t host_r_o; + logic host_r_valid_o; + logic host_r_ready_i; + host_wrsp_t host_wrsp_o; + logic host_wrsp_valid_o; + logic host_wrsp_ready_i; + hyper_rx_t rx_i; + logic rx_valid_i; + logic rx_ready_o; + hyper_tx_t tx_o; + logic tx_valid_o; + logic tx_ready_i; + logic wrsp_error_i; + logic wrsp_valid_i; + logic wrsp_ready_o; + hyper_cmd_t cmd_o; + logic cmd_valid_o; + logic cmd_ready_i; + + assign host_cmd_i = host_link_req_i.cmd; + assign host_req_valid_i = host_link_req_i.cmd_valid; + assign host_w_i = host_link_req_i.w; + assign host_w_valid_i = host_link_req_i.w_valid; + assign host_r_ready_i = host_link_req_i.r_ready; + assign host_wrsp_ready_i = host_link_req_i.wrsp_ready; + + assign host_link_rsp_o.cmd_ready = host_req_ready_o; + assign host_link_rsp_o.w_ready = host_w_ready_o; + assign host_link_rsp_o.r = host_r_o; + assign host_link_rsp_o.r_valid = host_r_valid_o; + assign host_link_rsp_o.wrsp = host_wrsp_o; + assign host_link_rsp_o.wrsp_valid = host_wrsp_valid_o; + + assign rx_i = hyper_link_rsp_i.rx; + assign rx_valid_i = hyper_link_rsp_i.rx_valid; + assign tx_ready_i = hyper_link_rsp_i.tx_ready; + assign wrsp_error_i = hyper_link_rsp_i.wrsp.error; + assign wrsp_valid_i = hyper_link_rsp_i.wrsp_valid; + assign cmd_ready_i = hyper_link_rsp_i.cmd_ready; + + assign hyper_link_req_o.rx_ready = rx_ready_o; + assign hyper_link_req_o.tx = tx_o; + assign hyper_link_req_o.tx_valid = tx_valid_o; + assign hyper_link_req_o.wrsp_ready = wrsp_ready_o; + assign hyper_link_req_o.cmd = cmd_o; + assign hyper_link_req_o.cmd_valid = cmd_valid_o; + + // Command decode and invalid-request response tracking. + typedef enum logic [1:0] { + ErrorIdle, + ErrorRead, + ErrorWrite, + ErrorWriteResp + } error_state_e; + + chip_sel_idx_t cmd_chip_sel_idx; + chip_sel_idx_t cmd_end_chip_sel_idx; + logic command_accepted; + logic adapter_started; + logic cmd_dec_valid; + logic cmd_dec_error; + logic cmd_end_dec_valid; + logic req_range_valid; + logic req_decode_error; + logic error_req_accepted; + error_state_e error_state_d, error_state_q; + hyperbus_pkg::hyper_blen_t error_beats_d, error_beats_q; + + host_r_t converted_host_r; + logic converted_host_r_valid; + logic converted_host_r_ready; + logic converted_host_w_ready; + host_w_t converted_host_w; + logic converted_host_w_valid; + + // Address decode and command segmentation. + host_cmd_t cmd_req; + host_addr_t req_phy_first_addr; + host_ext_addr_t req_phy_first_ext; + host_ext_addr_t req_phy_end_addr; + host_ext_addr_t req_last_addr; + host_ext_addr_t req_phy_bytes; + hyperbus_pkg::hyper_blen_t req_phy_burst; + host_ext_addr_t cmd_rule_end_addr; + logic req_addr_overflow; + logic atomic_range_valid; + + host_cmd_t segment_req_d, segment_req_q; + hyperbus_pkg::hyper_blen_t segment_remaining_d, segment_remaining_q; + hyperbus_pkg::hyper_blen_t cmd_remaining; + host_ext_addr_t cmd_rule_capacity; + hyperbus_pkg::hyper_blen_t cmd_segment_burst; + host_addr_t cmd_next_addr; + logic segment_pending_d, segment_pending_q; + logic segment_final_d, segment_final_q; + logic segment_wrsp_error_d, segment_wrsp_error_q; + logic cmd_segment_final; + logic normal_cmd_accepted; + logic normal_segment_complete; + // Atomic requests use the same command and data adapters as normal traffic. + logic atomic_req_accepted; + logic atomic_cmd_valid; + logic atomic_active; + logic atomic_completed; + logic normal_req; + logic atomic_request_valid; + host_cmd_t atomic_cmd; + host_w_t atomic_host_w; + logic atomic_host_w_valid; + logic atomic_host_w_ready; + host_r_t atomic_host_r; + logic atomic_host_r_valid; + host_wrsp_t atomic_host_wrsp; + logic atomic_host_wrsp_valid; + logic atomic_read_ready; + logic atomic_wrsp_ready; + + logic adapter_rx_last; + + logic trans_active_d; + logic trans_active_q; + logic trans_active_set; + logic trans_active_reset; + logic host_req_accepted; + + assign normal_req = host_cmd_i.atomic_op == hyperbus_pkg::HyperAtomicNone; + assign atomic_request_valid = + (host_cmd_i.atomic_op != hyperbus_pkg::HyperAtomicInvalid) && + (host_cmd_i.beats == 1) && + (host_cmd_i.burst == hyperbus_pkg::HyperBurstIncr) && + (host_cmd_i.size <= HostBusAddrWidth) && + ((host_cmd_i.atomic_op != hyperbus_pkg::HyperAtomicCompare) || + (host_cmd_i.size != '0)) && + atomic_range_valid; + assign cmd_valid_o = atomic_cmd_valid || + segment_pending_q || + (host_req_valid_i && !trans_active_q && cmd_dec_valid && + req_range_valid && normal_req); + assign host_req_ready_o = !trans_active_q && + (normal_req ? + (req_decode_error || (cmd_dec_valid && cmd_ready_i)) : 1'b1); + assign host_req_accepted = host_req_valid_i && host_req_ready_o; + assign command_accepted = cmd_valid_o && cmd_ready_i; + assign normal_cmd_accepted = command_accepted && !atomic_cmd_valid; + assign adapter_started = atomic_cmd_valid ? command_accepted : + (normal_cmd_accepted && !segment_pending_q); + assign error_req_accepted = host_req_accepted && req_decode_error; + assign atomic_req_accepted = host_req_accepted && !normal_req; + assign decode_error_o = error_req_accepted || + (atomic_req_accepted && !atomic_range_valid); + assign trans_active_o = trans_active_q; + + always_comb begin : proc_cmd_cs + cmd_o.cs = '0; + if (cmd_dec_valid) begin + cmd_o.cs[cmd_chip_sel_idx] = 1'b1; + end + end + + always_comb begin : proc_cmd_req + cmd_req = host_cmd_i; + if (segment_pending_q) begin + cmd_req = segment_req_q; + end + if (atomic_active) begin + cmd_req = atomic_cmd; + end + end + + // Cover every aligned PHY group touched by the host transaction. + always_comb begin : proc_req_phy_range + req_phy_first_addr = (cmd_req.addr >> NumPhys) << NumPhys; + req_phy_first_ext = host_ext_addr_t'(req_phy_first_addr); + req_last_addr = (host_ext_addr_t'(cmd_req.addr >> cmd_req.size) << cmd_req.size) + + (host_ext_addr_t'(cmd_req.beats) << cmd_req.size); + req_phy_bytes = ((req_last_addr - req_phy_first_ext) + + ((host_ext_addr_t'(1) << NumPhys) - 1)) >> NumPhys; + req_phy_bytes = req_phy_bytes << NumPhys; + req_phy_burst = hyperbus_pkg::hyper_blen_t'(req_phy_bytes >> 1); + req_phy_end_addr = req_phy_first_ext + req_phy_bytes; + req_addr_overflow = (req_last_addr > (host_ext_addr_t'(1) << HostAddrWidth)) || + (req_phy_end_addr > (host_ext_addr_t'(1) << HostAddrWidth)); + end + + // Software keeps ranges ordered and non-overlapping; transactions may cross contiguous ranges. + always_comb begin : proc_req_rule_range + cmd_chip_sel_idx = '0; + cmd_end_chip_sel_idx = '0; + cmd_dec_valid = 1'b0; + cmd_end_dec_valid = 1'b0; + cmd_rule_end_addr = req_phy_first_ext; + + for (int unsigned i = 0; i < NumChips; i++) begin + host_ext_addr_t rule_start; + host_ext_addr_t rule_end; + + rule_start = host_ext_addr_t'(chip_rules_i[i].start_addr); + rule_end = (chip_rules_i[i].end_addr == '0) ? + (host_ext_addr_t'(1) << HostAddrWidth) : + host_ext_addr_t'(chip_rules_i[i].end_addr); + if (!cmd_dec_valid && (req_phy_first_ext >= rule_start) && + (req_phy_first_ext < rule_end)) begin + cmd_chip_sel_idx = chip_sel_idx_t'(i); + cmd_dec_valid = 1'b1; + cmd_rule_end_addr = rule_end; + end + if (!cmd_end_dec_valid && ((req_phy_end_addr - 1'b1) >= rule_start) && + ((req_phy_end_addr - 1'b1) < rule_end)) begin + cmd_end_chip_sel_idx = chip_sel_idx_t'(i); + cmd_end_dec_valid = 1'b1; + end + end + + req_range_valid = !req_addr_overflow && cmd_dec_valid && cmd_end_dec_valid && + (cmd_end_chip_sel_idx >= cmd_chip_sel_idx); + for (int unsigned i = 0; i < NumChips - 1; i++) begin + 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; + end + end + end + + assign cmd_dec_error = !cmd_dec_valid; + assign atomic_range_valid = req_range_valid && + (cmd_chip_sel_idx == cmd_end_chip_sel_idx); + + assign req_decode_error = normal_req && + (cmd_dec_error || + (!atomic_active && !segment_pending_q && !req_range_valid)); + + assign cmd_o.trans.write = cmd_req.write; + assign cmd_o.trans.burst_type = 1'b1; // Wrapping HyperBus bursts are not supported. + assign cmd_o.trans.address_space = frontend_cfg_i.address_space; + assign cmd_o.trans.address = (NumPhys == 2) ? + (frontend_cfg_i.dual_phy ? + ((req_phy_first_addr & + ((host_addr_t'(1) << frontend_cfg_i.address_mask_msb) - 1)) >> 2) : + (((req_phy_first_addr & + ((host_addr_t'(1) << frontend_cfg_i.address_mask_msb) - 1)) >> 2) << 1)) : + ((req_phy_first_addr & + ((host_addr_t'(1) << frontend_cfg_i.address_mask_msb) - 1)) >> 1); + + always_comb begin : proc_cmd_segment + cmd_remaining = segment_pending_q ? segment_remaining_q : req_phy_burst; + cmd_rule_capacity = host_ext_addr_t'(cmd_remaining); + if (cmd_dec_valid) begin + cmd_rule_capacity = (cmd_rule_end_addr - req_phy_first_ext) >> 1; + end + cmd_segment_burst = cmd_remaining; + if (cmd_rule_capacity < host_ext_addr_t'(cmd_remaining)) begin + cmd_segment_burst = hyperbus_pkg::hyper_blen_t'(cmd_rule_capacity); + end + if (atomic_cmd_valid) begin + cmd_segment_burst = req_phy_burst; + end + + cmd_segment_final = atomic_cmd_valid || (cmd_segment_burst == cmd_remaining); + cmd_next_addr = host_addr_t'(req_phy_first_ext + + (host_ext_addr_t'(cmd_segment_burst) << 1)); + cmd_o.trans.burst = cmd_segment_burst; + end + + assign adapter_rx_last = rx_i.last && (atomic_active || segment_final_q); + + hyperbus_read_adapter #( + .HostDataWidth ( HostDataWidth ), + .BurstLength ( hyperbus_pkg::HyperBurstWidth ), + .T ( host_r_t ), + .NumPhys ( NumPhys ) + ) i_read_adapter ( + .clk_i, + .rst_ni, + .size_i ( cmd_req.size ), + .start_i ( adapter_started && !cmd_req.write ), + .dual_phy_i ( frontend_cfg_i.dual_phy ), + .start_addr_i ( cmd_req.addr[HostBusAddrWidth-1:0] ), + .burst_len_i ( cmd_req.beats ), + .phy_valid_i ( rx_valid_i ), + .phy_ready_o ( rx_ready_o ), + .data_i ( rx_i.data ), + .last_i ( adapter_rx_last ), + .error_i ( rx_i.error ), + .host_valid_o ( converted_host_r_valid ), + .host_ready_i ( converted_host_r_ready ), + .data_o ( converted_host_r ) + ); + + hyperbus_write_adapter #( + .HostDataWidth ( HostDataWidth ), + .T ( host_w_t ), + .NumPhys ( NumPhys ) + ) i_write_adapter ( + .clk_i, + .rst_ni, + .size_i ( cmd_req.size ), + .start_i ( adapter_started && cmd_req.write ), + .dual_phy_i ( frontend_cfg_i.dual_phy ), + .start_addr_i ( cmd_req.addr[HostBusAddrWidth-1:0] ), + .data_i ( converted_host_w ), + .host_valid_i ( converted_host_w_valid ), + .host_ready_o ( converted_host_w_ready ), + .data_o ( tx_o.data ), + .last_o ( tx_o.last ), + .strb_o ( tx_o.strb ), + .phy_valid_o ( tx_valid_o ), + .phy_ready_i ( tx_ready_i ) + ); + + hyperbus_atomic_handler #( + .HostAddrWidth ( HostAddrWidth ), + .HostDataWidth ( HostDataWidth ), + .host_cmd_t ( host_cmd_t ), + .host_w_t ( host_w_t ), + .host_r_t ( host_r_t ), + .host_wrsp_t ( host_wrsp_t ) + ) i_atomic_handler ( + .clk_i, + .rst_ni, + .start_i ( atomic_req_accepted ), + .request_valid_i ( atomic_request_valid ), + .request_i ( host_cmd_i ), + .active_o ( atomic_active ), + .completed_o ( atomic_completed ), + .command_o ( atomic_cmd ), + .command_valid_o ( atomic_cmd_valid ), + .command_ready_i ( cmd_ready_i ), + .host_w_i ( host_w_i ), + .host_w_valid_i ( host_w_valid_i ), + .host_w_ready_o ( atomic_host_w_ready ), + .host_r_o ( atomic_host_r ), + .host_r_valid_o ( atomic_host_r_valid ), + .host_r_ready_i ( host_r_ready_i ), + .host_wrsp_o ( atomic_host_wrsp ), + .host_wrsp_valid_o ( atomic_host_wrsp_valid ), + .host_wrsp_ready_i ( host_wrsp_ready_i ), + .read_i ( converted_host_r ), + .read_valid_i ( converted_host_r_valid ), + .read_ready_o ( atomic_read_ready ), + .write_o ( atomic_host_w ), + .write_valid_o ( atomic_host_w_valid ), + .write_ready_i ( converted_host_w_ready ), + .write_rsp_error_i ( wrsp_error_i ), + .write_rsp_valid_i ( wrsp_valid_i ), + .write_rsp_ready_o ( atomic_wrsp_ready ) + ); + + assign normal_segment_complete = trans_active_q && !atomic_active && + (segment_req_q.write ? (wrsp_valid_i && wrsp_ready_o) : + (rx_valid_i && rx_ready_o && rx_i.last)); + + always_comb begin : proc_segments + segment_req_d = segment_req_q; + segment_remaining_d = segment_remaining_q; + segment_pending_d = segment_pending_q; + segment_final_d = segment_final_q; + segment_wrsp_error_d = segment_wrsp_error_q; + + if (normal_segment_complete && !segment_final_q) begin + segment_pending_d = 1'b1; + if (segment_req_q.write && wrsp_error_i) begin + segment_wrsp_error_d = 1'b1; + end + end + if (normal_cmd_accepted) begin + segment_pending_d = 1'b0; + segment_final_d = cmd_segment_final; + segment_req_d = cmd_req; + segment_req_d.addr = cmd_next_addr; + segment_remaining_d = cmd_remaining - cmd_segment_burst; + if (!trans_active_q) begin + segment_wrsp_error_d = 1'b0; + end + end + end + + `FFARN(segment_req_q, segment_req_d, '0, clk_i, rst_ni) + `FFARN(segment_remaining_q, segment_remaining_d, '0, clk_i, rst_ni) + `FFARN(segment_pending_q, segment_pending_d, 1'b0, clk_i, rst_ni) + `FFARN(segment_final_q, segment_final_d, 1'b1, clk_i, rst_ni) + `FFARN(segment_wrsp_error_q, segment_wrsp_error_d, 1'b0, clk_i, rst_ni) + + always_comb begin : proc_error_control + error_state_d = error_state_q; + error_beats_d = error_beats_q; + + unique case (error_state_q) + ErrorIdle: begin + if (error_req_accepted) begin + error_beats_d = host_cmd_i.beats; + error_state_d = host_cmd_i.write ? ErrorWrite : ErrorRead; + end + end + ErrorRead: begin + if (host_r_valid_o && host_r_ready_i) begin + error_beats_d = error_beats_q - 1'b1; + if (error_beats_q == 1) begin + error_state_d = ErrorIdle; + end + end + end + ErrorWrite: begin + if (host_w_valid_i && host_w_ready_o && host_w_i.last) begin + error_state_d = ErrorWriteResp; + end + end + ErrorWriteResp: begin + if (host_wrsp_valid_o && host_wrsp_ready_i) begin + error_state_d = ErrorIdle; + end + end + default: begin + error_state_d = ErrorIdle; + end + endcase + end + + always_comb begin : proc_host_mux + converted_host_w = host_w_i; + converted_host_w_valid = host_w_valid_i; + host_w_ready_o = converted_host_w_ready; + host_r_o = converted_host_r; + host_r_valid_o = converted_host_r_valid; + converted_host_r_ready = host_r_ready_i; + host_wrsp_o.resp = (wrsp_error_i || segment_wrsp_error_q) ? + hyperbus_pkg::HyperRespAccessError : + hyperbus_pkg::HyperRespOkay; + host_wrsp_o.atomic_ok = 1'b0; + host_wrsp_valid_o = wrsp_valid_i && segment_final_q; + wrsp_ready_o = segment_final_q ? host_wrsp_ready_i : 1'b1; + + if (atomic_active) begin + converted_host_w = atomic_host_w; + converted_host_w_valid = atomic_host_w_valid; + host_w_ready_o = atomic_host_w_ready; + host_r_o = atomic_host_r; + host_r_valid_o = atomic_host_r_valid; + converted_host_r_ready = atomic_read_ready; + host_wrsp_o = atomic_host_wrsp; + host_wrsp_valid_o = atomic_host_wrsp_valid; + wrsp_ready_o = atomic_wrsp_ready; + end + + if (error_state_q != ErrorIdle) begin + converted_host_w = '0; + converted_host_w_valid = 1'b0; + host_w_ready_o = error_state_q == ErrorWrite; + host_r_o = '0; + host_r_o.resp = hyperbus_pkg::HyperRespDecodeError; + host_r_o.last = error_beats_q == 1; + host_r_valid_o = error_state_q == ErrorRead; + converted_host_r_ready = 1'b0; + host_wrsp_o = '0; + host_wrsp_o.resp = hyperbus_pkg::HyperRespDecodeError; + host_wrsp_valid_o = error_state_q == ErrorWriteResp; + wrsp_ready_o = 1'b0; + end + end + + `FFARN(error_state_q, error_state_d, ErrorIdle, clk_i, rst_ni) + `FFARN(error_beats_q, error_beats_d, '0, clk_i, rst_ni) + + assign trans_active_set = (normal_cmd_accepted && !segment_pending_q) || + error_req_accepted || atomic_req_accepted; + assign trans_active_reset = atomic_active ? atomic_completed : + ((host_r_valid_o && host_r_ready_i && host_r_o.last) || + (host_wrsp_valid_o && host_wrsp_ready_i)); + + always_comb begin : proc_trans_active + trans_active_d = trans_active_q; + + if (trans_active_reset) begin + trans_active_d = 1'b0; + end + if (trans_active_set) begin + trans_active_d = 1'b1; + end + end + + `FFARN(trans_active_q, trans_active_d, 1'b0, clk_i, rst_ni) + + `ASSERT_KNOWN_IF(HostReqKnown, host_cmd_i, host_req_accepted) + `ASSERT(HostReqBeatsNonzero, host_req_accepted |-> (host_cmd_i.beats != '0)) + `ASSERT(HostReqSizeValid, host_req_accepted |-> (host_cmd_i.size <= HostBusAddrWidth)) + `ASSERT(HostReqBurstValid, host_req_accepted |-> + ((host_cmd_i.burst == hyperbus_pkg::HyperBurstIncr) || + ((host_cmd_i.burst == hyperbus_pkg::HyperBurstFixed) && (host_cmd_i.beats == 1)))) + `ASSERT(HostReqAtomicValid, host_req_accepted |-> + (host_cmd_i.atomic_op <= hyperbus_pkg::HyperAtomicUnsignedMin)) + `ASSERT(HostReqAtomicWrite, (host_req_accepted && + (host_cmd_i.atomic_op != hyperbus_pkg::HyperAtomicNone)) |-> host_cmd_i.write) + `ASSERT(BackendBurstNonzero, command_accepted |-> (cmd_o.trans.burst != '0)) + +endmodule diff --git a/src/hyperbus_phy2r.sv b/src/hyperbus_phy2r.sv deleted file mode 100644 index 5415e4c..0000000 --- a/src/hyperbus_phy2r.sv +++ /dev/null @@ -1,155 +0,0 @@ -// Copyright 2023 ETH Zurich and University of Bologna. -// Solderpad Hardware License, Version 0.51, see LICENSE for details. -// SPDX-License-Identifier: SHL-0.51 -// -// Luca Valente - -module hyperbus_phy2r #( - parameter int unsigned AxiDataWidth = -1, - parameter int unsigned NumPhys = -1, - parameter type T = logic, - parameter int unsigned BurstLength = -1, - parameter int unsigned AddrWidth = $clog2(AxiDataWidth/8) -) ( - input logic clk_i, - input logic rst_ni, - input logic [2:0] size, - input logic is_a_read, - input logic trans_handshake, - input logic [AddrWidth-1:0] start_addr, - input logic [BurstLength-1:0] burst_len, - output logic axi_valid_o, - input logic axi_ready_i, - output T data_o, - input logic phy_valid_i, - output logic phy_ready_o, - input logic [16*NumPhys-1:0] data_i, - input logic last_i, - input logic error_i -); - - // Cutting the combinatorial path between cdc fifo and AXI Master - typedef enum logic [2:0] { - Idle, - WaitData, - Sample, - CntReady - } hyper_splitter_state_t; - - hyper_splitter_state_t state_d, state_q; - - localparam int unsigned NumAxiBytes = AxiDataWidth/8; - localparam int unsigned NumPhyBytes = NumPhys*2; - localparam int unsigned AxiBytesInPhyBeat = NumAxiBytes/NumPhyBytes; - localparam int unsigned WordCntWidth = (AxiBytesInPhyBeat==1) ? 1 : $clog2(AxiBytesInPhyBeat); - - logic [BurstLength-1:0] byte_axi_addr_d, byte_axi_addr_q; - logic [BurstLength-1:0] byte_phy_cnt_d, byte_phy_cnt_q; - logic [BurstLength-1:0] last_addr_d, last_addr_q; - - logic [3:0] size_d, size_q; - T data_buffer_d, data_buffer_q; - - logic is_16_bw, is_8_bw; - logic [WordCntWidth-1:0] word_cnt; - logic enough_data; - logic sent_available_data; - logic [BurstLength-1:0] next_axi_addr; - - assign word_cnt = (AxiBytesInPhyBeat==1) ? '0 : byte_phy_cnt_q[($clog2(NumPhys)+1) +:WordCntWidth]; - assign next_axi_addr = ((byte_axi_addr_q>>size_d)<< size_d) + (1<= next_axi_addr; - assign sent_available_data = byte_axi_addr_d >= byte_phy_cnt_q; - assign data_o.data = data_buffer_q.data; - assign data_o.error = data_buffer_q.error; - assign data_o.valid = '0; - assign data_o.last = data_buffer_q.last && (last_addr_q==byte_axi_addr_d); - - always_comb begin : counter - byte_axi_addr_d = byte_axi_addr_q; - size_d = size_q; - byte_phy_cnt_d = byte_phy_cnt_q; - last_addr_d = last_addr_q; - if (trans_handshake & is_a_read) begin - byte_axi_addr_d[BurstLength-1:AddrWidth] = '0; - byte_axi_addr_d[AddrWidth-1:0] = start_addr; - size_d = size; - byte_phy_cnt_d[BurstLength-1:AddrWidth] = '0; - byte_phy_cnt_d[AddrWidth-1:0] = (start_addr>>NumPhys)<>size)<>size_q)<< size_q) + (1< + +`include "common_cells/registers.svh" + +module hyperbus_read_adapter #( + parameter int unsigned HostDataWidth = -1, + parameter int unsigned NumPhys = -1, + parameter type T = logic, + parameter int unsigned BurstLength = -1, + parameter int unsigned AddrWidth = $clog2(HostDataWidth / 8) +) ( + input logic clk_i, + input logic rst_ni, + input logic [2:0] size_i, + input logic start_i, + input logic dual_phy_i, + input logic [AddrWidth-1:0] start_addr_i, + input logic [BurstLength-1:0] burst_len_i, + output logic host_valid_o, + input logic host_ready_i, + output T data_o, + input logic phy_valid_i, + output logic phy_ready_o, + input logic [16*NumPhys-1:0] data_i, + input logic last_i, + input logic error_i +); + + localparam int unsigned NumHostBytes = HostDataWidth / 8; + localparam int unsigned NumPhyBytes = NumPhys * 2; + localparam int unsigned PhyBeatsPerHost = NumHostBytes / NumPhyBytes; + localparam int unsigned WordCntWidth = + (PhyBeatsPerHost == 1) ? 1 : $clog2(PhyBeatsPerHost); + + typedef enum logic [2:0] { + Idle, + WaitData, + Sample, + CntReady + } read_adapter_state_e; + + read_adapter_state_e state_d, state_q; + logic [BurstLength-1:0] byte_host_addr_d, byte_host_addr_q; + logic [BurstLength-1:0] byte_phy_cnt_d, byte_phy_cnt_q; + logic [BurstLength-1:0] last_addr_d, last_addr_q; + logic [3:0] size_d, size_q; + T data_buffer_d, data_buffer_q; + logic [WordCntWidth-1:0] word_cnt; + logic enough_data; + logic enough_data_q; + logic sent_available_data; + logic [BurstLength-1:0] next_host_addr; + logic host_last; + logic [16*NumPhys-1:0] converted_data; + logic converted_last; + logic converted_error; + logic converted_valid; + logic converted_ready; + + assign word_cnt = (PhyBeatsPerHost == 1) ? '0 : + byte_phy_cnt_q[($clog2(NumPhys) + 1) +: WordCntWidth]; + assign next_host_addr = ((byte_host_addr_q >> size_q) << size_q) + (1 << size_q); + assign enough_data = byte_phy_cnt_d >= next_host_addr; + assign enough_data_q = byte_phy_cnt_q >= next_host_addr; + assign sent_available_data = byte_host_addr_d >= byte_phy_cnt_q; + assign host_last = data_buffer_q.last && (last_addr_q == next_host_addr); + + assign data_o.data = data_buffer_q.data; + assign data_o.resp = data_buffer_q.resp; + assign data_o.last = host_last; + assign data_o.atomic_ok = 1'b0; + + always_comb begin : proc_counters + byte_host_addr_d = byte_host_addr_q; + byte_phy_cnt_d = byte_phy_cnt_q; + last_addr_d = last_addr_q; + size_d = size_q; + + if (start_i) begin + byte_host_addr_d[BurstLength-1:AddrWidth] = '0; + byte_host_addr_d[AddrWidth-1:0] = start_addr_i; + byte_phy_cnt_d[BurstLength-1:AddrWidth] = '0; + byte_phy_cnt_d[AddrWidth-1:0] = (start_addr_i >> NumPhys) << NumPhys; + last_addr_d = ((start_addr_i >> size_i) << size_i) + (burst_len_i << size_i); + size_d = size_i; + end + if (host_valid_o && host_ready_i) begin + byte_host_addr_d = ((byte_host_addr_q >> size_q) << size_q) + (1 << size_q); + end + if (converted_valid && converted_ready) begin + byte_phy_cnt_d = byte_phy_cnt_q + NumPhys * 2; + end + end + + always_comb begin : proc_sample + data_buffer_d = data_buffer_q; + + if (state_d == Idle) begin + data_buffer_d.last = 1'b0; + data_buffer_d.data = '0; + data_buffer_d.resp = hyperbus_pkg::HyperRespOkay; + end else begin + if (host_valid_o && host_ready_i) begin + data_buffer_d.resp = hyperbus_pkg::HyperRespOkay; + end + if (converted_ready && converted_valid) begin + data_buffer_d.data[word_cnt*(16*NumPhys) +: (16*NumPhys)] = converted_data; + if (converted_error) begin + data_buffer_d.resp = hyperbus_pkg::HyperRespAccessError; + end + data_buffer_d.last = converted_last; + end + end + end + + always_comb begin : proc_fsm + state_d = state_q; + host_valid_o = 1'b0; + converted_ready = 1'b0; + + unique case (state_q) + Idle: begin + if (start_i) begin + state_d = WaitData; + end + end + WaitData: begin + converted_ready = 1'b1; + if (converted_valid) begin + state_d = Sample; + end + end + Sample: begin + converted_ready = 1'b1; + if (enough_data) begin + state_d = CntReady; + end + end + CntReady: begin + host_valid_o = enough_data_q; + converted_ready = !enough_data_q; + if (host_valid_o && host_ready_i) begin + if (data_o.last || (last_addr_q == byte_host_addr_d)) begin + state_d = Idle; + end else if (sent_available_data) begin + converted_ready = 1'b1; + state_d = converted_valid ? Sample : WaitData; + end + end + end + default: begin + state_d = Idle; + end + endcase + end + + if (NumPhys == 2) begin : gen_dual_phy + logic [15:0] lower_data_d, lower_data_q; + logic lower_error_d, lower_error_q; + logic merge_d, merge_q; + + always_comb begin : proc_phy_width + converted_data = data_i; + converted_last = last_i; + converted_error = error_i; + converted_valid = phy_valid_i; + phy_ready_o = converted_ready; + lower_data_d = lower_data_q; + lower_error_d = lower_error_q; + merge_d = merge_q; + + if (!dual_phy_i) begin + converted_data = {data_i[15:0], lower_data_q}; + converted_last = last_i && merge_q; + converted_error = error_i || lower_error_q; + converted_valid = phy_valid_i && merge_q; + if (phy_valid_i && phy_ready_o) begin + merge_d = !merge_q; + if (!merge_q) begin + lower_data_d = data_i[15:0]; + lower_error_d = error_i; + end else begin + lower_error_d = 1'b0; + end + end + end + if (start_i) begin + merge_d = 1'b0; + lower_error_d = 1'b0; + end + end + + `FFARN(lower_data_q, lower_data_d, '0, clk_i, rst_ni) + `FFARN(lower_error_q, lower_error_d, 1'b0, clk_i, rst_ni) + `FFARN(merge_q, merge_d, 1'b0, clk_i, rst_ni) + end else begin : gen_single_phy + always_comb begin : proc_phy_width + converted_data = data_i; + converted_last = last_i; + converted_error = error_i; + converted_valid = phy_valid_i; + phy_ready_o = converted_ready; + end + end + + `FFARN(state_q, state_d, Idle, clk_i, rst_ni) + `FFARN(data_buffer_q, data_buffer_d, '0, clk_i, rst_ni) + `FFARN(byte_host_addr_q, byte_host_addr_d, '0, clk_i, rst_ni) + `FFARN(byte_phy_cnt_q, byte_phy_cnt_d, '0, clk_i, rst_ni) + `FFARN(size_q, size_d, '0, clk_i, rst_ni) + `FFARN(last_addr_q, last_addr_d, '0, clk_i, rst_ni) +endmodule : hyperbus_read_adapter diff --git a/src/hyperbus_stub.sv b/src/hyperbus_stub.sv deleted file mode 100644 index 548e54d..0000000 --- a/src/hyperbus_stub.sv +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright 2023 ETH Zurich and University of Bologna. -// Solderpad Hardware License, Version 0.51, see LICENSE for details. -// SPDX-License-Identifier: SHL-0.51 -// -// Thomas Benz -// Paul Scheffler - -module hyperbus #( - parameter int unsigned NumChips = -1, - parameter int unsigned AxiDataWidth = -1, - parameter int unsigned AxiIdWidth = -1, - parameter type axi_req_t = logic, - parameter type axi_rsp_t = logic, - parameter type axi_rule_t = logic -) ( - input logic clk_phy_i, - input logic clk_sys_i, - input logic rst_ni, - input logic test_mode_i, - // AXI bus - input axi_req_t axi_req_i, - output axi_rsp_t axi_rsp_o, - // Reg bus - input reg_intf_pkg::req_a32_d32 reg_req_i, - output reg_intf_pkg::rsp_d32 reg_rsp_o, - // PHY interface - output logic [NumChips-1:0] hyper_cs_no, - output logic hyper_ck_o, - output logic hyper_ck_no, - output logic hyper_rwds_o, - input logic hyper_rwds_i, - output logic hyper_rwds_oe_o, - input logic [7:0] hyper_dq_i, - output logic [7:0] hyper_dq_o, - output logic hyper_dq_oe_o, - output logic hyper_reset_no, - // Debug interface - output logic debug_hyper_rwds_oe_o, - output logic debug_hyper_dq_oe_o, - output logic [3:0] debug_hyper_phy_state_o -); - - assign axi_rsp_o = '0; - assign reg_rsp_o = '0; - assign hyper_cs_no = '0; - assign hyper_ck_o = '0; - assign hyper_ck_no = '0; - assign hyper_rwds_o = '0; - assign hyper_rwds_oe_o = '0; - assign hyper_dq_o = '0; - assign hyper_dq_oe_o = '0; - assign hyper_reset_no = '0; - assign debug_hyper_rwds_oe_o = '0; - assign debug_hyper_dq_oe_o = '0; - assign debug_hyper_phy_state_o = '0; - -endmodule : hyperbus diff --git a/src/hyperbus_synchronous.sv b/src/hyperbus_synchronous.sv new file mode 100644 index 0000000..f6b48be --- /dev/null +++ b/src/hyperbus_synchronous.sv @@ -0,0 +1,205 @@ +// Copyright 2026 ETH Zurich and University of Bologna. +// Solderpad Hardware License, Version 0.51, see LICENSE for details. +// SPDX-License-Identifier: SHL-0.51 + +`include "hyperbus/typedef.svh" +`include "common_cells/assertions.svh" + +module hyperbus_synchronous #( + parameter int unsigned NumChips = -1, + parameter int unsigned NumPhys = 2, + parameter int unsigned AxiAddrWidth = -1, + parameter int unsigned AxiDataWidth = -1, + parameter int unsigned AxiIdWidth = -1, + parameter int unsigned AxiUserWidth = -1, + parameter type axi_req_t = logic, + parameter type axi_rsp_t = logic, + parameter int unsigned RegDataWidth = -1, + parameter type reg_req_t = logic, + parameter type reg_rsp_t = logic, + parameter type axi_rule_t = logic, + parameter int unsigned PhyStartupCycles = 300 * 200, + parameter int unsigned SyncStages = 2 +) ( + input logic clk_sys_i, + input logic rst_sys_ni, +`ifdef TARGET_XILINX + input logic clk_ref200_i, +`endif + input logic test_mode_i, + + input axi_req_t axi_req_i, + output axi_rsp_t axi_rsp_o, + + input reg_req_t reg_req_i, + output reg_rsp_t reg_rsp_o, + + output logic [NumPhys-1:0][NumChips-1:0] hyper_cs_no, + output logic [NumPhys-1:0] hyper_ck_o, + output logic [NumPhys-1:0] hyper_ck_no, + output logic [NumPhys-1:0] hyper_rwds_o, + input logic [NumPhys-1:0] hyper_rwds_i, + output logic [NumPhys-1:0] hyper_rwds_oe_o, + input logic [NumPhys-1:0][7:0] hyper_dq_i, + output logic [NumPhys-1:0][7:0] hyper_dq_o, + output logic [NumPhys-1:0] hyper_dq_oe_o, + output logic [NumPhys-1:0] hyper_reset_no +); + + `ASSERT_INIT(AxiAddrWidthValid, AxiAddrWidth >= $clog2(AxiDataWidth / 8)) + + typedef logic [AxiAddrWidth-1:0] host_addr_t; + typedef logic [AxiDataWidth-1:0] host_data_t; + typedef logic [AxiDataWidth/8-1:0] host_strb_t; + `HYPERBUS_TYPEDEF_HOST_ALL_CT(host, host_addr_t, host_data_t, host_strb_t) + `HYPERBUS_TYPEDEF_LINK_ALL_CT(hyper, NumPhys, NumChips) + + logic clk_backend; + logic rst_backend_n; + hyperbus_pkg::phy_cfg_t frontend_cfg_apply; + logic frontend_cfg_apply_valid; + logic frontend_cfg_apply_ready; + logic frontend_cfg_apply_done; + logic frontend_clock_div_apply_valid; + logic frontend_drain; + logic host_idle; + hyperbus_pkg::phy_cfg_t backend_cfg_apply; + logic backend_cfg_apply_valid; + logic backend_cfg_apply_ready; + + hyperbus_pkg::frontend_cfg_t frontend_cfg; + axi_rule_t [NumChips-1:0] frontend_chip_rules; + + host_req_t host_req; + host_rsp_t host_rsp; + hyper_req_t midend_req; + hyper_rsp_t midend_rsp; + logic midend_trans_active; + logic midend_decode_error; + hyper_req_t backend_req; + hyper_rsp_t backend_rsp; + + assign clk_backend = clk_sys_i; + assign rst_backend_n = rst_sys_ni; + + // The synchronous top keeps the frontend/backend boundary visible without a bridge module. + assign backend_cfg_apply = frontend_cfg_apply; + assign backend_cfg_apply_valid = frontend_cfg_apply_valid; + assign frontend_cfg_apply_ready = backend_cfg_apply_ready; + assign frontend_cfg_apply_done = frontend_cfg_apply_valid && backend_cfg_apply_ready; + assign backend_req = midend_req; + assign midend_rsp = backend_rsp; + + hyperbus_cfg_frontend #( + .NumChips ( NumChips ), + .NumPhys ( NumPhys ), + .reg_req_t ( reg_req_t ), + .reg_rsp_t ( reg_rsp_t ), + .host_rule_t ( axi_rule_t ), + .RegDataWidth ( RegDataWidth ) + ) i_cfg_frontend ( + .clk_i ( clk_sys_i ), + .rst_ni ( rst_sys_ni ), + .reg_req_i ( reg_req_i ), + .reg_rsp_o ( reg_rsp_o ), + .drain_o ( frontend_drain ), + .host_idle_i ( host_idle ), + .trans_active_i ( midend_trans_active ), + .cfg_apply_o ( frontend_cfg_apply ), + .cfg_apply_valid_o ( frontend_cfg_apply_valid ), + .cfg_apply_ready_i ( frontend_cfg_apply_ready ), + .cfg_apply_done_i ( frontend_cfg_apply_done ), + .clock_div_apply_o ( ), + .clock_div_apply_valid_o ( frontend_clock_div_apply_valid ), + .clock_div_apply_ready_i ( 1'b1 ), + .clock_div_apply_done_i ( frontend_clock_div_apply_valid ), + .frontend_cfg_o ( frontend_cfg ), + .chip_rules_o ( frontend_chip_rules ), + .decode_error_i ( midend_decode_error ) + ); + + hyperbus_axi_frontend #( + .AxiDataWidth ( AxiDataWidth ), + .AxiAddrWidth ( AxiAddrWidth ), + .AxiIdWidth ( AxiIdWidth ), + .AxiUserWidth ( AxiUserWidth ), + .axi_req_t ( axi_req_t ), + .axi_rsp_t ( axi_rsp_t ), + .host_req_t ( host_req_t ), + .host_rsp_t ( host_rsp_t ) + ) i_axi_frontend ( + .clk_i ( clk_sys_i ), + .rst_ni ( rst_sys_ni ), + .drain_i ( frontend_drain ), + .idle_o ( host_idle ), + .axi_req_i ( axi_req_i ), + .axi_rsp_o ( axi_rsp_o ), + .host_req_o ( host_req ), + .host_rsp_i ( host_rsp ) + ); + + hyperbus_midend #( + .HostAddrWidth ( AxiAddrWidth ), + .HostDataWidth ( AxiDataWidth ), + .NumChips ( NumChips ), + .NumPhys ( NumPhys ), + .host_cmd_t ( host_cmd_t ), + .host_w_t ( host_w_t ), + .host_r_t ( host_r_t ), + .host_wrsp_t ( host_wrsp_t ), + .host_req_t ( host_req_t ), + .host_rsp_t ( host_rsp_t ), + .hyper_rx_t ( hyper_rx_t ), + .hyper_tx_t ( hyper_tx_t ), + .hyper_cmd_t ( hyper_cmd_t ), + .hyper_req_t ( hyper_req_t ), + .hyper_rsp_t ( hyper_rsp_t ), + .rule_t ( axi_rule_t ) + ) i_midend ( + .clk_i ( clk_sys_i ), + .rst_ni ( rst_sys_ni ), + .host_link_req_i ( host_req ), + .host_link_rsp_o ( host_rsp ), + .frontend_cfg_i ( frontend_cfg ), + .chip_rules_i ( frontend_chip_rules ), + .trans_active_o ( midend_trans_active ), + .decode_error_o ( midend_decode_error ), + .hyper_link_req_o ( midend_req ), + .hyper_link_rsp_i ( midend_rsp ) + ); + + hyperbus_backend #( + .NumChips ( NumChips ), + .NumPhys ( NumPhys ), + .StartupCycles ( PhyStartupCycles ), + .SyncStages ( SyncStages ), + .hyper_rx_t ( hyper_rx_t ), + .hyper_tx_t ( hyper_tx_t ), + .hyper_req_t ( hyper_req_t ), + .hyper_rsp_t ( hyper_rsp_t ) + ) i_backend ( + .clk_i ( clk_backend ), + .rst_ni ( rst_backend_n ), +`ifdef TARGET_XILINX + .clk_ref200_i ( clk_ref200_i ), +`endif + .test_mode_i ( test_mode_i ), + .cfg_apply_i ( backend_cfg_apply ), + .cfg_apply_valid_i ( backend_cfg_apply_valid ), + .cfg_apply_ready_o ( backend_cfg_apply_ready ), + .busy_o ( ), + .req_i ( backend_req ), + .rsp_o ( backend_rsp ), + .hyper_cs_no ( hyper_cs_no ), + .hyper_ck_o ( hyper_ck_o ), + .hyper_ck_no ( hyper_ck_no ), + .hyper_rwds_o ( hyper_rwds_o ), + .hyper_rwds_i ( hyper_rwds_i ), + .hyper_rwds_oe_o ( hyper_rwds_oe_o ), + .hyper_dq_i ( hyper_dq_i ), + .hyper_dq_o ( hyper_dq_o ), + .hyper_dq_oe_o ( hyper_dq_oe_o ), + .hyper_reset_no ( hyper_reset_no ) + ); + +endmodule diff --git a/src/hyperbus_synth_wrap.sv b/src/hyperbus_synth_wrap.sv deleted file mode 100644 index ed9ccc4..0000000 --- a/src/hyperbus_synth_wrap.sv +++ /dev/null @@ -1,232 +0,0 @@ -// Copyright 2023 ETH Zurich and University of Bologna. -// Solderpad Hardware License, Version 0.51, see LICENSE for details. -// SPDX-License-Identifier: SHL-0.51 -// -// Thomas Benz -// Paul Scheffler - -`include "axi/assign.svh" -`include "axi/typedef.svh" -`include "register_interface/typedef.svh" - -module hyperbus_lint_wrap #( - // HyperBus parameters - parameter int unsigned NumChips = 2, - parameter int unsigned NumPhys = 2, - parameter int unsigned IsClockODelayed = 0, - // AXI parameters - parameter int unsigned AxiIdWidth = 6, - parameter int unsigned AxiAddrWidth = 48, - parameter int unsigned AxiDataWidth = 128, - parameter int unsigned AxiUserWidth = 1, - // Regbus parameters - parameter int unsigned RegAddrWidth = 32, - parameter int unsigned RegDataWidth = 32, - // Dependent parameters; do not override! - parameter type axi_addr_t = logic [AxiAddrWidth-1:0], - parameter type axi_data_t = logic [AxiDataWidth-1:0], - parameter type axi_strb_t = logic [AxiDataWidth/8-1:0], - parameter type axi_id_t = logic [AxiIdWidth-1:0], - parameter type axi_user_t = logic [AxiUserWidth-1:0], - parameter type reg_addr_t = logic [RegAddrWidth-1:0], - parameter type reg_data_t = logic [RegDataWidth-1:0], - parameter type reg_strb_t = logic [RegDataWidth/8-1:0] -) ( - // SoC - input logic clk_phy_i, - input logic rst_phy_ni, - input logic clk_sys_i, - input logic rst_sys_ni, - input logic test_mode_i, - - // AXI bus - input axi_id_t axi_aw_id_i, - input axi_addr_t axi_aw_addr_i, - input axi_pkg::len_t axi_aw_len_i, - input axi_pkg::size_t axi_aw_size_i, - input axi_pkg::burst_t axi_aw_burst_i, - input logic axi_aw_lock_i, - input axi_pkg::cache_t axi_aw_cache_i, - input axi_pkg::prot_t axi_aw_prot_i, - input axi_pkg::qos_t axi_aw_qos_i, - input axi_pkg::region_t axi_aw_region_i, - input axi_pkg::atop_t axi_aw_atop_i, - input axi_user_t axi_aw_user_i, - input logic axi_aw_valid_i, - output logic axi_aw_ready_o, - input axi_data_t axi_w_data_i, - input axi_strb_t axi_w_strb_i, - input logic axi_w_last_i, - input axi_user_t axi_w_user_i, - input logic axi_w_valid_i, - output logic axi_w_ready_o, - output axi_id_t axi_b_id_o, - output axi_pkg::resp_t axi_b_resp_o, - output axi_user_t axi_b_user_o, - output logic axi_b_valid_o, - input logic axi_b_ready_i, - input axi_id_t axi_ar_id_i, - input axi_addr_t axi_ar_addr_i, - input axi_pkg::len_t axi_ar_len_i, - input axi_pkg::size_t axi_ar_size_i, - input axi_pkg::burst_t axi_ar_burst_i, - input logic axi_ar_lock_i, - input axi_pkg::cache_t axi_ar_cache_i, - input axi_pkg::prot_t axi_ar_prot_i, - input axi_pkg::qos_t axi_ar_qos_i, - input axi_pkg::region_t axi_ar_region_i, - input axi_user_t axi_ar_user_i, - input logic axi_ar_valid_i, - output logic axi_ar_ready_o, - output axi_id_t axi_r_id_o, - output axi_data_t axi_r_data_o, - output axi_pkg::resp_t axi_r_resp_o, - output logic axi_r_last_o, - output axi_user_t axi_r_user_o, - output logic axi_r_valid_o, - input logic axi_r_ready_i, - - // Reg bus - input reg_addr_t rbus_req_addr_i, - input logic rbus_req_write_i, - input reg_data_t rbus_req_wdata_i, - input reg_strb_t rbus_req_wstrb_i, - input logic rbus_req_valid_i, - output reg_data_t rbus_rsp_rdata_o, - output logic rbus_rsp_ready_o, - output logic rbus_rsp_error_o, - - // PHY interface - output logic [NumPhys-1:0][NumChips-1:0] hyper_cs_no, - output logic [NumPhys-1:0] hyper_ck_o, - output logic [NumPhys-1:0] hyper_ck_no, - output logic [NumPhys-1:0] hyper_rwds_o, - input logic [NumPhys-1:0] hyper_rwds_i, - output logic [NumPhys-1:0] hyper_rwds_oe_o, - input logic [NumPhys-1:0][7:0] hyper_dq_i, - output logic [NumPhys-1:0][7:0] hyper_dq_o, - output logic [NumPhys-1:0] hyper_dq_oe_o, - output logic [NumPhys-1:0] hyper_reset_no -); - - // Types - `AXI_TYPEDEF_AW_CHAN_T(aw_chan_t, axi_addr_t, axi_id_t, axi_user_t) - `AXI_TYPEDEF_W_CHAN_T(w_chan_t, axi_data_t, axi_strb_t, axi_user_t) - `AXI_TYPEDEF_B_CHAN_T(b_chan_t, axi_id_t, axi_user_t) - `AXI_TYPEDEF_AR_CHAN_T(ar_chan_t, axi_addr_t, axi_id_t, axi_user_t) - `AXI_TYPEDEF_R_CHAN_T(r_chan_t, axi_data_t, axi_id_t, axi_user_t) - `AXI_TYPEDEF_REQ_T(axi_req_t, aw_chan_t, w_chan_t, ar_chan_t) - `AXI_TYPEDEF_RESP_T(axi_rsp_t, b_chan_t, r_chan_t) - - axi_req_t axi_req; - axi_rsp_t axi_rsp; - - `REG_BUS_TYPEDEF_REQ(reg_req_t, reg_addr_t, reg_data_t, reg_strb_t) - `REG_BUS_TYPEDEF_RSP(reg_rsp_t, reg_data_t) - - reg_req_t reg_req; - reg_rsp_t reg_rsp; - - typedef axi_pkg::xbar_rule_32_t axi_rule_t; - - // Wrapped instance - hyperbus #( - .NumChips ( NumChips ), - .NumPhys ( NumPhys ), - .IsClockODelayed ( IsClockODelayed ), - .AxiAddrWidth ( AxiAddrWidth ), - .AxiDataWidth ( AxiDataWidth ), - .AxiIdWidth ( AxiIdWidth ), - .AxiUserWidth ( AxiUserWidth ), - .axi_req_t ( axi_req_t ), - .axi_rsp_t ( axi_rsp_t ), - .axi_w_chan_t ( w_chan_t ), - .axi_b_chan_t ( b_chan_t ), - .axi_ar_chan_t ( ar_chan_t ), - .axi_r_chan_t ( r_chan_t ), - .axi_aw_chan_t ( aw_chan_t ), - .RegDataWidth ( RegDataWidth ), - .RegAddrWidth ( RegAddrWidth ), - .reg_req_t ( reg_req_t ), - .reg_rsp_t ( reg_rsp_t ), - .axi_rule_t ( axi_rule_t ) - ) i_hyperbus ( - .clk_phy_i ( clk_phy_i ), - .rst_phy_ni ( rst_phy_ni ), - .clk_sys_i ( clk_sys_i ), - .rst_sys_ni ( rst_sys_ni ), - .test_mode_i ( test_mode_i ), - .axi_req_i ( axi_req ), - .axi_rsp_o ( axi_rsp ), - .reg_req_i ( reg_req ), - .reg_rsp_o ( reg_rsp ), - .hyper_cs_no ( hyper_cs_no ), - .hyper_ck_o ( hyper_ck_o ), - .hyper_ck_no ( hyper_ck_no ), - .hyper_rwds_o ( hyper_rwds_o ), - .hyper_rwds_i ( hyper_rwds_i ), - .hyper_rwds_oe_o ( hyper_rwds_oe_o ), - .hyper_dq_i ( hyper_dq_i ), - .hyper_dq_o ( hyper_dq_o ), - .hyper_dq_oe_o ( hyper_dq_oe_o ), - .hyper_reset_no ( hyper_reset_no ) - ); - - // AXI Slave - assign axi_req.aw.id = axi_aw_id_i; - assign axi_req.aw.addr = axi_aw_addr_i; - assign axi_req.aw.len = axi_aw_len_i; - assign axi_req.aw.size = axi_aw_size_i; - assign axi_req.aw.burst = axi_aw_burst_i; - assign axi_req.aw.lock = axi_aw_lock_i; - assign axi_req.aw.cache = axi_aw_cache_i; - assign axi_req.aw.prot = axi_aw_prot_i; - assign axi_req.aw.qos = axi_aw_qos_i; - assign axi_req.aw.region = axi_aw_region_i; - assign axi_req.aw.atop = axi_aw_atop_i; - assign axi_req.aw.user = axi_aw_user_i; - assign axi_req.aw_valid = axi_aw_valid_i; - assign axi_aw_ready_o = axi_rsp.aw_ready; - assign axi_req.w.data = axi_w_data_i; - assign axi_req.w.strb = axi_w_strb_i; - assign axi_req.w.last = axi_w_last_i; - assign axi_req.w.user = axi_w_user_i; - assign axi_req.w_valid = axi_w_valid_i; - assign axi_w_ready_o = axi_rsp.w_ready; - assign axi_b_id_o = axi_rsp.b.id; - assign axi_b_resp_o = axi_rsp.b.resp; - assign axi_b_user_o = axi_rsp.b.user; - assign axi_b_valid_o = axi_rsp.b_valid; - assign axi_req.b_ready = axi_b_ready_i; - assign axi_req.ar.id = axi_ar_id_i; - assign axi_req.ar.addr = axi_ar_addr_i; - assign axi_req.ar.len = axi_ar_len_i; - assign axi_req.ar.size = axi_ar_size_i; - assign axi_req.ar.burst = axi_ar_burst_i; - assign axi_req.ar.lock = axi_ar_lock_i; - assign axi_req.ar.cache = axi_ar_cache_i; - assign axi_req.ar.prot = axi_ar_prot_i; - assign axi_req.ar.qos = axi_ar_qos_i; - assign axi_req.ar.region = axi_ar_region_i; - assign axi_req.ar.user = axi_ar_user_i; - assign axi_req.ar_valid = axi_ar_valid_i; - assign axi_ar_ready_o = axi_rsp.ar_ready; - assign axi_r_id_o = axi_rsp.r.id; - assign axi_r_data_o = axi_rsp.r.data; - assign axi_r_resp_o = axi_rsp.r.resp; - assign axi_r_last_o = axi_rsp.r.last; - assign axi_r_user_o = axi_rsp.r.user; - assign axi_r_valid_o = axi_rsp.r_valid; - assign axi_req.r_ready = axi_r_ready_i; - - // Regbus slave - assign reg_req.addr = rbus_req_addr_i; - assign reg_req.write = rbus_req_write_i; - assign reg_req.wdata = rbus_req_wdata_i; - assign reg_req.wstrb = rbus_req_wstrb_i; - assign reg_req.valid = rbus_req_valid_i; - assign rbus_rsp_rdata_o = reg_rsp.rdata; - assign rbus_rsp_ready_o = reg_rsp.ready; - assign rbus_rsp_error_o = reg_rsp.error; - -endmodule : hyperbus_lint_wrap diff --git a/src/hyperbus_w2phy.sv b/src/hyperbus_w2phy.sv deleted file mode 100644 index 78a04fa..0000000 --- a/src/hyperbus_w2phy.sv +++ /dev/null @@ -1,214 +0,0 @@ -// Copyright 2023 ETH Zurich and University of Bologna. -// Solderpad Hardware License, Version 0.51, see LICENSE for details. -// SPDX-License-Identifier: SHL-0.51 -// -// Thomas Benz -// Paul Scheffler -// Luca Valente - -module hyperbus_w2phy #( - parameter int unsigned AxiDataWidth = -1, - parameter int unsigned NumPhys = -1, - parameter int unsigned BurstLength = -1, - parameter type T = logic, - parameter int unsigned AddrWidth = $clog2(AxiDataWidth/8) -) ( - input logic clk_i, - input logic rst_ni, - input logic [2:0] size, - input logic [AddrWidth-1:0] start_addr, - input logic [BurstLength-1:0] len, - input logic is_a_write, - input logic trans_handshake, - input logic axi_valid_i, - output logic axi_ready_o, - input T data_i, - output logic phy_valid_o, - input logic phy_ready_i, - output logic [16*NumPhys-1:0] data_o, - output logic last_o, - output logic [2*NumPhys-1:0] strb_o -); - - localparam int unsigned NumAxiBytes = AxiDataWidth/8; - localparam int unsigned NumPhyBytes = NumPhys*2; - localparam int unsigned AxiBytesInPhyBeat = NumAxiBytes/NumPhyBytes; - localparam int unsigned WordCntWidth = (AxiBytesInPhyBeat==1) ? 1 : $clog2(AxiBytesInPhyBeat); - // Cutting the combinatorial path between AXI master and cdc fifo - typedef enum logic [2:0] { - Idle, - Sample, - CntReady - } hyper_upsizer_state_t; - - hyper_upsizer_state_t state_d, state_q; - - typedef struct packed { - logic [AxiDataWidth/8-1:0] strb; - logic [AxiDataWidth-1:0] data; - logic last; - } w2phy_chan_t; - - w2phy_chan_t data_buffer_d, data_buffer_q; - - logic is_16_bw, is_8_bw; - logic upsize; - logic enough_data; - logic first_tx_d, first_tx_q; - - - logic [NumPhys*2-1:0] mask_strobe_d, mask_strobe_q; - logic [WordCntWidth-1:0] word_cnt; - logic [AddrWidth-1:0] byte_idx_d, byte_idx_q; - logic [3:0] size_d, size_q; - logic [AddrWidth-1:0] cnt_data_phy_d, cnt_data_phy_q; - logic keep_sampling, keep_sending; - logic upsize_q; - - assign is_8_bw = (size_d == 0); - assign is_16_bw = (size_d == 1) ; - assign upsize = (is_16_bw && (NumPhys==2)) | is_8_bw ; - assign upsize_q = ( (size_q==1) && (NumPhys==2)) | (size_q==0) ; - assign enough_data = !upsize; - assign keep_sampling = (size_d<($clog2(NumPhys)+1)) && (byte_idx_d[NumPhys-1:0]!='0); - assign keep_sending = (size_d>($clog2(NumPhys)+1)) && (cnt_data_phy_d != byte_idx_q); - assign word_cnt = cnt_data_phy_q>>($clog2(NumPhys)+1); - - - assign data_o = data_buffer_q.data[(16*NumPhys)*word_cnt +:(16*NumPhys)]; - assign strb_o = data_buffer_q.strb[ (2*NumPhys)*word_cnt +: (2*NumPhys)] & mask_strobe_q; - assign last_o = data_buffer_q.last && (!keep_sending || upsize_q); - - always_comb begin : counter - byte_idx_d = byte_idx_q; - size_d = size_q; - cnt_data_phy_d = cnt_data_phy_q; - first_tx_d = first_tx_q; - if (trans_handshake & is_a_write) begin - byte_idx_d = start_addr; - size_d = size; - cnt_data_phy_d = (start_addr>>NumPhys)<>size_d)<< size_d) + (1<=NumPhys) begin - if (cnt_data_phy_d != byte_idx_q) begin - state_d = CntReady; - end else if (axi_valid_i) begin - axi_ready_o = 1'b1; - state_d = enough_data ? CntReady : Sample; - end else begin - state_d = Sample; - end - end else if (size_d +// Paul Scheffler +// Luca Valente + +`include "common_cells/registers.svh" + +module hyperbus_write_adapter #( + parameter int unsigned HostDataWidth = -1, + parameter int unsigned NumPhys = -1, + parameter type T = logic, + parameter int unsigned AddrWidth = $clog2(HostDataWidth / 8) +) ( + input logic clk_i, + input logic rst_ni, + input logic [2:0] size_i, + input logic [AddrWidth-1:0] start_addr_i, + input logic start_i, + input logic dual_phy_i, + input logic host_valid_i, + output logic host_ready_o, + input T data_i, + output logic phy_valid_o, + input logic phy_ready_i, + output logic [16*NumPhys-1:0] data_o, + output logic last_o, + output logic [2*NumPhys-1:0] strb_o +); + + localparam int unsigned NumHostBytes = HostDataWidth / 8; + localparam int unsigned NumPhyBytes = NumPhys * 2; + localparam int unsigned PhyBeatsPerHost = NumHostBytes / NumPhyBytes; + localparam int unsigned WordCntWidth = + (PhyBeatsPerHost == 1) ? 1 : $clog2(PhyBeatsPerHost); + + typedef enum logic [2:0] { + Idle, + Sample, + CntReady + } write_adapter_state_e; + + typedef struct packed { + logic [HostDataWidth/8-1:0] strb; + logic [HostDataWidth-1:0] data; + logic last; + } write_buffer_t; + + write_adapter_state_e state_d, state_q; + write_buffer_t data_buffer_d, data_buffer_q; + logic upsize; + logic enough_data; + logic first_tx_d, first_tx_q; + logic [NumPhys*2-1:0] mask_strobe_d, mask_strobe_q; + logic [WordCntWidth-1:0] word_cnt; + logic [AddrWidth-1:0] byte_idx_d, byte_idx_q; + logic [3:0] size_d, size_q; + logic [AddrWidth-1:0] cnt_data_phy_d, cnt_data_phy_q; + logic keep_sending; + logic [16*NumPhys-1:0] converted_data; + logic [2*NumPhys-1:0] converted_strb; + logic converted_last; + logic converted_valid; + logic converted_ready; + + assign upsize = ((size_q == 1) && (NumPhys == 2)) || (size_q == 0); + assign enough_data = !upsize; + assign keep_sending = (size_d > ($clog2(NumPhys) + 1)) && + (cnt_data_phy_d != byte_idx_q); + assign word_cnt = cnt_data_phy_q >> ($clog2(NumPhys) + 1); + + assign converted_data = data_buffer_q.data[(16*NumPhys)*word_cnt +: (16*NumPhys)]; + assign converted_strb = data_buffer_q.strb[(2*NumPhys)*word_cnt +: (2*NumPhys)] & + mask_strobe_q; + assign converted_last = data_buffer_q.last && (!keep_sending || upsize); + + always_comb begin : proc_counters + byte_idx_d = byte_idx_q; + size_d = size_q; + cnt_data_phy_d = cnt_data_phy_q; + first_tx_d = first_tx_q; + + if (start_i) begin + byte_idx_d = start_addr_i; + size_d = size_i; + cnt_data_phy_d = (start_addr_i >> NumPhys) << NumPhys; + first_tx_d = 1'b1; + end + if (host_valid_i && host_ready_o) begin + byte_idx_d = ((byte_idx_q >> size_d) << size_d) + (1 << size_d); + first_tx_d = 1'b0; + end + if (converted_valid && converted_ready) begin + cnt_data_phy_d = cnt_data_phy_q + NumPhys * 2; + end + end + + always_comb begin : proc_sample + data_buffer_d = data_buffer_q; + + if (state_d == Idle) begin + data_buffer_d.last = 1'b0; + data_buffer_d.data = '0; + end else if (host_ready_o && host_valid_i) begin + if (!upsize) begin + // A full host beat remains buffered until all PHY words are emitted. + data_buffer_d.data = data_i.data; + data_buffer_d.strb = data_i.strb; + data_buffer_d.last = data_i.last; + if (first_tx_q) begin + for (int unsigned i = 0; i < byte_idx_q; i++) begin + data_buffer_d.strb[i] = 1'b0; + end + end + end else begin + data_buffer_d.strb[byte_idx_q +: (2*NumPhys)] = + data_i.strb[byte_idx_q +: (2*NumPhys)]; + data_buffer_d.data[byte_idx_q*8 +: (8*NumPhys)] = + data_i.data[byte_idx_q*8 +: (8*NumPhys)]; + data_buffer_d.last = data_i.last; + if (first_tx_q) begin + for (int unsigned i = 0; i < byte_idx_q; i++) begin + data_buffer_d.strb[i] = 1'b0; + end + end + end + end + end + + always_comb begin : proc_fsm + state_d = state_q; + mask_strobe_d = mask_strobe_q; + host_ready_o = 1'b0; + converted_valid = 1'b0; + + unique case (state_q) + Idle: begin + mask_strobe_d = '1; + if (start_i) begin + state_d = Sample; + end + end + Sample: begin + host_ready_o = 1'b1; + if (host_valid_i && enough_data) begin + state_d = CntReady; + end else if (host_valid_i) begin + if (byte_idx_d[NumPhys-1:0] != '0) begin + if (data_i.last) begin + state_d = CntReady; + for (int unsigned i = 0; i < NumPhys * 2; i++) begin + mask_strobe_d[i] = i < byte_idx_d[NumPhys-1:0]; + end + end + end else begin + state_d = CntReady; + end + end + end + CntReady: begin + converted_valid = 1'b1; + if (converted_ready) begin + if (converted_last) begin + state_d = start_i ? Sample : Idle; + end else if (size_d >= NumPhys) begin + if (cnt_data_phy_d != byte_idx_q) begin + state_d = CntReady; + end else if (host_valid_i) begin + host_ready_o = 1'b1; + state_d = enough_data ? CntReady : Sample; + end else begin + state_d = Sample; + end + end else if (cnt_data_phy_d[NumPhys-1:0] == '0) begin + host_ready_o = !upsize; + state_d = Sample; + end + end + end + default: begin + state_d = Idle; + end + endcase + end + + if (NumPhys == 2) begin : gen_dual_phy + logic split_d, split_q; + + always_comb begin : proc_phy_width + data_o = converted_data; + strb_o = converted_strb; + last_o = converted_last; + phy_valid_o = converted_valid; + converted_ready = phy_ready_i; + split_d = split_q; + + if (!dual_phy_i) begin + data_o = {converted_data[15:0], converted_data[15:0]}; + strb_o = {converted_strb[1:0], converted_strb[1:0]}; + last_o = converted_last && split_q; + converted_ready = phy_ready_i && split_q; + if (split_q) begin + data_o = {converted_data[31:16], converted_data[31:16]}; + strb_o = {converted_strb[3:2], converted_strb[3:2]}; + end + if (phy_valid_o && phy_ready_i) begin + split_d = !split_q; + end + end + if (start_i) begin + split_d = 1'b0; + end + end + + `FFARN(split_q, split_d, 1'b0, clk_i, rst_ni) + end else begin : gen_single_phy + always_comb begin : proc_phy_width + data_o = converted_data; + strb_o = converted_strb; + last_o = converted_last; + phy_valid_o = converted_valid; + converted_ready = phy_ready_i; + end + end + + `FFARN(state_q, state_d, Idle, clk_i, rst_ni) + `FFARN(data_buffer_q, data_buffer_d, '0, clk_i, rst_ni) + `FFARN(byte_idx_q, byte_idx_d, '0, clk_i, rst_ni) + `FFARN(size_q, size_d, '0, clk_i, rst_ni) + `FFARN(cnt_data_phy_q, cnt_data_phy_d, '0, clk_i, rst_ni) + `FFARN(first_tx_q, first_tx_d, 1'b0, clk_i, rst_ni) + `FFARN(mask_strobe_q, mask_strobe_d, '0, clk_i, rst_ni) +endmodule : hyperbus_write_adapter diff --git a/test/axi_hyper_tb.sv b/test/axi_hyper_tb.sv index fd1d57f..6ee1c03 100644 --- a/test/axi_hyper_tb.sv +++ b/test/axi_hyper_tb.sv @@ -23,10 +23,28 @@ module axi_hyper_tb parameter int unsigned TbNumReads = 32'd1000, /// Cycle time for the TB clock generator parameter time TbCyclTime = 5ns, + /// Cycle time for the optional asynchronous PHY clock + parameter time TbPhyCyclTime = 6ns, /// Application time to the DUT parameter time TbApplTime = 1ns, /// Test time of the DUT - parameter time TbTestTime = 4ns + parameter time TbTestTime = 4ns, + /// DUT variant: 0 is isochronous, 1 is synchronous, 2 is asynchronous + parameter int unsigned TbDutVariant = 0, + /// RX delay-line tap value. + parameter int unsigned TbRxDelayLineTaps = 16, + /// TX delay-line tap value. + parameter int unsigned TbTxDelayLineTaps = 19, + /// Number of AXI beats in the directed slow read/write stress transactions. + parameter int unsigned TbSlowNumBeats = 64, + /// Idle cycles inserted between each accepted AXI beat in the slow stress transactions. + parameter int unsigned TbSlowGapCycles = 64, + /// Temporary t_burst_max used to force HyperBus segment restarts in the slow stress phase. + parameter int unsigned TbSlowBurstMax = 16, + /// Maximum system-clock cycles before a stalled regression is terminated. + parameter longint unsigned TbTimeoutCycles = 5_000_000, + /// Annotate the HyperRAM timing SDF. Disable for fast RTL regressions. + parameter bit TbAnnotateSdf = 1'b1 ); import hyperbus_tb_pkg::*; ///////////////////////////// @@ -54,12 +72,30 @@ module axi_hyper_tb typedef axi_pkg::xbar_rule_32_t rule_t; localparam int unsigned RegBusDW = 32; - localparam int unsigned RegBusAW = 8; + // The stable register map is a hierarchical 4 KiB window. + localparam int unsigned RegBusAW = 12; localparam int unsigned TbDramDataWidth = 8; localparam int unsigned TbDramLenWidth = 32'h80000; logic end_of_sim; + logic [31:0] segment_start_count; + logic [63:0] cycle_count; + + sim_timeout #( + .Cycles ( TbTimeoutCycles ) + ) i_sim_timeout ( + .clk_i ( clk ), + .rst_ni ( rst_n ) + ); + + always_ff @(posedge clk or negedge rst_n) begin + if (!rst_n) begin + cycle_count <= '0; + end else begin + cycle_count <= cycle_count + 1'b1; + end + end /////////////////////// @@ -235,10 +271,6 @@ module axi_hyper_tb // Address Ranges // //////////////////// localparam axi_addr_t MemRegionStart = axi_addr_t'(32'h8000_0000); - localparam logic [31:0] CfgFrontendAddr = 32'h0000_0100; - localparam logic [31:0] CfgChip0AddressAddr = 32'h0000_0408; - localparam logic [31:0] CfgChip0Memory = 32'h0001_1900; - localparam logic [31:0] CfgChip0Register = 32'h0001_1901; localparam axi_addr_t MemRegionLength = axi_addr_t'(TbDramDataWidth * TbDramLenWidth); logic s_error; @@ -263,6 +295,17 @@ module axi_hyper_tb return data << (8 * addr[$clog2(TbAxiDataWidthFull/8)-1:0]); endfunction + function automatic logic [TbAxiDataWidthFull-1:0] slow_stress_data( + input axi_addr_t addr, + input int unsigned beat + ); + automatic logic [TbAxiDataWidthFull-1:0] data = '0; + for (int unsigned byte_idx = 0; byte_idx < TbAxiDataWidthFull/8; byte_idx++) begin + data[8*byte_idx +: 8] = 8'(addr[7:0] + beat + (byte_idx * 17)); + end + return data; + endfunction + task automatic axi_write_subword( input axi_ctrl_master_t axi_drv, input axi_addr_t addr, @@ -356,6 +399,463 @@ module axi_hyper_tb end endtask + task automatic check_unaligned_word_access(input axi_ctrl_master_t axi_drv); + localparam axi_addr_t BaseAddr = axi_addr_t'(32'h8000_0180); + localparam logic [63:0] InitialData = 64'h8877_6655_4433_2211; + localparam logic [23:0] UpdatedData = 24'hc3_b2_a1; + axi_ctrl_master_t::ax_beat_t ax = new(); + axi_ctrl_master_t::w_beat_t w = new(); + axi_ctrl_master_t::b_beat_t b; + axi_ctrl_master_t::r_beat_t r; + + axi_write_subword(axi_drv, BaseAddr, InitialData, 3); + + ax.ax_addr = BaseAddr + 1; + ax.ax_id = '0; + ax.ax_len = '0; + ax.ax_size = 2; + ax.ax_burst = axi_pkg::BURST_INCR; + axi_drv.send_aw(ax); + + w.w_data = 64'(UpdatedData) << 8; + w.w_strb = 8'b0000_1110; + w.w_last = 1'b1; + axi_drv.send_w(w); + axi_drv.recv_b(b); + if (b.b_resp != axi_pkg::RESP_OKAY) begin + $error("[AXI-UNALIGNED] Write returned response %0d", b.b_resp); + end + + axi_check_subword(axi_drv, BaseAddr, 64'h8877_6655_c3b2_a111, 3); + + axi_drv.send_ar(ax); + axi_drv.recv_r(r); + if ((r.r_resp != axi_pkg::RESP_OKAY) || !r.r_last || + (r.r_data[31:8] != UpdatedData)) begin + $error("[AXI-UNALIGNED] Read returned data=0x%016x last=%0b resp=%0d", + r.r_data, r.r_last, r.r_resp); + end + endtask + + task automatic axi_write_slow( + input axi_ctrl_master_t axi_drv, + input axi_addr_t addr, + input int unsigned num_beats, + input int unsigned gap_cycles + ); + axi_ctrl_master_t::ax_beat_t ax = new(); + axi_ctrl_master_t::w_beat_t w; + axi_ctrl_master_t::b_beat_t b; + + if (num_beats == 0 || num_beats > 256) begin + $fatal(1, "Slow AXI write num_beats must be in [1, 256], got %0d", num_beats); + end + + ax.ax_addr = addr; + ax.ax_id = '0; + ax.ax_len = 8'(num_beats - 1); + ax.ax_size = $clog2(TbAxiDataWidthFull/8); + ax.ax_burst = axi_pkg::BURST_INCR; + axi_drv.send_aw(ax); + + for (int unsigned beat = 0; beat < num_beats; beat++) begin + repeat (gap_cycles) @(posedge clk); + w = new(); + w.w_data = slow_stress_data(addr, beat); + w.w_strb = '1; + w.w_last = beat == (num_beats - 1); + axi_drv.send_w(w); + end + + axi_drv.recv_b(b); + if (b.b_resp != axi_pkg::RESP_OKAY) begin + $error("[AXI-SLOW] Write to 0x%08x returned response %0d", addr, b.b_resp); + end + endtask + + task automatic axi_read_slow_check( + input axi_ctrl_master_t axi_drv, + input axi_addr_t addr, + input int unsigned num_beats, + input int unsigned gap_cycles + ); + axi_ctrl_master_t::ax_beat_t ax = new(); + axi_ctrl_master_t::r_beat_t r; + logic [TbAxiDataWidthFull-1:0] expected; + + if (num_beats == 0 || num_beats > 256) begin + $fatal(1, "Slow AXI read num_beats must be in [1, 256], got %0d", num_beats); + end + + ax.ax_addr = addr; + ax.ax_id = '0; + ax.ax_len = 8'(num_beats - 1); + ax.ax_size = $clog2(TbAxiDataWidthFull/8); + ax.ax_burst = axi_pkg::BURST_INCR; + axi_drv.send_ar(ax); + + for (int unsigned beat = 0; beat < num_beats; beat++) begin + repeat (gap_cycles) @(posedge clk); + axi_drv.recv_r(r); + expected = slow_stress_data(addr, beat); + if (r.r_resp != axi_pkg::RESP_OKAY || r.r_data != expected || + r.r_last != (beat == (num_beats - 1))) begin + $error("[AXI-SLOW] Read beat %0d from 0x%08x returned data=0x%016x last=%0b resp=%0d, expected data=0x%016x last=%0b", + beat, addr, r.r_data, r.r_last, r.r_resp, expected, beat == (num_beats - 1)); + end + end + endtask + + task automatic check_config_barrier( + input axi_ctrl_master_t axi_drv, + input reg_bus_master_t reg_drv + ); + localparam axi_addr_t BarrierAddr = axi_addr_t'(32'h8000_6000); + logic reg_error; + time write_done_time; + time cfg_done_time; + + write_done_time = 0; + cfg_done_time = 0; + $display("==========================="); + $display("= Config drain/apply ="); + $display("==========================="); + + fork + begin + axi_write_slow(axi_drv, BarrierAddr, 16, 16); + write_done_time = $time; + 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; + if (reg_error != 1'b0) begin + $error("[CFG-DRAIN] Configuration 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); + end + endtask + + task automatic check_decode_errors( + input axi_ctrl_master_t axi_drv, + input reg_bus_master_t reg_drv + ); + localparam axi_addr_t InvalidAddr = axi_addr_t'(32'h9000_0000); + axi_ctrl_master_t::ax_beat_t ax = new(); + axi_ctrl_master_t::w_beat_t w = new(); + axi_ctrl_master_t::b_beat_t b; + axi_ctrl_master_t::r_beat_t r; + logic [31:0] status; + logic reg_error; + + $display("==========================="); + $display("= Decode error handling ="); + $display("==========================="); + + ax.ax_addr = InvalidAddr; + ax.ax_id = '0; + ax.ax_len = 1; + ax.ax_size = 3; + ax.ax_burst = axi_pkg::BURST_INCR; + axi_drv.send_ar(ax); + for (int unsigned beat = 0; beat < 2; beat++) begin + axi_drv.recv_r(r); + if ((r.r_resp != axi_pkg::RESP_DECERR) || (r.r_data != '0) || + (r.r_last != (beat == 1))) begin + $error("[DECODE] Invalid read beat %0d returned data=0x%016x last=%0b resp=%0d", + beat, r.r_data, r.r_last, r.r_resp); + end + end + + ax.ax_len = 0; + axi_drv.send_aw(ax); + w.w_data = '0; + w.w_strb = '1; + w.w_last = 1'b1; + axi_drv.send_w(w); + axi_drv.recv_b(b); + if (b.b_resp != axi_pkg::RESP_DECERR) begin + $error("[DECODE] Invalid write returned response %0d", b.b_resp); + end + + reg_drv.send_read(32'h010, status, reg_error); + if ((reg_error != 1'b0) || !status[0]) begin + $error("[DECODE] Sticky decode-error status was not set"); + end + reg_drv.send_write(32'h010, 32'h1, '1, reg_error); + if (reg_error != 1'b0) $error("unexpected error"); + reg_drv.send_read(32'h010, status, reg_error); + if ((reg_error != 1'b0) || status[0]) begin + $error("[DECODE] Sticky decode-error status did not clear"); + end + endtask + + task automatic check_cross_chip_burst( + input axi_ctrl_master_t axi_drv, + input reg_bus_master_t reg_drv + ); + localparam axi_addr_t Boundary = axi_addr_t'(32'h8040_0000); + localparam axi_addr_t BurstAddr = Boundary - 16; + logic [31:0] segment_start_snapshot; + logic reg_error; + + if (NumChips < 2) begin + return; + end + + $display("==========================="); + $display("= Cross-chip burst ="); + $display("==========================="); + + reg_drv.send_write(32'h404, Boundary, '1, reg_error); + 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"); + + segment_start_snapshot = segment_start_count; + axi_write_slow(axi_drv, BurstAddr, 4, 0); + axi_read_slow_check(axi_drv, BurstAddr, 4, 0); + if ((segment_start_count - segment_start_snapshot) < 4) begin + $error("[CROSS-CHIP] Observed %0d segment starts, expected at least four", + segment_start_count - segment_start_snapshot); + end + + reg_drv.send_write(32'h440, 32'h8100_0000, '1, reg_error); + if (reg_error != 1'b0) $error("unexpected error"); + reg_drv.send_write(32'h404, 32'h8100_0000, '1, reg_error); + if (reg_error != 1'b0) $error("unexpected error"); + endtask + + task automatic check_large_rule_distance(input axi_ctrl_master_t axi_drv); + localparam axi_addr_t ReadAddr = axi_addr_t'(32'h802f_0002); + localparam int unsigned NumBeats = 121; + axi_ctrl_master_t::ax_beat_t ax = new(); + axi_ctrl_master_t::r_beat_t r; + + ax.ax_addr = ReadAddr; + ax.ax_id = '0; + ax.ax_len = NumBeats - 1; + ax.ax_size = 2; + ax.ax_burst = axi_pkg::BURST_INCR; + axi_drv.send_ar(ax); + for (int unsigned beat = 0; beat < NumBeats; beat++) begin + axi_drv.recv_r(r); + if ((r.r_resp != axi_pkg::RESP_OKAY) || (r.r_last != (beat == NumBeats - 1))) begin + $error("[AXI-RANGE] Beat %0d returned last=%0b resp=%0d", + beat, r.r_last, r.r_resp); + end + end + endtask + + task automatic check_range_edges( + input axi_ctrl_master_t axi_drv, + input reg_bus_master_t reg_drv + ); + localparam axi_addr_t ValidAddr = axi_addr_t'(32'h8100_0100); + localparam axi_addr_t OverflowAddr = axi_addr_t'(32'hffff_fff8); + localparam logic [63:0] TestData = 64'h0123_4567_89ab_cdef; + axi_ctrl_master_t::ax_beat_t ax = new(); + axi_ctrl_master_t::r_beat_t r; + logic reg_error; + + if (NumChips < 2) begin + return; + end + + $display("==========================="); + $display("= Address range edges ="); + $display("==========================="); + + // A zero bound extends the final rule through the end of the address space. + reg_drv.send_write(32'h444, '0, '1, reg_error); + if (reg_error != 1'b0) $error("unexpected error"); + axi_write_subword(axi_drv, ValidAddr, TestData, 3); + axi_check_subword(axi_drv, ValidAddr, TestData, 3); + + // The end address is exclusive; a burst extending beyond it must be rejected. + ax.ax_addr = OverflowAddr; + ax.ax_id = '0; + ax.ax_len = 1; + ax.ax_size = 3; + ax.ax_burst = axi_pkg::BURST_INCR; + axi_drv.send_ar(ax); + for (int unsigned beat = 0; beat < 2; beat++) begin + axi_drv.recv_r(r); + if ((r.r_resp != axi_pkg::RESP_DECERR) || + (r.r_last != (beat == 1))) begin + $error("[AXI-RANGE] Overflow beat %0d returned last=%0b resp=%0d", + beat, r.r_last, r.r_resp); + end + end + + reg_drv.send_write(32'h444, 32'h8200_0000, '1, reg_error); + if (reg_error != 1'b0) $error("unexpected error"); + endtask + + task automatic check_atomic_add(input axi_ctrl_master_t axi_drv); + localparam axi_addr_t AtomicAddr = axi_addr_t'(32'h8000_0200); + localparam logic [31:0] InitialValue = 32'h1234_5678; + localparam logic [31:0] Addend = 32'h0102_0304; + axi_ctrl_master_t::ax_beat_t ax = new(); + axi_ctrl_master_t::w_beat_t w = new(); + axi_ctrl_master_t::b_beat_t b; + axi_ctrl_master_t::r_beat_t r; + + $display("==========================="); + $display("= Atomic add ="); + $display("==========================="); + + axi_write_subword(axi_drv, AtomicAddr, InitialValue, 2); + + ax.ax_addr = AtomicAddr; + ax.ax_id = '0; + ax.ax_len = '0; + ax.ax_size = 2; + ax.ax_burst = axi_pkg::BURST_INCR; + ax.ax_atop = {axi_pkg::ATOP_ATOMICLOAD, axi_pkg::ATOP_LITTLE_END, + axi_pkg::ATOP_ADD}; + axi_drv.send_aw(ax); + + w.w_data = subword_data(Addend, AtomicAddr); + w.w_strb = subword_strb(AtomicAddr, 2); + w.w_last = 1'b1; + axi_drv.send_w(w); + + fork + axi_drv.recv_b(b); + axi_drv.recv_r(r); + join + + if ((b.b_resp != axi_pkg::RESP_OKAY) || (r.r_resp != axi_pkg::RESP_OKAY) || + (r.r_data[31:0] != InitialValue) || !r.r_last) begin + $error("[ATOMIC] Add returned old=0x%08x rresp=%0d bresp=%0d last=%0b", + r.r_data[31:0], r.r_resp, b.b_resp, r.r_last); + end + axi_check_subword(axi_drv, AtomicAddr, InitialValue + Addend, 2); + + // Big-endian arithmetic is rejected explicitly and must still drain W. + ax.ax_atop = {axi_pkg::ATOP_ATOMICLOAD, axi_pkg::ATOP_BIG_END, + axi_pkg::ATOP_ADD}; + axi_drv.send_aw(ax); + axi_drv.send_w(w); + fork + axi_drv.recv_b(b); + axi_drv.recv_r(r); + join + if ((b.b_resp != axi_pkg::RESP_SLVERR) || (r.r_resp != axi_pkg::RESP_SLVERR)) begin + $error("[ATOMIC] Unsupported operation returned rresp=%0d bresp=%0d", + r.r_resp, b.b_resp); + end + axi_check_subword(axi_drv, AtomicAddr, InitialValue + Addend, 2); + + // A malformed multi-beat atomic must drain all W beats before returning an error. + ax.ax_len = 1; + ax.ax_atop = {axi_pkg::ATOP_ATOMICLOAD, axi_pkg::ATOP_LITTLE_END, + axi_pkg::ATOP_ADD}; + axi_drv.send_aw(ax); + w.w_data = subword_data(32'hdead_beef, AtomicAddr); + w.w_strb = subword_strb(AtomicAddr, 2); + w.w_last = 1'b0; + axi_drv.send_w(w); + w.w_data = subword_data(32'hfeed_cafe, AtomicAddr); + w.w_last = 1'b1; + axi_drv.send_w(w); + fork + axi_drv.recv_b(b); + axi_drv.recv_r(r); + join + if ((b.b_resp != axi_pkg::RESP_SLVERR) || (r.r_resp != axi_pkg::RESP_SLVERR)) begin + $error("[ATOMIC] Multi-beat operation returned rresp=%0d bresp=%0d", + r.r_resp, b.b_resp); + end + axi_write_subword(axi_drv, AtomicAddr, 32'h89ab_cdef, 2); + axi_check_subword(axi_drv, AtomicAddr, 32'h89ab_cdef, 2); + endtask + + task automatic check_atomic_range_errors( + input axi_ctrl_master_t axi_drv, + input reg_bus_master_t reg_drv + ); + localparam axi_addr_t InvalidAddr = axi_addr_t'(32'h9000_0000); + localparam axi_addr_t ZeroEndAddr = axi_addr_t'(32'h8100_0400); + localparam int unsigned AtomicSize = NumPhys + 1; + localparam logic [31:0] ZeroEndInitial = 32'h1020_3040; + localparam logic [31:0] ZeroEndAddend = 32'h0101_0101; + axi_ctrl_master_t::ax_beat_t ax = new(); + axi_ctrl_master_t::w_beat_t w = new(); + axi_ctrl_master_t::b_beat_t b; + axi_ctrl_master_t::r_beat_t r; + logic [31:0] segment_start_snapshot; + logic reg_error; + + if (NumChips < 2) begin + return; + end + + $display("==========================="); + $display("= Atomic range errors ="); + $display("==========================="); + + ax.ax_addr = InvalidAddr; + ax.ax_id = '0; + ax.ax_len = '0; + ax.ax_size = AtomicSize; + ax.ax_burst = axi_pkg::BURST_INCR; + ax.ax_atop = {axi_pkg::ATOP_ATOMICLOAD, axi_pkg::ATOP_LITTLE_END, + axi_pkg::ATOP_ADD}; + w.w_data = subword_data(64'hfedc_ba98_7654_3210, InvalidAddr); + w.w_strb = subword_strb(InvalidAddr, AtomicSize); + w.w_last = 1'b1; + + // Unmapped atomic loads still owe both the AXI R and B responses. + segment_start_snapshot = segment_start_count; + axi_drv.send_aw(ax); + axi_drv.send_w(w); + fork + axi_drv.recv_b(b); + axi_drv.recv_r(r); + join + if ((b.b_resp != axi_pkg::RESP_SLVERR) || (r.r_resp != axi_pkg::RESP_SLVERR) || + !r.r_last) begin + $error("[ATOMIC-RANGE] Unmapped operation returned rlast=%0b rresp=%0d bresp=%0d", + r.r_last, r.r_resp, b.b_resp); + end + if (segment_start_count != segment_start_snapshot) begin + $error("[ATOMIC-RANGE] Rejected unmapped operation issued a HyperBus command"); + end + + // 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"); + axi_write_subword(axi_drv, ZeroEndAddr, ZeroEndInitial, 2); + ax.ax_addr = ZeroEndAddr; + ax.ax_size = 2; + w.w_data = subword_data(ZeroEndAddend, ZeroEndAddr); + w.w_strb = subword_strb(ZeroEndAddr, 2); + axi_drv.send_aw(ax); + axi_drv.send_w(w); + fork + axi_drv.recv_b(b); + axi_drv.recv_r(r); + join + if ((b.b_resp != axi_pkg::RESP_OKAY) || (r.r_resp != axi_pkg::RESP_OKAY) || + (r.r_data[31:0] != ZeroEndInitial) || !r.r_last) begin + $error("[ATOMIC-RANGE] Zero-ended rule returned old=0x%08x rresp=%0d bresp=%0d", + r.r_data[31:0], r.r_resp, b.b_resp); + end + axi_check_subword(axi_drv, ZeroEndAddr, ZeroEndInitial + ZeroEndAddend, 2); + reg_drv.send_write(32'h444, 32'h8200_0000, '1, reg_error); + if (reg_error != 1'b0) $error("unexpected error"); + endtask + initial begin : proc_sim_crtl automatic axi_scoreboard_mst_t mst_scoreboard = new( score_mst_intf_dv ); @@ -363,6 +863,9 @@ module axi_hyper_tb automatic reg_bus_master_t reg_master = new( reg_bus_mst ); automatic s27ks_cfg0_reg_t s27ks_cfg0 = hyperbus_tb_pkg::s27ks_cfg0_default; + automatic logic [63:0] divider_cycle_snapshot; + automatic logic [63:0] div2_write_cycles; + automatic logic [63:0] div4_write_cycles; // Reset the AXI drivers and scoreboards end_of_sim = 1'b0; @@ -379,19 +882,83 @@ module axi_hyper_tb @(posedge rst_n); mst_scoreboard.monitor(); - #600350ns; + // Map each chip to a distinct 16 MiB host-address window. + if (NumChips > 1) begin + reg_master.send_write(32'h444, 32'h8200_0000, '1, s_reg_error); + if (s_reg_error != 1'b0) $error("unexpected error"); + reg_master.send_write(32'h440, 32'h8100_0000, '1, s_reg_error); + if (s_reg_error != 1'b0) $error("unexpected error"); + end + reg_master.send_write(32'h404, 32'h8100_0000, '1, s_reg_error); + if (s_reg_error != 1'b0) $error("unexpected error"); + reg_master.send_write(32'h400, 32'h8000_0000, '1, s_reg_error); + if (s_reg_error != 1'b0) $error("unexpected error"); - // switch memory address space to register space - reg_master.send_write(CfgChip0AddressAddr, CfgChip0Register, '1, s_reg_error); + reg_master.send_write(32'h418, TbRxDelayLineTaps, '1, s_reg_error); + if (s_reg_error != 1'b0) $error("unexpected error"); + reg_master.send_write(32'h300, TbTxDelayLineTaps, '1, s_reg_error); if (s_reg_error != 1'b0) $error("unexpected error"); - // 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)); + 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"); + end - // switch back to memory address space - reg_master.send_write(CfgChip0AddressAddr, CfgChip0Memory, '1, s_reg_error); - if (s_reg_error != 1'b0) $error("unexpected error"); + #600350ns; + + if (TbDutVariant == 0) begin + // The configuration barrier completes only after the divided clock resumes. + reg_master.send_write(32'h200, 8'd4, '1, s_reg_error); + if (s_reg_error != 1'b0) $error("unexpected error"); + reg_master.send_read(32'h200, reg_read, s_reg_error); + if ((s_reg_error != 1'b0) || (reg_read != 4)) $error("divider update failed"); + + divider_cycle_snapshot = cycle_count; + axi_write_slow(axi_ctrl_mst, 32'h8000_7000, 4, 0); + div4_write_cycles = cycle_count - divider_cycle_snapshot; + + reg_master.send_write(32'h200, 8'd2, '1, s_reg_error); + if (s_reg_error != 1'b0) $error("unexpected error"); + reg_master.send_read(32'h200, reg_read, s_reg_error); + if ((s_reg_error != 1'b0) || (reg_read != 2)) $error("divider restore failed"); + + divider_cycle_snapshot = cycle_count; + axi_write_slow(axi_ctrl_mst, 32'h8000_8000, 4, 0); + div2_write_cycles = cycle_count - divider_cycle_snapshot; + + if (div4_write_cycles <= div2_write_cycles + div2_write_cycles / 2) begin + $error("divider did not reduce PHY throughput: div4=%0d cycles, div2=%0d cycles", + div4_write_cycles, div2_write_cycles); + end + end + + check_config_barrier(axi_ctrl_mst, reg_master); + check_decode_errors(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); + check_atomic_add(axi_ctrl_mst); + check_atomic_range_errors(axi_ctrl_mst, reg_master); + + if (NumPhys == 1) begin + check_unaligned_word_access(axi_ctrl_mst); + end + + if (TbDutVariant == 0) begin + // switch memory address space to register space + reg_master.send_write(32'h408, 32'h1_1901, '1, s_reg_error); + if (s_reg_error != 1'b0) $error("unexpected error"); + + // 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); + if (s_reg_error != 1'b0) $error("unexpected error"); + end check_consecutive_reads(axi_ctrl_mst); @@ -415,7 +982,7 @@ module axi_hyper_tb $display("= Use only phy 0 ="); $display("==========================="); - reg_master.send_write(CfgFrontendAddr, 32'h0, '1, s_reg_error); + reg_master.send_write(32'h100,1'b0,'1,s_reg_error); if (s_reg_error != 1'b0) $error("unexpected error"); axi_rand_mst.reset(); @@ -453,15 +1020,61 @@ module axi_hyper_tb .NumChips ( NumChips ), .NumPhys ( NumPhys ), + .AnnotateSdf ( TbAnnotateSdf ), .IsClockODelayed ( IsClockODelayed ), + .DutVariant ( TbDutVariant ), + .PhyCyclTime ( TbPhyCyclTime ), .axi_rule_t ( rule_t ) ) i_dut_if ( // clk and rst signal .clk_i ( clk ), .rst_ni ( rst_n ), .end_sim_i ( end_of_sim ), + .segment_start_count_o ( segment_start_count ), .axi_slv_if ( axi_dut_intf ), .reg_slv_if ( reg_bus_mst ) ); endmodule + +module axi_hyper_tb_isochronous; + axi_hyper_tb #( + .TbDutVariant ( 0 ), + .TbCyclTime ( 5ns ), + .TbPhyCyclTime ( 6ns ), + .TbTxDelayLineTaps ( 31 ) + ) i_axi_hyper_tb (); +endmodule + +module axi_hyper_tb_synchronous; + axi_hyper_tb #( + .TbDutVariant ( 1 ), + .TbCyclTime ( 10ns ), + .TbPhyCyclTime ( 10ns ), + .TbRxDelayLineTaps ( 16 ), + .TbTxDelayLineTaps ( 31 ) + ) i_axi_hyper_tb (); +endmodule + +module axi_hyper_tb_asynchronous; + axi_hyper_tb #( + .TbDutVariant ( 2 ), + .TbCyclTime ( 5ns ), + .TbPhyCyclTime ( 6ns ), + .TbRxDelayLineTaps ( 16 ), + .TbTxDelayLineTaps ( 19 ) + ) i_axi_hyper_tb (); +endmodule + +module axi_hyper_tb_synchronous_one_phy; + axi_hyper_tb #( + .NumPhys ( 1 ), + .TbNumWrites ( 0 ), + .TbNumReads ( 0 ), + .TbDutVariant ( 1 ), + .TbCyclTime ( 10ns ), + .TbPhyCyclTime ( 10ns ), + .TbRxDelayLineTaps( 16 ), + .TbTxDelayLineTaps( 31 ) + ) i_axi_hyper_tb (); +endmodule diff --git a/test/dut_if.sv b/test/dut_if.sv index e63d9e9..5075bf1 100644 --- a/test/dut_if.sv +++ b/test/dut_if.sv @@ -24,12 +24,16 @@ module dut_if parameter int NumChips = -1, parameter int NumPhys = -1, + parameter bit AnnotateSdf = 1'b1, parameter int IsClockODelayed = -1, + parameter int unsigned DutVariant = 0, + parameter time PhyCyclTime = 6ns, parameter type axi_rule_t = logic )( input logic clk_i, input logic rst_ni, input logic end_sim_i, + output logic [31:0] segment_start_count_o, AXI_BUS.Slave axi_slv_if, REG_BUS.in reg_slv_if @@ -80,6 +84,9 @@ module dut_if logic [NumPhys-1:0][7:0] hyper_dq_o; logic [NumPhys-1:0] hyper_dq_oe; logic [NumPhys-1:0] hyper_reset_n_wire; + logic phy_clk; + logic [NumPhys-1:0][NumChips-1:0] hyper_cs_n_q; + logic segment_start; wire [NumPhys-1:0][NumChips-1:0] pad_hyper_csn; wire [NumPhys-1:0] pad_hyper_ck; @@ -122,8 +129,31 @@ module dut_if .r_ready_i ( axi_req.r_ready ) ); + initial begin + phy_clk = 1'b0; + forever begin + #(PhyCyclTime/2); + phy_clk = ~phy_clk; + end + end + + assign segment_start = |(hyper_cs_n_q & ~hyper_cs_n_wire); + + always_ff @(posedge clk_i or negedge rst_ni) begin + if (!rst_ni) begin + hyper_cs_n_q <= '1; + segment_start_count_o <= '0; + end else begin + hyper_cs_n_q <= hyper_cs_n_wire; + if (segment_start) begin + segment_start_count_o <= segment_start_count_o + 1; + end + end + end + // DUT - hyperbus #( + hyperbus_test_dut #( + .DutVariant ( DutVariant ), .NumChips ( NumChips ), .NumPhys ( NumPhys ), .AxiAddrWidth ( AxiAddrWidth ), @@ -132,21 +162,18 @@ module dut_if .AxiUserWidth ( AxiUserWidth ), .axi_req_t ( axi_req_t ), .axi_rsp_t ( axi_resp_t ), - .axi_aw_chan_t ( axi_aw_chan_t ), - .axi_w_chan_t ( axi_w_chan_t ), - .axi_b_chan_t ( axi_b_chan_t ), - .axi_ar_chan_t ( axi_ar_chan_t ), - .axi_r_chan_t ( axi_r_chan_t ), .RegDataWidth ( RegDw ), .reg_req_t ( reg_req_t ), .reg_rsp_t ( reg_rsp_t ), - .UsePhyClkDivider ( 1 ), .axi_rule_t ( axi_rule_t ) ) i_dut ( - .clk_phy_i ( clk_i ), - .rst_phy_ni ( rst_ni ), .clk_sys_i ( clk_i ), .rst_sys_ni ( rst_ni ), + .clk_phy_i ( phy_clk ), + .rst_phy_ni ( rst_ni ), +`ifdef TARGET_XILINX + .clk_ref200_i ( clk_i ), +`endif .test_mode_i ( 1'b0 ), .axi_req_i ( axi_req ), .axi_rsp_o ( axi_resp ), @@ -184,7 +211,7 @@ module dut_if .DQ1 ( pad_hyper_dq[i][1] ), .DQ0 ( pad_hyper_dq[i][0] ), .RWDS ( pad_hyper_rwds[i] ), - .CSNeg ( pad_hyper_csn[i][0] ), + .CSNeg ( pad_hyper_csn[i][j] ), .CK ( pad_hyper_ck[i] ), .CKNeg ( pad_hyper_ckn[i] ), .RESETNeg ( pad_hyper_reset[i] ) @@ -193,18 +220,18 @@ module dut_if end // block: hyperrams endgenerate - generate + if (AnnotateSdf) begin : gen_sdf_annotation for (genvar p=0; p