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 de23acc..df9a1bc 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,28 +42,40 @@ 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/hyperbus_axi.sv + - src/backend/hyperbus_phy.sv + - src/hyperbus_axi_frontend.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 - - src/hyperbus.sv + - test/hyperbus_atomic_handler_tb.sv + - test/hyperbus_cfg_regs_tb.sv diff --git a/Makefile b/Makefile index 93b6d6f..e94ebbd 100644 --- a/Makefile +++ b/Makefile @@ -12,6 +12,9 @@ all: build run clean: sim_clean +update-regs: + bash scripts/gen_regs.sh + # Ensure half-built targets are purged .DELETE_ON_ERROR: diff --git a/docs/regs/hyperbus_cfg_regs.md b/docs/regs/hyperbus_cfg_regs.md new file mode 100644 index 0000000..4a6b384 --- /dev/null +++ b/docs/regs/hyperbus_cfg_regs.md @@ -0,0 +1,437 @@ + + +## hyperbus_cfg_regs address map + +- Absolute Address: 0x0 +- Base Offset: 0x0 +- Size: 0x7C + +

HyperBus controller configuration. Chip address ranges must be 4 MiB aligned, ordered by chip-select index, and non-overlapping.

+ +|Offset| Identifier |Name| +|------|---------------------|----| +| 0x00 | t_latency_access | — | +| 0x04 |en_latency_additional| — | +| 0x08 | t_burst_max | — | +| 0x0C |t_read_write_recovery| — | +| 0x10 | t_rx_clk_delay | — | +| 0x14 | t_tx_clk_delay | — | +| 0x18 | address_mask_msb | — | +| 0x1C | address_space | — | +| 0x20 | dual_phy | — | +| 0x28 | t_csh_cycles | — | +| 0x2C | csn_to_ck_cycles | — | +| 0x30 | chip0_base | — | +| 0x34 | chip0_bound | — | +| 0x38 | chip1_base | — | +| 0x3C | chip1_bound | — | +| 0x40 | chip2_base | — | +| 0x44 | chip2_bound | — | +| 0x48 | chip3_base | — | +| 0x4C | chip3_bound | — | +| 0x50 | flush | — | +| 0x54 | status | — | +| 0x58 | chip4_base | — | +| 0x5C | chip4_bound | — | +| 0x60 | chip5_base | — | +| 0x64 | chip5_bound | — | +| 0x68 | chip6_base | — | +| 0x6C | chip6_bound | — | +| 0x70 | chip7_base | — | +| 0x74 | chip7_bound | — | +| 0x78 | phy_clock_div | — | + +### t_latency_access register + +- Absolute Address: 0x0 +- Base Offset: 0x0 +- Size: 0x4 + +

Initial latency cycles before read or write data.

+ +|Bits|Identifier|Access|Reset|Name| +|----|----------|------|-----|----| +| 3:0| value | rw | 0x6 | — | + +### en_latency_additional register + +- Absolute Address: 0x4 +- Base Offset: 0x4 +- Size: 0x4 + +

Enable additional latency cycles when requested by RWDS.

+ +|Bits|Identifier|Access|Reset|Name| +|----|----------|------|-----|----| +| 0 | value | rw | 0x0 | — | + +### t_burst_max register + +- Absolute Address: 0x8 +- Base Offset: 0x8 +- Size: 0x4 + +

Maximum continuous burst length before the PHY restarts a transfer.

+ +|Bits|Identifier|Access|Reset|Name| +|----|----------|------|-----|----| +|15:0| value | rw |0x15E| — | + +### t_read_write_recovery register + +- Absolute Address: 0xC +- Base Offset: 0xC +- Size: 0x4 + +

Recovery cycles inserted between read and write phases.

+ +|Bits|Identifier|Access|Reset|Name| +|----|----------|------|-----|----| +| 3:0| value | rw | 0x6 | — | + +### t_rx_clk_delay register + +- Absolute Address: 0x10 +- Base Offset: 0x10 +- Size: 0x4 + +

RX sampling delay with fine taps in bits 4:0 and coarse taps in bits 7:5.

+ +|Bits|Identifier|Access|Reset|Name| +|----|----------|------|-----|----| +| 4:0| fine | rw | 0x10| — | +| 7:5| coarse | rw | 0x0 | — | + +#### fine field + +

Fine delay-line tap setting, nominally about 78 ps per tap.

+ +#### coarse field + +

Coarse delay-line tap setting with an implementation-defined step size.

+ +### t_tx_clk_delay register + +- Absolute Address: 0x14 +- Base Offset: 0x14 +- Size: 0x4 + +

TX clock delay with fine taps in bits 4:0 and coarse taps in bits 7:5.

+ +|Bits|Identifier|Access|Reset|Name| +|----|----------|------|-----|----| +| 4:0| fine | rw | 0x10| — | +| 7:5| coarse | rw | 0x0 | — | + +#### fine field + +

Fine delay-line tap setting, nominally about 78 ps per tap.

+ +#### coarse field + +

Coarse delay-line tap setting with an implementation-defined step size.

+ +### address_mask_msb register + +- Absolute Address: 0x18 +- Base Offset: 0x18 +- Size: 0x4 + +

Most-significant address bit used by frontend address packing.

+ +|Bits|Identifier|Access|Reset|Name| +|----|----------|------|-----|----| +| 4:0| value | rw | 0x19| — | + +### address_space register + +- Absolute Address: 0x1C +- Base Offset: 0x1C +- Size: 0x4 + +

Select HyperRAM or HyperFlash address-space interpretation.

+ +|Bits|Identifier|Access|Reset|Name| +|----|----------|------|-----|----| +| 0 | value | rw | 0x0 | — | + +### dual_phy register + +- Absolute Address: 0x20 +- Base Offset: 0x20 +- Size: 0x4 + +

Enable both PHY lanes; when clear, only PHY lane 0 is active.

+ +|Bits|Identifier|Access|Reset|Name| +|----|----------|------|-----|----| +| 0 | value | rw | 0x1 | — | + +### t_csh_cycles register + +- Absolute Address: 0x28 +- Base Offset: 0x28 +- Size: 0x4 + +

Chip-select high time between transfers.

+ +|Bits|Identifier|Access|Reset|Name| +|----|----------|------|-----|----| +| 3:0| value | rw | 0x1 | — | + +### csn_to_ck_cycles register + +- Absolute Address: 0x2C +- Base Offset: 0x2C +- Size: 0x4 + +

Delay cycles from chip-select assertion to clock start.

+ +|Bits|Identifier|Access|Reset|Name| +|----|----------|------|-----|----| +| 3:0| value | rw | 0x0 | — | + +### chip0_base register + +- Absolute Address: 0x30 +- Base Offset: 0x30 +- Size: 0x4 + +

Inclusive 4 MiB-aligned address for chip-select 0 decoding.

+ +| Bits|Identifier|Access|Reset|Name| +|-----|----------|------|-----|----| +|31:22| value | rw | 0x0 | — | + +### chip0_bound register + +- Absolute Address: 0x34 +- Base Offset: 0x34 +- Size: 0x4 + +

Exclusive 4 MiB-aligned address for chip-select 0 decoding.

+ +| Bits|Identifier|Access|Reset|Name| +|-----|----------|------|-----|----| +|31:22| value | rw | 0x1 | — | + +### chip1_base register + +- Absolute Address: 0x38 +- Base Offset: 0x38 +- Size: 0x4 + +

Inclusive 4 MiB-aligned address for chip-select 1 decoding.

+ +| Bits|Identifier|Access|Reset|Name| +|-----|----------|------|-----|----| +|31:22| value | rw | 0x1 | — | + +### chip1_bound register + +- Absolute Address: 0x3C +- Base Offset: 0x3C +- Size: 0x4 + +

Exclusive 4 MiB-aligned address for chip-select 1 decoding.

+ +| Bits|Identifier|Access|Reset|Name| +|-----|----------|------|-----|----| +|31:22| value | rw | 0x2 | — | + +### chip2_base register + +- Absolute Address: 0x40 +- Base Offset: 0x40 +- Size: 0x4 + +

Inclusive 4 MiB-aligned address for chip-select 2 decoding.

+ +| Bits|Identifier|Access|Reset|Name| +|-----|----------|------|-----|----| +|31:22| value | rw | 0x2 | — | + +### chip2_bound register + +- Absolute Address: 0x44 +- Base Offset: 0x44 +- Size: 0x4 + +

Exclusive 4 MiB-aligned address for chip-select 2 decoding.

+ +| Bits|Identifier|Access|Reset|Name| +|-----|----------|------|-----|----| +|31:22| value | rw | 0x3 | — | + +### chip3_base register + +- Absolute Address: 0x48 +- Base Offset: 0x48 +- Size: 0x4 + +

Inclusive 4 MiB-aligned address for chip-select 3 decoding.

+ +| Bits|Identifier|Access|Reset|Name| +|-----|----------|------|-----|----| +|31:22| value | rw | 0x3 | — | + +### chip3_bound register + +- Absolute Address: 0x4C +- Base Offset: 0x4C +- Size: 0x4 + +

Exclusive 4 MiB-aligned address for chip-select 3 decoding.

+ +| Bits|Identifier|Access|Reset|Name| +|-----|----------|------|-----|----| +|31:22| value | rw | 0x4 | — | + +### flush register + +- Absolute Address: 0x50 +- Base Offset: 0x50 +- Size: 0x4 + +

Write to stop accepting requests and drain all in-flight HyperBus operations.

+ +|Bits|Identifier|Access|Reset|Name| +|----|----------|------|-----|----| +| 0 | request | w | — | — | + +#### request field + +

Writing any value executes the ordering barrier; the value is not stored.

+ +### status register + +- Absolute Address: 0x54 +- Base Offset: 0x54 +- Size: 0x4 + +

Sticky protocol and address error status.

+ +|Bits| Identifier | Access |Reset|Name| +|----|------------|---------|-----|----| +| 0 |decode_error|rw, woclr| 0x0 | — | + +#### decode_error field + +

A host request did not match any configured chip address range.

+ +### chip4_base register + +- Absolute Address: 0x58 +- Base Offset: 0x58 +- Size: 0x4 + +

Inclusive 4 MiB-aligned address for chip-select 4 decoding.

+ +| Bits|Identifier|Access|Reset|Name| +|-----|----------|------|-----|----| +|31:22| value | rw | 0x4 | — | + +### chip4_bound register + +- Absolute Address: 0x5C +- Base Offset: 0x5C +- Size: 0x4 + +

Exclusive 4 MiB-aligned address for chip-select 4 decoding.

+ +| Bits|Identifier|Access|Reset|Name| +|-----|----------|------|-----|----| +|31:22| value | rw | 0x5 | — | + +### chip5_base register + +- Absolute Address: 0x60 +- Base Offset: 0x60 +- Size: 0x4 + +

Inclusive 4 MiB-aligned address for chip-select 5 decoding.

+ +| Bits|Identifier|Access|Reset|Name| +|-----|----------|------|-----|----| +|31:22| value | rw | 0x5 | — | + +### chip5_bound register + +- Absolute Address: 0x64 +- Base Offset: 0x64 +- Size: 0x4 + +

Exclusive 4 MiB-aligned address for chip-select 5 decoding.

+ +| Bits|Identifier|Access|Reset|Name| +|-----|----------|------|-----|----| +|31:22| value | rw | 0x6 | — | + +### chip6_base register + +- Absolute Address: 0x68 +- Base Offset: 0x68 +- Size: 0x4 + +

Inclusive 4 MiB-aligned address for chip-select 6 decoding.

+ +| Bits|Identifier|Access|Reset|Name| +|-----|----------|------|-----|----| +|31:22| value | rw | 0x6 | — | + +### chip6_bound register + +- Absolute Address: 0x6C +- Base Offset: 0x6C +- Size: 0x4 + +

Exclusive 4 MiB-aligned address for chip-select 6 decoding.

+ +| Bits|Identifier|Access|Reset|Name| +|-----|----------|------|-----|----| +|31:22| value | rw | 0x7 | — | + +### chip7_base register + +- Absolute Address: 0x70 +- Base Offset: 0x70 +- Size: 0x4 + +

Inclusive 4 MiB-aligned address for chip-select 7 decoding.

+ +| Bits|Identifier|Access|Reset|Name| +|-----|----------|------|-----|----| +|31:22| value | rw | 0x7 | — | + +### chip7_bound register + +- Absolute Address: 0x74 +- Base Offset: 0x74 +- Size: 0x4 + +

Exclusive 4 MiB-aligned address for chip-select 7 decoding.

+ +| Bits|Identifier|Access|Reset|Name| +|-----|----------|------|-----|----| +|31:22| value | rw | 0x8 | — | + +### phy_clock_div register + +- Absolute Address: 0x78 +- Base Offset: 0x78 +- Size: 0x4 + +

System-to-PHY clock division factor used by the isochronous top.

+ +|Bits|Identifier|Access|Reset|Name| +|----|----------|------|-----|----| +| 7:0| value | rw | 0x8 | — | + +#### value field + +

Must be at least two. Ignored by synchronous and asynchronous tops.

diff --git a/include/hyperbus/typedef.svh b/include/hyperbus/typedef.svh new file mode 100644 index 0000000..cd20bbb --- /dev/null +++ b/include/hyperbus/typedef.svh @@ -0,0 +1,114 @@ +// 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_ + +//////////////////////////////// +// Protocol-neutral host link // +//////////////////////////////// + +`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) + +//////////////////////////// +// Midend-to-backend link // +//////////////////////////// + +`define HYPERBUS_TYPEDEF_LINK_ALL_CT(__name, __num_phys) \ + typedef struct packed { \ + logic [(16*__num_phys)-1:0] data; \ + logic last; \ + 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 [hyperbus_pkg::HyperNumChips-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/include/hyperbus_cfg_regs.h b/include/hyperbus_cfg_regs.h new file mode 100644 index 0000000..9e8a234 --- /dev/null +++ b/include/hyperbus_cfg_regs.h @@ -0,0 +1,242 @@ +// Generated by PeakRDL-cheader - A free and open-source header generator +// https://github.com/SystemRDL/PeakRDL-cheader + +#ifndef HYPERBUS_CFG_REGS_H +#define HYPERBUS_CFG_REGS_H + +#ifdef __cplusplus +extern "C" { +#endif +#include +#include + +// reg - hyperbus_cfg_regs::t_latency_access +#define HYPERBUS_CFG_REGS__T_LATENCY_ACCESS__VALUE_bm 0xf +#define HYPERBUS_CFG_REGS__T_LATENCY_ACCESS__VALUE_bp 0 +#define HYPERBUS_CFG_REGS__T_LATENCY_ACCESS__VALUE_bw 4 +#define HYPERBUS_CFG_REGS__T_LATENCY_ACCESS__VALUE_reset 0x6 + +// reg - hyperbus_cfg_regs::en_latency_additional +#define HYPERBUS_CFG_REGS__EN_LATENCY_ADDITIONAL__VALUE_bm 0x1 +#define HYPERBUS_CFG_REGS__EN_LATENCY_ADDITIONAL__VALUE_bp 0 +#define HYPERBUS_CFG_REGS__EN_LATENCY_ADDITIONAL__VALUE_bw 1 +#define HYPERBUS_CFG_REGS__EN_LATENCY_ADDITIONAL__VALUE_reset 0x0 + +// reg - hyperbus_cfg_regs::t_burst_max +#define HYPERBUS_CFG_REGS__T_BURST_MAX__VALUE_bm 0xffff +#define HYPERBUS_CFG_REGS__T_BURST_MAX__VALUE_bp 0 +#define HYPERBUS_CFG_REGS__T_BURST_MAX__VALUE_bw 16 +#define HYPERBUS_CFG_REGS__T_BURST_MAX__VALUE_reset 0x15e + +// reg - hyperbus_cfg_regs::t_read_write_recovery +#define HYPERBUS_CFG_REGS__T_READ_WRITE_RECOVERY__VALUE_bm 0xf +#define HYPERBUS_CFG_REGS__T_READ_WRITE_RECOVERY__VALUE_bp 0 +#define HYPERBUS_CFG_REGS__T_READ_WRITE_RECOVERY__VALUE_bw 4 +#define HYPERBUS_CFG_REGS__T_READ_WRITE_RECOVERY__VALUE_reset 0x6 + +// reg - hyperbus_cfg_regs::t_rx_clk_delay +#define HYPERBUS_CFG_REGS__T_RX_CLK_DELAY__FINE_bm 0x1f +#define HYPERBUS_CFG_REGS__T_RX_CLK_DELAY__FINE_bp 0 +#define HYPERBUS_CFG_REGS__T_RX_CLK_DELAY__FINE_bw 5 +#define HYPERBUS_CFG_REGS__T_RX_CLK_DELAY__FINE_reset 0x10 +#define HYPERBUS_CFG_REGS__T_RX_CLK_DELAY__COARSE_bm 0xe0 +#define HYPERBUS_CFG_REGS__T_RX_CLK_DELAY__COARSE_bp 5 +#define HYPERBUS_CFG_REGS__T_RX_CLK_DELAY__COARSE_bw 3 +#define HYPERBUS_CFG_REGS__T_RX_CLK_DELAY__COARSE_reset 0x0 + +// reg - hyperbus_cfg_regs::t_tx_clk_delay +#define HYPERBUS_CFG_REGS__T_TX_CLK_DELAY__FINE_bm 0x1f +#define HYPERBUS_CFG_REGS__T_TX_CLK_DELAY__FINE_bp 0 +#define HYPERBUS_CFG_REGS__T_TX_CLK_DELAY__FINE_bw 5 +#define HYPERBUS_CFG_REGS__T_TX_CLK_DELAY__FINE_reset 0x10 +#define HYPERBUS_CFG_REGS__T_TX_CLK_DELAY__COARSE_bm 0xe0 +#define HYPERBUS_CFG_REGS__T_TX_CLK_DELAY__COARSE_bp 5 +#define HYPERBUS_CFG_REGS__T_TX_CLK_DELAY__COARSE_bw 3 +#define HYPERBUS_CFG_REGS__T_TX_CLK_DELAY__COARSE_reset 0x0 + +// reg - hyperbus_cfg_regs::address_mask_msb +#define HYPERBUS_CFG_REGS__ADDRESS_MASK_MSB__VALUE_bm 0x1f +#define HYPERBUS_CFG_REGS__ADDRESS_MASK_MSB__VALUE_bp 0 +#define HYPERBUS_CFG_REGS__ADDRESS_MASK_MSB__VALUE_bw 5 +#define HYPERBUS_CFG_REGS__ADDRESS_MASK_MSB__VALUE_reset 0x19 + +// reg - hyperbus_cfg_regs::address_space +#define HYPERBUS_CFG_REGS__ADDRESS_SPACE__VALUE_bm 0x1 +#define HYPERBUS_CFG_REGS__ADDRESS_SPACE__VALUE_bp 0 +#define HYPERBUS_CFG_REGS__ADDRESS_SPACE__VALUE_bw 1 +#define HYPERBUS_CFG_REGS__ADDRESS_SPACE__VALUE_reset 0x0 + +// reg - hyperbus_cfg_regs::dual_phy +#define HYPERBUS_CFG_REGS__DUAL_PHY__VALUE_bm 0x1 +#define HYPERBUS_CFG_REGS__DUAL_PHY__VALUE_bp 0 +#define HYPERBUS_CFG_REGS__DUAL_PHY__VALUE_bw 1 +#define HYPERBUS_CFG_REGS__DUAL_PHY__VALUE_reset 0x1 + +// reg - hyperbus_cfg_regs::t_csh_cycles +#define HYPERBUS_CFG_REGS__T_CSH_CYCLES__VALUE_bm 0xf +#define HYPERBUS_CFG_REGS__T_CSH_CYCLES__VALUE_bp 0 +#define HYPERBUS_CFG_REGS__T_CSH_CYCLES__VALUE_bw 4 +#define HYPERBUS_CFG_REGS__T_CSH_CYCLES__VALUE_reset 0x1 + +// reg - hyperbus_cfg_regs::csn_to_ck_cycles +#define HYPERBUS_CFG_REGS__CSN_TO_CK_CYCLES__VALUE_bm 0xf +#define HYPERBUS_CFG_REGS__CSN_TO_CK_CYCLES__VALUE_bp 0 +#define HYPERBUS_CFG_REGS__CSN_TO_CK_CYCLES__VALUE_bw 4 +#define HYPERBUS_CFG_REGS__CSN_TO_CK_CYCLES__VALUE_reset 0x0 + +// reg - hyperbus_cfg_regs::chip0_base +#define HYPERBUS_CFG_REGS__CHIP0_BASE__VALUE_bm 0xffc00000 +#define HYPERBUS_CFG_REGS__CHIP0_BASE__VALUE_bp 22 +#define HYPERBUS_CFG_REGS__CHIP0_BASE__VALUE_bw 10 +#define HYPERBUS_CFG_REGS__CHIP0_BASE__VALUE_reset 0x0 + +// reg - hyperbus_cfg_regs::chip0_bound +#define HYPERBUS_CFG_REGS__CHIP0_BOUND__VALUE_bm 0xffc00000 +#define HYPERBUS_CFG_REGS__CHIP0_BOUND__VALUE_bp 22 +#define HYPERBUS_CFG_REGS__CHIP0_BOUND__VALUE_bw 10 +#define HYPERBUS_CFG_REGS__CHIP0_BOUND__VALUE_reset 0x1 + +// reg - hyperbus_cfg_regs::chip1_base +#define HYPERBUS_CFG_REGS__CHIP1_BASE__VALUE_bm 0xffc00000 +#define HYPERBUS_CFG_REGS__CHIP1_BASE__VALUE_bp 22 +#define HYPERBUS_CFG_REGS__CHIP1_BASE__VALUE_bw 10 +#define HYPERBUS_CFG_REGS__CHIP1_BASE__VALUE_reset 0x1 + +// reg - hyperbus_cfg_regs::chip1_bound +#define HYPERBUS_CFG_REGS__CHIP1_BOUND__VALUE_bm 0xffc00000 +#define HYPERBUS_CFG_REGS__CHIP1_BOUND__VALUE_bp 22 +#define HYPERBUS_CFG_REGS__CHIP1_BOUND__VALUE_bw 10 +#define HYPERBUS_CFG_REGS__CHIP1_BOUND__VALUE_reset 0x2 + +// reg - hyperbus_cfg_regs::chip2_base +#define HYPERBUS_CFG_REGS__CHIP2_BASE__VALUE_bm 0xffc00000 +#define HYPERBUS_CFG_REGS__CHIP2_BASE__VALUE_bp 22 +#define HYPERBUS_CFG_REGS__CHIP2_BASE__VALUE_bw 10 +#define HYPERBUS_CFG_REGS__CHIP2_BASE__VALUE_reset 0x2 + +// reg - hyperbus_cfg_regs::chip2_bound +#define HYPERBUS_CFG_REGS__CHIP2_BOUND__VALUE_bm 0xffc00000 +#define HYPERBUS_CFG_REGS__CHIP2_BOUND__VALUE_bp 22 +#define HYPERBUS_CFG_REGS__CHIP2_BOUND__VALUE_bw 10 +#define HYPERBUS_CFG_REGS__CHIP2_BOUND__VALUE_reset 0x3 + +// reg - hyperbus_cfg_regs::chip3_base +#define HYPERBUS_CFG_REGS__CHIP3_BASE__VALUE_bm 0xffc00000 +#define HYPERBUS_CFG_REGS__CHIP3_BASE__VALUE_bp 22 +#define HYPERBUS_CFG_REGS__CHIP3_BASE__VALUE_bw 10 +#define HYPERBUS_CFG_REGS__CHIP3_BASE__VALUE_reset 0x3 + +// reg - hyperbus_cfg_regs::chip3_bound +#define HYPERBUS_CFG_REGS__CHIP3_BOUND__VALUE_bm 0xffc00000 +#define HYPERBUS_CFG_REGS__CHIP3_BOUND__VALUE_bp 22 +#define HYPERBUS_CFG_REGS__CHIP3_BOUND__VALUE_bw 10 +#define HYPERBUS_CFG_REGS__CHIP3_BOUND__VALUE_reset 0x4 + +// reg - hyperbus_cfg_regs::flush +#define HYPERBUS_CFG_REGS__FLUSH__REQUEST_bm 0x1 +#define HYPERBUS_CFG_REGS__FLUSH__REQUEST_bp 0 +#define HYPERBUS_CFG_REGS__FLUSH__REQUEST_bw 1 + +// reg - hyperbus_cfg_regs::status +#define HYPERBUS_CFG_REGS__STATUS__DECODE_ERROR_bm 0x1 +#define HYPERBUS_CFG_REGS__STATUS__DECODE_ERROR_bp 0 +#define HYPERBUS_CFG_REGS__STATUS__DECODE_ERROR_bw 1 +#define HYPERBUS_CFG_REGS__STATUS__DECODE_ERROR_reset 0x0 + +// reg - hyperbus_cfg_regs::chip4_base +#define HYPERBUS_CFG_REGS__CHIP4_BASE__VALUE_bm 0xffc00000 +#define HYPERBUS_CFG_REGS__CHIP4_BASE__VALUE_bp 22 +#define HYPERBUS_CFG_REGS__CHIP4_BASE__VALUE_bw 10 +#define HYPERBUS_CFG_REGS__CHIP4_BASE__VALUE_reset 0x4 + +// reg - hyperbus_cfg_regs::chip4_bound +#define HYPERBUS_CFG_REGS__CHIP4_BOUND__VALUE_bm 0xffc00000 +#define HYPERBUS_CFG_REGS__CHIP4_BOUND__VALUE_bp 22 +#define HYPERBUS_CFG_REGS__CHIP4_BOUND__VALUE_bw 10 +#define HYPERBUS_CFG_REGS__CHIP4_BOUND__VALUE_reset 0x5 + +// reg - hyperbus_cfg_regs::chip5_base +#define HYPERBUS_CFG_REGS__CHIP5_BASE__VALUE_bm 0xffc00000 +#define HYPERBUS_CFG_REGS__CHIP5_BASE__VALUE_bp 22 +#define HYPERBUS_CFG_REGS__CHIP5_BASE__VALUE_bw 10 +#define HYPERBUS_CFG_REGS__CHIP5_BASE__VALUE_reset 0x5 + +// reg - hyperbus_cfg_regs::chip5_bound +#define HYPERBUS_CFG_REGS__CHIP5_BOUND__VALUE_bm 0xffc00000 +#define HYPERBUS_CFG_REGS__CHIP5_BOUND__VALUE_bp 22 +#define HYPERBUS_CFG_REGS__CHIP5_BOUND__VALUE_bw 10 +#define HYPERBUS_CFG_REGS__CHIP5_BOUND__VALUE_reset 0x6 + +// reg - hyperbus_cfg_regs::chip6_base +#define HYPERBUS_CFG_REGS__CHIP6_BASE__VALUE_bm 0xffc00000 +#define HYPERBUS_CFG_REGS__CHIP6_BASE__VALUE_bp 22 +#define HYPERBUS_CFG_REGS__CHIP6_BASE__VALUE_bw 10 +#define HYPERBUS_CFG_REGS__CHIP6_BASE__VALUE_reset 0x6 + +// reg - hyperbus_cfg_regs::chip6_bound +#define HYPERBUS_CFG_REGS__CHIP6_BOUND__VALUE_bm 0xffc00000 +#define HYPERBUS_CFG_REGS__CHIP6_BOUND__VALUE_bp 22 +#define HYPERBUS_CFG_REGS__CHIP6_BOUND__VALUE_bw 10 +#define HYPERBUS_CFG_REGS__CHIP6_BOUND__VALUE_reset 0x7 + +// reg - hyperbus_cfg_regs::chip7_base +#define HYPERBUS_CFG_REGS__CHIP7_BASE__VALUE_bm 0xffc00000 +#define HYPERBUS_CFG_REGS__CHIP7_BASE__VALUE_bp 22 +#define HYPERBUS_CFG_REGS__CHIP7_BASE__VALUE_bw 10 +#define HYPERBUS_CFG_REGS__CHIP7_BASE__VALUE_reset 0x7 + +// reg - hyperbus_cfg_regs::chip7_bound +#define HYPERBUS_CFG_REGS__CHIP7_BOUND__VALUE_bm 0xffc00000 +#define HYPERBUS_CFG_REGS__CHIP7_BOUND__VALUE_bp 22 +#define HYPERBUS_CFG_REGS__CHIP7_BOUND__VALUE_bw 10 +#define HYPERBUS_CFG_REGS__CHIP7_BOUND__VALUE_reset 0x8 + +// reg - hyperbus_cfg_regs::phy_clock_div +#define HYPERBUS_CFG_REGS__PHY_CLOCK_DIV__VALUE_bm 0xff +#define HYPERBUS_CFG_REGS__PHY_CLOCK_DIV__VALUE_bp 0 +#define HYPERBUS_CFG_REGS__PHY_CLOCK_DIV__VALUE_bw 8 +#define HYPERBUS_CFG_REGS__PHY_CLOCK_DIV__VALUE_reset 0x8 + +// addrmap - hyperbus_cfg_regs +typedef struct __attribute__ ((__packed__)) { + uint32_t t_latency_access; + uint32_t en_latency_additional; + uint32_t t_burst_max; + uint32_t t_read_write_recovery; + uint32_t t_rx_clk_delay; + uint32_t t_tx_clk_delay; + uint32_t address_mask_msb; + uint32_t address_space; + uint32_t dual_phy; + uint8_t RESERVED_24_27[0x4]; + uint32_t t_csh_cycles; + uint32_t csn_to_ck_cycles; + uint32_t chip0_base; + uint32_t chip0_bound; + uint32_t chip1_base; + uint32_t chip1_bound; + uint32_t chip2_base; + uint32_t chip2_bound; + uint32_t chip3_base; + uint32_t chip3_bound; + uint32_t flush; + uint32_t status; + uint32_t chip4_base; + uint32_t chip4_bound; + uint32_t chip5_base; + uint32_t chip5_bound; + uint32_t chip6_base; + uint32_t chip6_bound; + uint32_t chip7_base; + uint32_t chip7_bound; + uint32_t phy_clock_div; +} hyperbus_cfg_regs_t; + + +static_assert(sizeof(hyperbus_cfg_regs_t) == 0x7c, "Packing error"); + +#ifdef __cplusplus +} +#endif + +#endif /* HYPERBUS_CFG_REGS_H */ diff --git a/scripts/gen_regs.sh b/scripts/gen_regs.sh new file mode 100755 index 0000000..3d625fc --- /dev/null +++ b/scripts/gen_regs.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash +# Copyright 2026 ETH Zurich and University of Bologna. +# Solderpad Hardware License, Version 0.51, see LICENSE for details. +# SPDX-License-Identifier: SHL-0.51 + +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +UV_CACHE_DIR="${UV_CACHE_DIR:-/tmp/uv-cache}" +UV_TOOL_DIR="${UV_TOOL_DIR:-/tmp/uv-tools}" +export UV_CACHE_DIR +export UV_TOOL_DIR + +RDL="${ROOT}/src/regs/hyperbus_cfg_regs.rdl" + +mkdir -p "${ROOT}/include" "${ROOT}/docs/regs" +rm -rf "${ROOT}/docs/regs/hyperbus_cfg_regs" + +uvx --from peakrdl-cli \ + --with peakrdl-regblock \ + --with peakrdl-cheader \ + --with peakrdl-markdown \ + peakrdl regblock \ + --cpuif apb4-flat \ + --default-reset arst_n \ + --module-name hyperbus_cfg_regblock \ + --package-name hyperbus_cfg_regblock_pkg \ + --addr-width 7 \ + -o "${ROOT}/src/regs" \ + "${RDL}" + +uvx --from peakrdl-cli \ + --with peakrdl-regblock \ + --with peakrdl-cheader \ + --with peakrdl-markdown \ + peakrdl c-header \ + -o "${ROOT}/include/hyperbus_cfg_regs.h" \ + "${RDL}" + +uvx --from peakrdl-cli \ + --with peakrdl-regblock \ + --with peakrdl-cheader \ + --with peakrdl-markdown \ + peakrdl markdown \ + -o "${ROOT}/docs/regs/hyperbus_cfg_regs.md" \ + "${RDL}" diff --git a/scripts/start.tcl b/scripts/start.tcl index 0488812..b192465 100644 --- a/scripts/start.tcl +++ b/scripts/start.tcl @@ -2,12 +2,46 @@ # Solderpad Hardware License, Version 0.51, see LICENSE for details. # SPDX-License-Identifier: SHL-0.51 -vsim axi_hyper_tb -t 1ps -voptargs=+acc -classdebug +set regression_failed 0 -set StdArithNoWarnings 1 -set NumericStdNoWarnings 1 -log -r /* +proc run_test {top_name wlf_name} { + global regression_failed -delete wave * + puts "==============================" + puts "= Running $top_name" + puts "==============================" -run -all + set transcript_name "[file rootname $wlf_name].log" + file delete -force $transcript_name + transcript file $transcript_name + + # 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 + quit -sim + + transcript file {} + set transcript_fd [open $transcript_name r] + set transcript_data [read $transcript_fd] + close $transcript_fd + if {[regexp -line {^# \*\* (Error|Fatal)( \([^)]*\))?:} $transcript_data]} { + puts "Regression errors detected in $transcript_name" + set regression_failed 1 + } +} + +run_test axi_hyper_tb_isochronous sim_run_isochronous.wlf +run_test axi_hyper_tb_synchronous sim_run_synchronous.wlf +run_test axi_hyper_tb_asynchronous sim_run_asynchronous.wlf +run_test axi_hyper_tb_synchronous_one_phy sim_run_synchronous_one_phy.wlf +run_test hyperbus_atomic_handler_tb sim_run_atomic_handler.wlf +run_test hyperbus_cfg_regs_tb sim_run_cfg_regs.wlf + +quit -code $regression_failed -f diff --git a/src/backend/hyperbus_backend.sv b/src/backend/hyperbus_backend.sv new file mode 100644 index 0000000..ac3e4d2 --- /dev/null +++ b/src/backend/hyperbus_backend.sv @@ -0,0 +1,259 @@ +// 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 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][hyperbus_pkg::HyperNumChips-1:0] hyper_cs_no, + output logic [NumPhys-1:0] hyper_ck_o, + output logic [NumPhys-1:0] hyper_ck_no, + output logic [NumPhys-1:0] hyper_rwds_o, + 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 +); + + ///////////////////////////// + // Configuration and clock // + ///////////////////////////// + + hyperbus_pkg::phy_cfg_t cfg_q; + logic cfg_apply_accepted; + logic phy_busy_any; + logic clk_tx; + + `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 ) + ); + + ///////////////////// + // Physical lanes // + ///////////////////// + + if (NumPhys == 2) begin : gen_dual_phy + hyperbus_pkg::phy_rx_t [NumPhys-1:0] phy_rx; + hyperbus_pkg::phy_rx_t [NumPhys-1:0] buffered_rx; + 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 #( + .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 #( + .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 69% rename from src/hyperbus_clock_diff_out.sv rename to src/backend/hyperbus_clock_diff_out.sv index 67c6ae6..72ce9fc 100644 --- a/src/hyperbus_clock_diff_out.sv +++ b/src/backend/hyperbus_clock_diff_out.sv @@ -9,28 +9,27 @@ (* no_ungroup *) (* no_boundary_optimization *) (* keep_hierarchy = "yes" *) -module hyperbus_clock_diff_out -( +module hyperbus_clock_diff_out ( input logic in_i, - input logic en_i, //high enable + input logic en_i, output logic out_o, output logic out_no ); - `ifdef FPGA_EMUL +`ifdef FPGA_EMUL - logic en_sync; + logic en_sync; - always_latch - begin - if (in_i == 1'b0) - en_sync <= en_i; - end + always_latch begin + if (in_i == 1'b0) begin + en_sync <= en_i; + end + end - assign out_o = in_i & en_sync; - assign out_no = ~out_o; + assign out_o = in_i & en_sync; + assign out_no = ~out_o; - `else +`else tc_clk_gating i_hyper_ck_gating ( .clk_i ( in_i ), @@ -44,6 +43,6 @@ module hyperbus_clock_diff_out .clk_o ( out_no ) ); - `endif // !`ifdef FPGA_EMUL +`endif endmodule diff --git a/src/hyperbus_ddr_out.sv b/src/backend/hyperbus_ddr_out.sv similarity index 84% rename from src/hyperbus_ddr_out.sv rename to src/backend/hyperbus_ddr_out.sv index d737d15..38f57f5 100644 --- a/src/hyperbus_ddr_out.sv +++ b/src/backend/hyperbus_ddr_out.sv @@ -7,7 +7,7 @@ module hyperbus_ddr_out #( parameter logic Init = 1'b0 -)( +) ( input logic clk_i, input logic rst_ni, input logic d0_i, @@ -18,13 +18,13 @@ module hyperbus_ddr_out #( logic q1; `ifdef FPGA_EMUL - always_comb - begin - if(clk_i == 1'b0) - q_o = q1; - else - q_o = q0; - end + always_comb begin + if (clk_i == 1'b0) begin + q_o = q1; + end else begin + q_o = q0; + end + end `else tc_clk_mux2 i_ddrmux ( .clk_o ( q_o ), @@ -32,7 +32,7 @@ module hyperbus_ddr_out #( .clk1_i ( q0 ), .clk_sel_i ( clk_i ) ); -`endif // !`ifdef FPGA_EMUL +`endif always_ff @(posedge clk_i or negedge rst_ni) begin if (~rst_ni) begin diff --git a/src/hyperbus_delay.sv b/src/backend/hyperbus_delay.sv similarity index 97% rename from src/hyperbus_delay.sv rename to src/backend/hyperbus_delay.sv index 00ea849..43dffc8 100644 --- a/src/hyperbus_delay.sv +++ b/src/backend/hyperbus_delay.sv @@ -20,7 +20,7 @@ module hyperbus_delay ( // Additional delay can be added for debug purposes, // the upper 3 bits are reserved for this optional additional delay configurable_delay #( - .NUM_STEPS(32) + .NUM_STEPS ( 32 ) ) i_delay ( .clk_i ( in_i ), .delay_i ( delay_i[4:0] ), diff --git a/src/hyperbus_phy.sv b/src/backend/hyperbus_phy.sv similarity index 76% rename from src/hyperbus_phy.sv rename to src/backend/hyperbus_phy.sv index 43a5a8d..031a830 100644 --- a/src/hyperbus_phy.sv +++ b/src/backend/hyperbus_phy.sv @@ -10,26 +10,26 @@ `include "common_cells/assertions.svh" module hyperbus_phy import hyperbus_pkg::*; #( - parameter int unsigned NumChips = 2, parameter int unsigned NumPhys = -1, parameter int unsigned TimerWidth = 16, parameter int unsigned RxFifoLogDepth = 3, parameter int unsigned SyncStages = 2, - parameter int unsigned StartupCycles = 300 /*us*/ * 200 /*MHz*/ // Conservative maximum frequency estimate + // Conservative startup delay: 300 us at 200 MHz. + parameter int unsigned StartupCycles = 300 * 200 )( input logic clk_i, - input logic clk_i_90, + input logic clk_tx_i, input logic rst_ni, input logic test_mode_i, // Config registers - input hyper_cfg_t cfg_i, + input phy_cfg_t cfg_i, // PHY control status output logic busy_o, // Transactions input logic trans_valid_i, output logic trans_ready_o, input hyper_tf_t trans_i, // TODO: increase burst width! - input logic [NumChips-1:0] trans_cs_i, + input logic [HyperNumChips-1:0] trans_cs_i, // Transmitting channel input logic tx_valid_i, output logic tx_ready_o, @@ -47,7 +47,7 @@ module hyperbus_phy import hyperbus_pkg::*; #( input logic b_ready_i, output logic b_error_o, // Physical interface - output logic [NumChips-1:0] hyper_cs_no, + output logic [HyperNumChips-1:0] hyper_cs_no, output logic hyper_ck_o, output logic hyper_ck_no, output logic hyper_rwds_o, @@ -59,15 +59,26 @@ 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; + + ////////////////////// + // Persistent state // + ////////////////////// - // PHY state hyper_phy_state_t state_d, state_q; logic [TimerWidth-1:0] timer_d, timer_q; hyper_tf_t tf_d, tf_q; - logic [NumChips-1:0] cs_d, cs_q; + logic [HyperNumChips-1:0] cs_d, cs_q; logic add_latency_d, add_latency_q; // Whether B response is pending @@ -80,7 +91,7 @@ module hyperbus_phy import hyperbus_pkg::*; #( logic r_outstand_inc; logic r_outstand_dec; - // Auxiliar control signals + // Auxiliary control signals logic ctl_write_zero_lat; logic ctl_add_latency; logic ctl_tf_burst_last; @@ -92,11 +103,12 @@ 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; - // Transciever IO + // Transceiver I/O logic trx_clk_ena; logic trx_cs_ena; logic trx_rwds_sample; @@ -111,17 +123,16 @@ module hyperbus_phy import hyperbus_pkg::*; #( logic trx_rx_valid; logic trx_rx_ready; - // ================= - // Transciever - // ================= + ////////////////////// + // Transceiver I/O // + ////////////////////// hyperbus_trx #( - .NumChips ( NumChips ), .RxFifoLogDepth ( RxFifoLogDepth ), .SyncStages ( SyncStages ) ) i_trx ( .clk_i, - .clk_i_90, + .clk_tx_i, .rst_ni, .test_mode_i, .cs_i ( cs_q ), @@ -133,7 +144,7 @@ module hyperbus_phy import hyperbus_pkg::*; #( .tx_data_oe_i ( trx_tx_data_oe ), .tx_rwds_i ( trx_tx_rwds ), .tx_rwds_oe_i ( trx_tx_rwds_oe ), - .rx_clk_delay_i ( cfg_i.t_rx_clk_delay ), + .rx_clk_delay_i ( cfg_i.chip.t_rx_clk_delay ), .rx_clk_set_i ( trx_rx_clk_set ), .rx_clk_reset_i ( trx_rx_clk_reset ), .rx_data_o ( trx_rx_data ), @@ -151,9 +162,9 @@ module hyperbus_phy import hyperbus_pkg::*; #( .hyper_reset_no ); - // ============== - // Dataflow - // ============== + ////////////// + // Dataflow // + ////////////// // Command-address assign ca = hyper_phy_ca_t '{ @@ -201,10 +212,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 @@ -215,16 +228,16 @@ module hyperbus_phy import hyperbus_pkg::*; #( else if (r_outstand_dec & ~r_outstand_inc) r_outstand_q <= r_outstand_q - 1; end - // ============= - // Control - // ============= + ///////////// + // Control // + ///////////// // Auxiliary control signals assign ctl_write_zero_lat = tf_q.address_space & tf_q.write; - // cfg_i.en_latency_additional overwrites the sampled RWDS value. - assign ctl_add_latency = trx_rwds_sample | cfg_i.en_latency_additional; + // 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); @@ -254,7 +267,8 @@ module hyperbus_phy import hyperbus_pkg::*; #( trx_tx_rwds_oe = 1'b0; trx_tx_data_oe = 1'b0; // State-dependent logic - case (state_q) + unique case (state_q) + // Hold chip select inactive until the startup delay expires. Startup: begin trx_cs_ena = 1'b0; // Timer resets to parameterized startup delay @@ -262,21 +276,23 @@ module hyperbus_phy import hyperbus_pkg::*; #( state_d = Idle; end end + // Accept a transfer only after all responses from the previous one have drained. Idle: begin trx_cs_ena = 1'b0; timer_d = timer_q; - // 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; - if(cfg_i.csn_to_ck_cycles != 0) begin + if(cfg_i.chip.csn_to_ck_cycles != 0) begin // assert CS but delay hyper_ck to allow more time // for memory to drive RWDS (to satisfy t_DSV) state_d = DelayCK; - timer_d = cfg_i.csn_to_ck_cycles -1; + timer_d = cfg_i.chip.csn_to_ck_cycles -1; end else begin // max throughput when memory RWDS signal arrives early state_d = SendCA; @@ -290,6 +306,7 @@ module hyperbus_phy import hyperbus_pkg::*; #( trx_tx_data_oe = 1'b1; end end + // Assert chip select early when RWDS needs extra setup time before CK starts. DelayCK: begin trx_clk_ena = 1'b0; trx_rwds_sample_ena = ~ctl_write_zero_lat; @@ -298,6 +315,7 @@ module hyperbus_phy import hyperbus_pkg::*; #( state_d = SendCA; end end + // Shift the three command/address words onto DQ. SendCA: begin // Dataflow handled outside FSM trx_clk_ena = 1'b1; @@ -305,15 +323,16 @@ module hyperbus_phy import hyperbus_pkg::*; #( trx_rwds_sample_ena = ~ctl_write_zero_lat; if (ctl_timer_zero) begin if (ctl_write_zero_lat) begin - timer_d = cfg_i.t_burst_max; + timer_d = cfg_i.chip.t_burst_max; state_d = Write; end else begin - timer_d = TimerWidth'(cfg_i.t_latency_access); + timer_d = TimerWidth'(cfg_i.chip.t_latency_access); add_latency_d = ctl_add_latency; state_d = WaitLatAccess; end end end + // Wait one access-latency interval and sample the additional-latency request. WaitLatAccess: begin trx_clk_ena = 1'b1; trx_tx_data_oe = 1'b1; @@ -325,7 +344,7 @@ module hyperbus_phy import hyperbus_pkg::*; #( if (~add_latency_q) begin // Substract cycle for last CA and another for state delay if(ctl_timer_two) begin - timer_d = cfg_i.t_burst_max; + timer_d = cfg_i.chip.t_burst_max; // Switch to write or read phase and already start // turnaround of tri-state driver (depending on latency // config and if read or write transaction). @@ -345,17 +364,18 @@ module hyperbus_phy import hyperbus_pkg::*; #( end else if (ctl_timer_one) begin // instead of going to 0, add another latency count state_d = WaitAddLatAccess; - timer_d = TimerWidth'(cfg_i.t_latency_access); + timer_d = TimerWidth'(cfg_i.chip.t_latency_access); add_latency_d = 1'b0; end end + // Complete the requested second access-latency interval. WaitAddLatAccess: begin // Same as WaitLatAccess but without possibility // of adding another latency count trx_clk_ena = 1'b1; trx_tx_data_oe = 1'b1; if (ctl_timer_two) begin - timer_d = cfg_i.t_burst_max; + timer_d = cfg_i.chip.t_burst_max; if (tf_q.write) begin state_d = Write; trx_tx_data_oe = 1'b1; @@ -367,25 +387,27 @@ module hyperbus_phy import hyperbus_pkg::*; #( end end end + // Capture read data until this segment completes or reaches its time limit. Read: begin // Dataflow handled outside FSM trx_rx_clk_set = 1'b1; 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.t_csh_cycles; + timer_d = cfg_i.chip.t_csh_cycles; state_d = WaitXfer; end end // Force-terminate access on burst time limit if (ctl_timer_one) begin - timer_d = cfg_i.t_csh_cycles; + timer_d = cfg_i.chip.t_csh_cycles; state_d = WaitXfer; end end + // Transmit write data until this segment completes or reaches its time limit. Write: begin // Drive DQ lines in write mode trx_tx_data_oe = 1'b1; @@ -394,33 +416,39 @@ 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; - timer_d = cfg_i.t_csh_cycles; + timer_d = cfg_i.chip.t_csh_cycles; state_d = WaitXfer; end end // Force-terminate access on burst time limit if (ctl_timer_one) begin - timer_d = cfg_i.t_csh_cycles; + timer_d = cfg_i.chip.t_csh_cycles; state_d = WaitXfer; end end + // Keep chip select asserted until the final generated clock edge has settled. WaitXfer: begin // Wait for FFed Clock and output to stop // May have to be prolonged for potential future devices with t_CSH > 0 if (ctl_timer_zero) begin - timer_d = cfg_i.t_read_write_recovery; + timer_d = cfg_i.chip.t_read_write_recovery; state_d = WaitRWR; end end + // Enforce read/write recovery, then continue a split transfer or return idle. WaitRWR: begin trx_cs_ena = 1'b0; if (ctl_timer_rwr_done) begin 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 @@ -429,12 +457,21 @@ module hyperbus_phy import hyperbus_pkg::*; #( end end end + // Recover safely from an invalid state through the normal startup sequence. + default: begin + state_d = Startup; + timer_d = StartupCycles; + tf_d = hyper_tf_t'{burst_type: 1'b1, default: '0}; + cs_d = '0; + add_latency_d = 1'b0; + trx_cs_ena = 1'b0; + end endcase end // 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 73% rename from src/hyperbus_trx.sv rename to src/backend/hyperbus_trx.sv index cc4c79b..d0b882e 100644 --- a/src/hyperbus_trx.sv +++ b/src/backend/hyperbus_trx.sv @@ -6,19 +6,20 @@ // Armin Berger // Stephan Keck +`include "common_cells/assertions.svh" + module hyperbus_trx #( - parameter int unsigned NumChips = 2, parameter int unsigned RxFifoLogDepth = 3, parameter int unsigned SyncStages = 2 )( // 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, // Transceiver control: facing controller - input logic [NumChips-1:0] cs_i, + input logic [hyperbus_pkg::HyperNumChips-1:0] cs_i, input logic cs_ena_i, output logic rwds_sample_o, input logic rwds_sample_ena_i, @@ -36,7 +37,7 @@ module hyperbus_trx #( output logic rx_valid_o, input logic rx_ready_i, // Physical interface: facing HyperBus - output logic [NumChips-1:0] hyper_cs_no, + output logic [hyperbus_pkg::HyperNumChips-1:0] hyper_cs_no, output logic hyper_ck_o, output logic hyper_ck_no, output logic hyper_rwds_o, @@ -48,37 +49,19 @@ 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 - - // Intermediate RX signals for RWDS domain - logic rx_rwds_clk_ena; - logic rx_rwds_clk_orig; - logic rx_rwds_clk; - logic rx_rwds_soft_rst; - logic [15:0] rx_rwds_fifo_in; - logic rx_rwds_fifo_valid; - logic rx_rwds_fifo_ready; - // Feed through async reset assign hyper_reset_no = rst_ni; - // ================= - // TX + control - // ================= + ////////////////// + // TX and control // + ////////////////// - // Shift clock by 90 degrees - assign tx_clk_90 = clk_i_90; + logic tx_clk_ena_q; - // 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 +69,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 +100,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; @@ -128,19 +111,29 @@ module hyperbus_trx #( end end - // ======== - // RX - // ======== + ///////////////////// + // RX data capture // + ///////////////////// + + logic rx_rwds_90; + logic rx_rwds_clk_ena; + logic rx_rwds_clk_orig; + logic rx_rwds_clk; + logic rx_rwds_clk_n; + logic rx_capture_rst; + logic [15:0] rx_rwds_fifo_in; + logic rx_rwds_fifo_valid; + logic rx_rwds_fifo_ready; // Sample RWDS for extra latency determination. always_ff @(posedge clk_i or negedge rst_ni) begin : proc_ff_rwds_sample - 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 +141,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 ), @@ -172,14 +165,14 @@ module hyperbus_trx #( .clk_o ( rx_rwds_clk_orig ) ); - // Reset RX state on async reset or on gated clock (whenever inactive) - // TODO: is this safe? Replace with tech cells? - assign rx_rwds_soft_rst = ~rst_ni | (~rx_rwds_clk_ena & ~test_mode_i); + // Reset RX state on async reset or on gated clock (whenever inactive) + // TODO: is this safe? Replace with tech cells? + assign rx_capture_rst = !rst_ni || (!rx_rwds_clk_ena && !test_mode_i); // RX data is valid one cycle after each RX soft reset - 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 +190,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 +220,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 da287fa..0000000 --- a/src/hyperbus.sv +++ /dev/null @@ -1,314 +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 RegAddrWidth = -1, - parameter int unsigned RegDataWidth = -1, - parameter int unsigned MinFreqMHz = 100, - 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 logic [RegDataWidth-1:0] RstChipBase = 'h0, // Base address for all chips - parameter logic [RegDataWidth-1:0] RstChipSpace = 'h1_0000, // 64 KiB: Current maximum HyperBus device size - parameter hyperbus_pkg::hyper_cfg_t RstCfg = hyperbus_pkg::gen_RstCfg(NumPhys,MinFreqMHz), - 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::hyper_cfg_t 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 ), - .RegAddrWidth ( RegAddrWidth ), - .RegDataWidth ( RegDataWidth ), - .reg_req_t ( reg_req_t ), - .reg_rsp_t ( reg_rsp_t ), - .rule_t ( axi_rule_t ), - .RstChipBase ( RstChipBase ), - .RstChipSpace ( RstChipSpace ), - .RstCfg ( RstCfg ) - ) 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 ), - .cfg_o ( cfg ), - .chip_rules_o ( chip_rules ), - .trans_active_i ( trans_active ) - ); - - // 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 ), - - .chip_rules_i ( chip_rules ), - .which_phy_i ( cfg.which_phy ), - .phys_in_use_i ( cfg.phys_in_use ), - .addr_mask_msb_i ( cfg.address_mask_msb ), - .addr_space_i ( cfg.address_space ), - .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 ( 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..e2644c2 --- /dev/null +++ b/src/hyperbus_async_bridge.sv @@ -0,0 +1,186 @@ +// 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 +); + + //////////////////////////// + // Configuration crossing // + //////////////////////////// + + 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 ) + ); + + ////////////////////// + // Command crossing // + ////////////////////// + + 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 ) + ); + + ///////////////////////////// + // Write-response crossing // + ///////////////////////////// + + 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 ) + ); + + /////////////////////// + // TX data crossing // + /////////////////////// + + 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 ) + ); + + /////////////////////// + // RX data crossing // + /////////////////////// + + 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..3069b4e --- /dev/null +++ b/src/hyperbus_asynchronous.sv @@ -0,0 +1,279 @@ +// 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 NumPhys = 2, + parameter int unsigned AxiAddrWidth = -1, + parameter int unsigned AxiDataWidth = -1, + parameter int unsigned AxiIdWidth = -1, + parameter int unsigned AxiUserWidth = -1, + parameter type axi_req_t = logic, + parameter type axi_rsp_t = logic, + parameter int unsigned RegDataWidth = -1, + parameter type reg_req_t = logic, + parameter type reg_rsp_t = logic, + parameter type axi_rule_t = logic, + parameter int unsigned HostCommandDepth = 8, + parameter int unsigned HostWriteBufferBytes = 128, + parameter int unsigned RxFifoLogDepth = 3, + parameter int unsigned TxFifoLogDepth = 3, + parameter int unsigned PhyStartupCycles = 300 * 200, + parameter int unsigned SyncStages = 2, + parameter int unsigned CdcSyncStages = 3 +) ( + input logic clk_sys_i, + input logic rst_sys_ni, + 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][hyperbus_pkg::HyperNumChips-1:0] hyper_cs_no, + output logic [NumPhys-1:0] hyper_ck_o, + output logic [NumPhys-1:0] hyper_ck_no, + output logic [NumPhys-1:0] hyper_rwds_o, + 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) + + ///////////////////// + // Clock and reset // + ///////////////////// + + logic clk_backend; + logic rst_backend_n; + logic rst_phy_async_n; + + //////////////////////// + // Configuration path // + //////////////////////// + + 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 [hyperbus_pkg::HyperNumChips-1:0] frontend_chip_rules; + + //////////////////// + // Dataflow links // + //////////////////// + + 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; + + // 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 ( ) + ); + + //////////////////////////// + // Configuration frontend // + //////////////////////////// + + hyperbus_cfg_frontend #( + .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 ) + ); + + ////////////////// + // AXI frontend // + ////////////////// + + 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 ) + ); + + //////////// + // Midend // + //////////// + + hyperbus_midend #( + .HostAddrWidth ( AxiAddrWidth ), + .HostDataWidth ( AxiDataWidth ), + .NumPhys ( NumPhys ), + .HostCommandDepth ( HostCommandDepth ), + .HostWriteBufferBytes ( HostWriteBufferBytes ), + .host_cmd_t ( host_cmd_t ), + .host_w_t ( host_w_t ), + .host_r_t ( host_r_t ), + .host_wrsp_t ( host_wrsp_t ), + .host_req_t ( host_req_t ), + .host_rsp_t ( host_rsp_t ), + .hyper_rx_t ( hyper_rx_t ), + .hyper_tx_t ( hyper_tx_t ), + .hyper_cmd_t ( hyper_cmd_t ), + .hyper_req_t ( hyper_req_t ), + .hyper_rsp_t ( hyper_rsp_t ), + .rule_t ( axi_rule_t ) + ) i_midend ( + .clk_i ( clk_sys_i ), + .rst_ni ( rst_sys_ni ), + .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 ) + ); + + ///////////////////////// + // Asynchronous bridge // + ///////////////////////// + + 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 ) + ); + + ///////////// + // Backend // + ///////////// + + hyperbus_backend #( + .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..b301199 --- /dev/null +++ b/src/hyperbus_atomic_handler.sv @@ -0,0 +1,369 @@ +// 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" + +/// Execute one atomic request as an exclusive backend read-modify-write sequence. +module hyperbus_atomic_handler #( + parameter int unsigned HostAddrWidth = -1, + parameter int unsigned HostDataWidth = -1, + 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; + + ////////////////////// + // Persistent state // + ////////////////////// + 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; + + //////////////////////// + // Request sequencing // + //////////////////////// + atomic_request_t request_in; + logic operation_valid; + hyperbus_pkg::hyper_resp_e initial_response, backend_write_response; + logic atomic_started, operand_accepted; + logic valid_operand_accepted, invalid_operand_completed; + logic command_accepted; + logic backend_read_accepted, backend_read_succeeded, backend_read_failed; + logic backend_write_accepted, backend_write_rsp_accepted; + logic host_read_accepted, host_write_rsp_accepted; + + assign request_in = '{ + addr: request_i.addr, + size: request_i.size, + atomic_op: request_i.atomic_op, + atomic_return: request_i.atomic_return + }; + + // A start captures the request even when validation failed. In that case the + // handler only drains its write data and returns an atomic error. + assign atomic_started = (state_q == Idle) && start_i; + assign operation_valid = response_q == hyperbus_pkg::HyperRespOkay; + assign initial_response = request_valid_i ? hyperbus_pkg::HyperRespOkay : + hyperbus_pkg::HyperRespAtomicError; + assign operand_accepted = host_w_valid_i && host_w_ready_o; + assign valid_operand_accepted = operand_accepted && operation_valid; + assign invalid_operand_completed = operand_accepted && !operation_valid && host_w_i.last; + + // These events advance the backend read-modify-write sequence. + // Read errors skip the write, write errors are reported after the write response. + assign command_accepted = command_valid_o && command_ready_i; + assign backend_read_accepted = read_valid_i && read_ready_o; + assign backend_read_succeeded = backend_read_accepted && + (read_i.resp == hyperbus_pkg::HyperRespOkay); + assign backend_read_failed = backend_read_accepted && + (read_i.resp != hyperbus_pkg::HyperRespOkay); + assign backend_write_accepted = write_valid_o && write_ready_i; + assign backend_write_rsp_accepted = write_rsp_valid_i && write_rsp_ready_o; + assign backend_write_response = write_rsp_error_i ? + hyperbus_pkg::HyperRespAccessError : hyperbus_pkg::HyperRespOkay; + + // Hold both host responses until independently accepted. + // Completion is asserted in the cycle where the final pending response is accepted. + assign host_read_accepted = host_r_valid_o && host_r_ready_i; + assign host_write_rsp_accepted = host_wrsp_valid_o && host_wrsp_ready_i; + assign completed_o = (state_q == ReturnResponse) && + (!read_pending_q || host_read_accepted) && + (!write_pending_q || host_write_rsp_accepted); + + assign active_o = state_q != Idle; + assign command_valid_o = (state_q == ReadCommand) || (state_q == WriteCommand); + + ////////////////////// + // Backend commands // + ////////////////////// + // Atomic commands are converted into one backend read followed by one write. + always_comb begin : proc_command + command_o = '0; + command_o.write = state_q == WriteCommand; + command_o.addr = request_q.addr; + command_o.beats = 1; + command_o.size = ((request_q.atomic_op == hyperbus_pkg::HyperAtomicCompare) && + (request_q.size != '0)) ? + request_q.size - 1'b1 : request_q.size; + command_o.burst = hyperbus_pkg::HyperBurstIncr; + command_o.atomic_op = hyperbus_pkg::HyperAtomicNone; + command_o.atomic_return = 1'b0; + command_o.ordered = 1'b1; + end + + //////////////// + // Atomic ALU // + //////////////// + // The operation works on one contiguous byte-lane range within the host data word. + host_data_t value_mask, write_mask; + host_data_t alu_operand_a, alu_operand_b; + host_data_t compare_value, swap_value; + host_data_t alu_result, shifted_alu_result; + host_data_t preserved_data, write_data; + int unsigned byte_offset, operand_bytes, operand_bits; + logic alu_enable, alu_equal; + logic alu_signed_less, alu_unsigned_less; + + // Select and align the memory value and write operand before the ALU. + always_comb begin : proc_alu_operands + byte_offset = 0; + operand_bytes = 1; + operand_bits = 8; + value_mask = '1; + + 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; + value_mask = value_mask >> (HostDataWidth - operand_bits); + end + + alu_operand_a = (read_data_q >> (byte_offset * 8)) & value_mask; + // Compare operations carry the compare value below the replacement value. + compare_value = (operand_q >> (byte_offset * 8)) & value_mask; + swap_value = (operand_q >> ((byte_offset * 8) + operand_bits)) & value_mask; + alu_operand_b = (request_q.atomic_op == hyperbus_pkg::HyperAtomicCompare) ? + swap_value : compare_value; + end + + assign alu_enable = (state_q == WriteData) && + (response_q == hyperbus_pkg::HyperRespOkay); + assign alu_equal = alu_operand_a == compare_value; + assign alu_unsigned_less = alu_operand_a < alu_operand_b; + assign alu_signed_less = (alu_operand_a[operand_bits-1] != + alu_operand_b[operand_bits-1]) ? + alu_operand_a[operand_bits-1] : alu_unsigned_less; + + // Apply the selected operation to the aligned operands. + always_comb begin : proc_alu + alu_result = alu_operand_a; + if (alu_enable) begin + unique case (request_q.atomic_op) + hyperbus_pkg::HyperAtomicSwap: alu_result = alu_operand_b; + hyperbus_pkg::HyperAtomicCompare: alu_result = alu_equal ? + alu_operand_b : alu_operand_a; + hyperbus_pkg::HyperAtomicAdd: alu_result = alu_operand_a + alu_operand_b; + hyperbus_pkg::HyperAtomicAnd: alu_result = alu_operand_a & alu_operand_b; + hyperbus_pkg::HyperAtomicClear: alu_result = alu_operand_a & ~alu_operand_b; + hyperbus_pkg::HyperAtomicXor: alu_result = alu_operand_a ^ alu_operand_b; + hyperbus_pkg::HyperAtomicSet: alu_result = alu_operand_a | alu_operand_b; + hyperbus_pkg::HyperAtomicSignedMax: alu_result = alu_signed_less ? + alu_operand_b : alu_operand_a; + hyperbus_pkg::HyperAtomicSignedMin: alu_result = alu_signed_less ? + alu_operand_a : alu_operand_b; + hyperbus_pkg::HyperAtomicUnsignedMax: alu_result = alu_unsigned_less ? + alu_operand_b : alu_operand_a; + hyperbus_pkg::HyperAtomicUnsignedMin: alu_result = alu_unsigned_less ? + alu_operand_a : alu_operand_b; + default:; + endcase + end + end + + assign write_mask = value_mask << (byte_offset * 8); + assign preserved_data = read_data_q & ~write_mask; + assign shifted_alu_result = (alu_result & value_mask) << (byte_offset * 8); + assign write_data = alu_enable ? preserved_data | shifted_alu_result : read_data_q; + + //////////////////// + // Stream outputs // + //////////////////// + always_comb begin : proc_outputs + host_w_ready_o = state_q == WaitWriteData; + + host_r_o = '0; + host_r_o.data = read_data_q; + host_r_o.resp = response_q; + host_r_o.last = 1'b1; + host_r_o.atomic_ok = response_q == hyperbus_pkg::HyperRespOkay; + host_r_valid_o = (state_q == ReturnResponse) && read_pending_q; + + host_wrsp_o = '0; + host_wrsp_o.resp = response_q; + host_wrsp_o.atomic_ok = response_q == hyperbus_pkg::HyperRespOkay; + host_wrsp_valid_o = (state_q == ReturnResponse) && write_pending_q; + + read_ready_o = state_q == ReadData; + + write_o = '0; + write_o.data = write_data; + write_o.last = 1'b1; + 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 + + /////////////////////////// + // Control state machine // + /////////////////////////// + // A valid request consumes one operand, reads the old value, writes the ALU result, and + // returns both responses. Invalid requests drain their operands; backend errors return early. + always_comb begin : proc_state + state_d = state_q; + request_d = request_q; + 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) + // Capture one validated or rejected atomic request. + Idle: begin + if (atomic_started) begin + request_d = request_in; + response_d = initial_response; + state_d = WaitWriteData; + end + end + // Buffer the operand, or drain an invalid request through its final beat. + WaitWriteData: begin + if (operand_accepted) begin + operand_d = host_w_i.data; + end + if (valid_operand_accepted) begin + state_d = ReadCommand; + end + if (invalid_operand_completed) begin + read_pending_d = request_q.atomic_return; + write_pending_d = 1'b1; + state_d = ReturnResponse; + end + end + // Issue the backend read for the target word. + ReadCommand: begin + if (command_accepted) begin + state_d = ReadData; + end + end + // Capture the old value and stop early on a backend read error. + ReadData: begin + if (backend_read_accepted) begin + read_data_d = read_i.data; + end + if (backend_read_succeeded) begin + state_d = WriteCommand; + end + if (backend_read_failed) begin + response_d = read_i.resp; + read_pending_d = request_q.atomic_return; + write_pending_d = 1'b1; + state_d = ReturnResponse; + end + end + // Issue the backend write for the updated word. + WriteCommand: begin + if (command_accepted) begin + state_d = WriteData; + end + end + // Transfer the ALU result and byte strobes. + WriteData: begin + if (backend_write_accepted) begin + state_d = WriteResponse; + end + end + // Collect the backend write status. + WriteResponse: begin + if (backend_write_rsp_accepted) begin + response_d = backend_write_response; + read_pending_d = request_q.atomic_return; + write_pending_d = 1'b1; + state_d = ReturnResponse; + end + end + // Hold the host read and write responses until independently accepted. + ReturnResponse: begin + if (host_read_accepted) begin + read_pending_d = 1'b0; + end + if (host_write_rsp_accepted) begin + write_pending_d = 1'b0; + end + if (completed_o) begin + state_d = Idle; + end + end + default: state_d = Idle; + endcase + end + + ///////////////////// + // State registers // + ///////////////////// + `FFARN(state_q, state_d, Idle, clk_i, rst_ni) + `FFARN(request_q, request_d, '0, clk_i, rst_ni) + `FFARN(operand_q, operand_d, '0, clk_i, rst_ni) + `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 deleted file mode 100644 index 78b3c7d..0000000 --- a/src/hyperbus_axi.sv +++ /dev/null @@ -1,556 +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/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 -) ( - 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 rule_t [NumChips-1:0] chip_rules_i, - input logic phys_in_use_i, - input logic which_phy_i, - input logic [4:0] addr_mask_msb_i, - input logic addr_space_i, - output logic trans_active_o -); - - 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); - - 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; - - `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]) - - // No need to track ID: serializer buffers it for us - typedef struct packed { - axi_addr_t addr; - axi_pkg::len_t len; - axi_pkg::burst_t burst; - axi_pkg::size_t size; - } axi_ax_t; - - typedef struct packed { - 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_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_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; - - // 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; - - logic [1:0] phys_in_use; - - assign phys_in_use = (NumPhys==2) ? (phys_in_use_i + 1) : 1; - - // ============================ - // Serialize requests - // ============================ - - axi_fifo #( - .Depth ( 8 ), - .FallThrough ( 1'b0 ), - .aw_chan_t ( axi_fifo_aw_chan_t ), - .w_chan_t ( axi_fifo_w_chan_t ), - .b_chan_t ( axi_fifo_b_chan_t ), - .ar_chan_t ( axi_fifo_ar_chan_t ), - .r_chan_t ( axi_fifo_r_chan_t ), - .axi_req_t ( axi_req_t ), - .axi_resp_t ( axi_rsp_t ) - ) i_axi_fifo ( - .clk_i, - .rst_ni, - .test_i ( 1'b0 ), - .slv_req_i ( 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 ) - ); - - // 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 ) - ) 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 ) - ); - - // 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_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; - - rr_arb_tree #( - .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 ) - ); - - // 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; - - 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 = addr_space_i; - assign trans_o.address = (NumPhys == 2) ? - (phys_in_use_i ? - ((rr_out_req_ax.addr & ((32'b1 << addr_mask_msb_i) - 1)) >> 2) : - (((rr_out_req_ax.addr & ((32'b1 << addr_mask_msb_i) - 1)) >> 2) << 1)) : - ((rr_out_req_ax.addr & ((32'b1 << addr_mask_msb_i) - 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) & (~phys_in_use_i) ) begin - if(rx_valid_i & s_rx_ready) begin - merge_r_d = merge_r_q + 1; - end - if(~which_phy_i) - 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(~which_phy_i) - 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 ( - .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 ) - ); - - `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) - - // ========================================================= - // W channel: Buffer. Cuts path and upsamples when needed - // ========================================================= - - assign w_data_fifo_in.data = ser_out_req.w.data; - assign w_data_fifo_in.strb = ser_out_req.w.strb; - assign w_data_fifo_in.last = ser_out_req.w.last; - assign w_data_fifo_in.user = ser_out_req.w.user; - - stream_fifo #( - .FALL_THROUGH ( 1'b0 ), - .T ( axi_w_chan_t ), - .DEPTH ( 16 ) - ) wchan_stream_fifo ( - .clk_i, - .rst_ni, - .flush_i ( 1'b0 ), - .testmode_i ( 1'b0 ), - .usage_o ( ), - .data_i ( w_data_fifo_in ), - .valid_i ( ser_out_req.w_valid ), - .ready_o ( ser_out_rsp.w_ready ), - .data_o ( w_data_fifo ), - .valid_o ( w_data_valid ), - .ready_i ( w_data_ready ) - ); - - 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) & (~phys_in_use_i) ) 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 - - // ========================= - // 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; - 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 - -endmodule diff --git a/src/hyperbus_axi_frontend.sv b/src/hyperbus_axi_frontend.sv new file mode 100644 index 0000000..1fcff28 --- /dev/null +++ b/src/hyperbus_axi_frontend.sv @@ -0,0 +1,339 @@ +// 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 "axi/typedef.svh" +`include "common_cells/registers.svh" +`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, + + 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 +); + + `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; + + // 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_pkg::atop_t atop; + } axi_ax_t; + + typedef struct packed { + axi_ax_t ax_data; + logic write; + } ax_channel_t; + + /////////////////////////// + // Serialized AXI stream // + /////////////////////////// + + axi_req_t ser_in_req; + axi_rsp_t ser_in_rsp; + axi_req_t ser_out_req; + axi_rsp_t ser_out_rsp; + + axi_ax_t ser_out_req_aw; + axi_ax_t ser_out_req_ar; + ax_channel_t ser_out_req_aw_channel; + ax_channel_t ser_out_req_ar_channel; + ax_channel_t arbitrated_ax; + ax_channel_t selected_ax; + + logic ax_arb_valid; + logic ax_arb_ready; + + ////////////////////// + // Drain accounting // + ////////////////////// + + localparam int unsigned PendingWidth = 8; + typedef logic [PendingWidth-1:0] pending_cnt_t; + typedef logic signed [PendingWidth-1:0] write_balance_t; + + pending_cnt_t read_pending_d, read_pending_q; + pending_cnt_t write_pending_d, write_pending_q; + write_balance_t write_balance_d, write_balance_q; + 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 + + ser_in_req = axi_req_i; + ser_in_req.ar_valid = axi_req_i.ar_valid && !drain_i; + ser_in_req.aw_valid = axi_req_i.aw_valid && allow_aw; + ser_in_req.w_valid = axi_req_i.w_valid && allow_w; + + axi_rsp_o = ser_in_rsp; + axi_rsp_o.ar_ready = ser_in_rsp.ar_ready && !drain_i; + axi_rsp_o.aw_ready = ser_in_rsp.aw_ready && allow_aw; + axi_rsp_o.w_ready = ser_in_rsp.w_ready && allow_w; + end + + assign axi_ar_accepted = axi_req_i.ar_valid && axi_rsp_o.ar_ready; + 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; + + ///////////////////////// + // Command arbitration // + ///////////////////////// + + axi_serializer #( + .MaxReadTxns ( 4 ), + .MaxWriteTxns ( 4 ), + .AxiIdWidth ( AxiIdWidth ), + .axi_req_t ( axi_req_t ), + .axi_resp_t ( axi_rsp_t ) + ) i_axi_serializer ( + .clk_i, + .rst_ni, + .slv_req_i ( ser_in_req ), + .slv_resp_o ( ser_in_rsp ), + .mst_req_o ( ser_out_req ), + .mst_resp_i ( ser_out_rsp ) + ); + + 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; + + assign ser_out_req_ar_channel = '{ax_data: ser_out_req_ar, write: 1'b0}; + assign ser_out_req_aw_channel = '{ax_data: ser_out_req_aw, write: 1'b1}; + + rr_arb_tree #( + .NumIn ( 2 ), + .DataType ( ax_channel_t ), + .AxiVldRdy ( 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_channel, ser_out_req_ar_channel} ), + .req_o ( ax_arb_valid ), + .gnt_i ( ax_arb_ready ), + .data_o ( arbitrated_ax ), + .idx_o ( ) + ); + + stream_register #( + .T ( ax_channel_t ) + ) i_ax_register ( + .clk_i, + .rst_ni, + .clr_i ( 1'b0 ), + .testmode_i ( 1'b0 ), + .valid_i ( ax_arb_valid ), + .ready_o ( ax_arb_ready ), + .data_i ( arbitrated_ax ), + .valid_o ( host_req_o.cmd_valid ), + .ready_i ( host_rsp_i.cmd_ready ), + .data_o ( selected_ax ) + ); + + ////////////////////////// + // Host command mapping // + ////////////////////////// + + assign host_req_o.cmd.write = selected_ax.write; + assign host_req_o.cmd.addr = selected_ax.ax_data.addr; + assign host_req_o.cmd.beats = hyperbus_pkg::hyper_blen_t'(selected_ax.ax_data.len) + + hyperbus_pkg::hyper_blen_t'(1); + assign host_req_o.cmd.size = selected_ax.ax_data.size; + assign host_req_o.cmd.burst = (selected_ax.ax_data.burst == axi_pkg::BURST_FIXED) ? + hyperbus_pkg::HyperBurstFixed : + hyperbus_pkg::HyperBurstIncr; + + logic atomic_arithmetic_class; + logic atomic_endian_sensitive; + logic atomic_big_endian_unsupported; + + assign atomic_arithmetic_class = + (selected_ax.ax_data.atop[5:4] == axi_pkg::ATOP_ATOMICSTORE) || + (selected_ax.ax_data.atop[5:4] == axi_pkg::ATOP_ATOMICLOAD); + assign atomic_endian_sensitive = + (selected_ax.ax_data.atop[2:0] == axi_pkg::ATOP_ADD) || + (selected_ax.ax_data.atop[2:0] >= axi_pkg::ATOP_SMAX); + assign atomic_big_endian_unsupported = + selected_ax.ax_data.atop[3] && atomic_endian_sensitive; + + always_comb begin : proc_atomic_decode + host_req_o.cmd.atomic_op = (selected_ax.ax_data.atop == '0) ? + hyperbus_pkg::HyperAtomicNone : + hyperbus_pkg::HyperAtomicInvalid; + unique case (selected_ax.ax_data.atop) + axi_pkg::ATOP_ATOMICSWAP: + host_req_o.cmd.atomic_op = hyperbus_pkg::HyperAtomicSwap; + axi_pkg::ATOP_ATOMICCMP: + host_req_o.cmd.atomic_op = hyperbus_pkg::HyperAtomicCompare; + default: begin + if (atomic_arithmetic_class) begin + unique case (selected_ax.ax_data.atop[2:0]) + axi_pkg::ATOP_ADD: + host_req_o.cmd.atomic_op = hyperbus_pkg::HyperAtomicAdd; + axi_pkg::ATOP_CLR: + 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 (atomic_big_endian_unsupported) begin + host_req_o.cmd.atomic_op = hyperbus_pkg::HyperAtomicInvalid; + end + end + end + endcase + host_req_o.cmd.atomic_return = selected_ax.ax_data.atop[axi_pkg::ATOP_R_RESP]; + host_req_o.cmd.ordered = selected_ax.ax_data.atop != '0; + end + + /////////////////////// + // Host write stream // + /////////////////////// + + assign host_req_o.w.data = ser_out_req.w.data; + assign host_req_o.w.strb = ser_out_req.w.strb; + assign host_req_o.w.last = ser_out_req.w.last; + assign host_req_o.w_valid = ser_out_req.w_valid; + assign ser_out_rsp.w_ready = host_rsp_i.w_ready; + + ////////////////////// + // Response mapping // + ////////////////////// + + 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 + + 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 + + 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) |-> + ((selected_ax.ax_data.burst == axi_pkg::BURST_INCR) || + ((selected_ax.ax_data.burst == axi_pkg::BURST_FIXED) && + (selected_ax.ax_data.len == '0)))) + `ASSERT(ReadResponsePending, axi_r_completed |-> (read_pending_q != '0)) + `ASSERT(WriteResponsePending, axi_b_accepted |-> (write_pending_q != '0)) + +endmodule diff --git a/src/hyperbus_cfg_frontend.sv b/src/hyperbus_cfg_frontend.sv new file mode 100644 index 0000000..e574d74 --- /dev/null +++ b/src/hyperbus_cfg_frontend.sv @@ -0,0 +1,235 @@ +// 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 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 +) ( + input logic clk_i, + input logic rst_ni, + + input reg_req_t reg_req_i, + output reg_rsp_t reg_rsp_o, + + output logic drain_o, + input logic host_idle_i, + input logic trans_active_i, + + output hyperbus_pkg::phy_cfg_t cfg_apply_o, + output logic cfg_apply_valid_o, + input logic cfg_apply_ready_i, + input logic cfg_apply_done_i, + + output logic [7:0] clock_div_apply_o, + output logic clock_div_apply_valid_o, + input logic clock_div_apply_ready_i, + input logic clock_div_apply_done_i, + + output hyperbus_pkg::frontend_cfg_t frontend_cfg_o, + output host_rule_t [hyperbus_pkg::HyperNumChips-1:0] chip_rules_o, + input logic decode_error_i +); + + typedef enum logic [2:0] { + CfgIdle, + CfgDrain, + CfgCommit, + CfgObserve, + CfgClockSend, + CfgClockWaitAck, + CfgPhySend, + CfgPhyWaitAck + } cfg_state_e; + + ////////////////////// + // Persistent state // + ////////////////////// + + cfg_state_e cfg_state_d; + cfg_state_e cfg_state_q; + hyperbus_pkg::phy_cfg_t phy_cfg; + 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(NumPhysValid, NumPhys == 1 || NumPhys == 2) + `ASSERT_INIT(RegDataWidthValid, RegDataWidth == 32) + + ////////////////////////// + // Configuration changes // + ////////////////////////// + + logic cfg_write_requested; + logic drain_completed; + logic cfg_write_accepted; + logic clock_apply_accepted; + logic phy_apply_accepted; + + assign phy_cfg_changed = phy_cfg != cfg_applied_q; + assign clock_div_changed = frontend_cfg_o.phy_clock_div != clock_div_applied_q; + assign cfg_changed = phy_cfg_changed || clock_div_changed; + assign drain_o = (cfg_state_q != CfgIdle) || cfg_changed; + assign cfg_apply_valid_o = cfg_state_q == CfgPhySend; + assign cfg_apply_o = phy_cfg; + assign clock_div_apply_valid_o = cfg_state_q == CfgClockSend; + assign clock_div_apply_o = frontend_cfg_o.phy_clock_div; + assign cfg_write_requested = reg_req_i.valid && reg_req_i.write; + assign drain_completed = host_idle_i && !trans_active_i; + assign cfg_write_accepted = cfg_reg_req.valid && cfg_reg_rsp.ready; + assign clock_apply_accepted = clock_div_apply_valid_o && clock_div_apply_ready_i; + assign phy_apply_accepted = cfg_apply_valid_o && cfg_apply_ready_i; + + //////////////////////////// + // Register access gating // + //////////////////////////// + + reg_req_t cfg_reg_req; + reg_rsp_t cfg_reg_rsp; + + always_comb begin : proc_cfg_reg_gate + cfg_reg_req = reg_req_i; + reg_rsp_o = cfg_reg_rsp; + + if (reg_req_i.write) begin + cfg_reg_req.valid = reg_req_i.valid && (cfg_state_q == CfgCommit); + if (cfg_state_q != CfgCommit) begin + reg_rsp_o.ready = 1'b0; + reg_rsp_o.error = 1'b0; + reg_rsp_o.rdata = '0; + end + end else if (cfg_state_q != CfgIdle) begin + 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 + + ///////////////////////////////// + // Atomic configuration update // + ///////////////////////////////// + + // Drain host traffic before committing a register write or applying changed + // clock and PHY values. Each destination acknowledges before traffic resumes. + always_comb begin : proc_cfg_apply + cfg_state_d = cfg_state_q; + cfg_applied_d = cfg_applied_q; + clock_div_applied_d = clock_div_applied_q; + cfg_write_pending_d = cfg_write_pending_q; + + unique case (cfg_state_q) + // Detect pending register or applied-configuration changes. + CfgIdle: begin + if (cfg_changed) begin + cfg_write_pending_d = 1'b0; + cfg_state_d = CfgDrain; + end else if (cfg_write_requested) begin + cfg_write_pending_d = 1'b1; + cfg_state_d = CfgDrain; + end + end + // Stop admission and wait until all accepted traffic has completed. + CfgDrain: begin + if (drain_completed) begin + cfg_state_d = cfg_write_pending_q ? CfgCommit : CfgObserve; + end + end + // Commit exactly one blocked register write while traffic remains drained. + CfgCommit: begin + if (cfg_write_accepted) begin + cfg_write_pending_d = 1'b0; + cfg_state_d = CfgObserve; + end + end + // Re-evaluate generated configuration outputs after the write commits. + CfgObserve: begin + if (clock_div_changed) begin + cfg_state_d = CfgClockSend; + end else if (phy_cfg_changed) begin + cfg_state_d = CfgPhySend; + end else begin + cfg_state_d = CfgIdle; + end + end + // Send a changed divider value to the isochronous clock generator. + CfgClockSend: begin + if (clock_apply_accepted && clock_div_apply_done_i) begin + clock_div_applied_d = frontend_cfg_o.phy_clock_div; + cfg_state_d = phy_cfg_changed ? CfgPhySend : CfgIdle; + end else if (clock_apply_accepted) begin + cfg_state_d = CfgClockWaitAck; + end + end + // Hold the barrier until the divider confirms the update. + CfgClockWaitAck: begin + if (clock_div_apply_done_i) begin + clock_div_applied_d = frontend_cfg_o.phy_clock_div; + cfg_state_d = phy_cfg_changed ? CfgPhySend : CfgIdle; + end + end + // Send changed PHY configuration through the selected bridge. + CfgPhySend: begin + if (phy_apply_accepted && cfg_apply_done_i) begin + cfg_applied_d = phy_cfg; + cfg_state_d = CfgIdle; + end else if (phy_apply_accepted) begin + cfg_state_d = CfgPhyWaitAck; + end + end + // Hold the barrier until the backend confirms the PHY update. + CfgPhyWaitAck: begin + if (cfg_apply_done_i) begin + cfg_applied_d = phy_cfg; + cfg_state_d = CfgIdle; + end + end + default: begin + cfg_state_d = CfgIdle; + end + endcase + end + + ///////////////////// + // State registers // + ///////////////////// + + `FFARN(cfg_state_q, cfg_state_d, CfgIdle, clk_i, rst_ni) + `FFARN(cfg_applied_q, cfg_applied_d, '0, clk_i, rst_ni) + `FFARN(clock_div_applied_q, clock_div_applied_d, '0, clk_i, rst_ni) + `FFARN(cfg_write_pending_q, cfg_write_pending_d, 1'b0, clk_i, rst_ni) + + /////////////////// + // Register bank // + /////////////////// + + hyperbus_cfg_regs #( + .NumPhys ( NumPhys ), + .RegDataWidth ( RegDataWidth ), + .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 70ac78e..c93825c 100644 --- a/src/hyperbus_cfg_regs.sv +++ b/src/hyperbus_cfg_regs.sv @@ -4,133 +4,198 @@ // // Paul Scheffler +`include "common_cells/assertions.svh" + module hyperbus_cfg_regs #( - parameter int unsigned NumChips = -1, parameter int unsigned NumPhys = -1, - parameter int unsigned RegAddrWidth = -1, parameter int unsigned RegDataWidth = -1, parameter type reg_req_t = logic, parameter type reg_rsp_t = logic, - parameter type rule_t = logic, - parameter logic [RegDataWidth-1:0] RstChipBase = -1, // Base address for all chips - parameter logic [RegDataWidth-1:0] RstChipSpace = -1, // 64 KiB: Current maximum HyperBus device size - parameter int unsigned MinFreqMHz = 100, - parameter hyperbus_pkg::hyper_cfg_t RstCfg = hyperbus_pkg::gen_RstCfg(NumPhys,MinFreqMHz) + parameter type addr_rule_t = logic ) ( - input logic clk_i, - input logic rst_ni, + input logic clk_i, + input logic rst_ni, input reg_req_t reg_req_i, output reg_rsp_t reg_rsp_o, - output hyperbus_pkg::hyper_cfg_t cfg_o, - output rule_t [NumChips-1:0] chip_rules_o, - input trans_active_i + output hyperbus_pkg::frontend_cfg_t frontend_cfg_o, + output hyperbus_pkg::phy_cfg_t phy_cfg_o, + output addr_rule_t [hyperbus_pkg::HyperNumChips-1:0] chip_rules_o, + input decode_error_i ); - `include "common_cells/registers.svh" - - // Internal Parameters - localparam int unsigned NumBaseRegs = 12; - localparam int unsigned NumRegs = 2*NumChips + NumBaseRegs; - localparam int unsigned RegsBits = cf_math_pkg::idx_width(NumRegs); - localparam int unsigned RegStrbWidth = RegDataWidth/8; - - // Data and index types - typedef logic [RegsBits-1:0] reg_idx_t; - typedef logic [RegDataWidth-1:0] reg_data_t; - - // Local signals - hyperbus_pkg::hyper_cfg_t cfg_d, cfg_q, cfg_rstval; - reg_data_t [NumChips-1:0][1:0] crange_d, crange_q, crange_rstval; - reg_idx_t sel_reg; - logic sel_reg_mapped; - reg_data_t wmask; - - assign sel_reg = reg_req_i.addr[$clog2(RegStrbWidth) +: RegsBits]; - assign sel_reg_mapped = (sel_reg < NumRegs); - - assign reg_rsp_o.ready = ~trans_active_i; // Config writeable unless currently in transfer - assign reg_rsp_o.error = ~sel_reg_mapped; - - // Read from register - always_comb begin : proc_comb_read - reg_data_t [NumRegs-1:0] rfield; - reg_rsp_o.rdata = '0; - if (sel_reg_mapped) begin - rfield = { - crange_q, - reg_data_t'(cfg_q.csn_to_ck_cycles), - reg_data_t'(cfg_q.t_csh_cycles), - reg_data_t'(cfg_q.which_phy), - reg_data_t'(cfg_q.phys_in_use), - reg_data_t'(cfg_q.address_space), - reg_data_t'(cfg_q.address_mask_msb), - reg_data_t'(cfg_q.t_tx_clk_delay), - reg_data_t'(cfg_q.t_rx_clk_delay), - reg_data_t'(cfg_q.t_read_write_recovery), - reg_data_t'(cfg_q.t_burst_max), - reg_data_t'(cfg_q.en_latency_additional), - reg_data_t'(cfg_q.t_latency_access) - }; - reg_rsp_o.rdata = rfield[sel_reg]; + localparam int unsigned NumRegs = 31; + localparam int unsigned RegsBits = cf_math_pkg::idx_width(NumRegs); + localparam int unsigned RegStrbWidth = RegDataWidth/8; + + `ASSERT_INIT(NumPhysValid, NumPhys == 1 || NumPhys == 2) + `ASSERT_INIT(RegDataWidthValid, RegDataWidth == 32) + + typedef logic [RegsBits-1:0] reg_idx_t; + typedef logic [6:0] cfg_addr_t; + typedef logic [31:0] cfg_data_t; + typedef logic [3:0] cfg_strb_t; + + typedef struct packed { + cfg_addr_t addr; + logic write; + cfg_data_t wdata; + cfg_strb_t wstrb; + logic valid; + } cfg_reg_req_t; + + typedef struct packed { + cfg_data_t rdata; + logic error; + logic ready; + } cfg_reg_rsp_t; + + typedef struct packed { + cfg_addr_t paddr; + logic [2:0] pprot; + logic psel; + logic penable; + logic pwrite; + cfg_data_t pwdata; + cfg_strb_t pstrb; + } cfg_apb_req_t; + + typedef struct packed { + logic pready; + cfg_data_t prdata; + logic pslverr; + } cfg_apb_rsp_t; + + hyperbus_cfg_regblock_pkg::hyperbus_cfg_regs__in_t cfg_hwif_in; + hyperbus_cfg_regblock_pkg::hyperbus_cfg_regs__out_t cfg_hwif_out; + addr_rule_t [hyperbus_pkg::HyperNumChips-1:0] chip_rules_all; + + reg_idx_t sel_reg; + logic sel_reg_mapped; + + cfg_reg_req_t cfg_reg_req; + cfg_reg_rsp_t cfg_reg_rsp; + cfg_apb_req_t cfg_apb_req; + cfg_apb_rsp_t cfg_apb_rsp; + + ///////////////////////////// + // Register bus adaptation // + ///////////////////////////// + + assign cfg_hwif_in.status.decode_error.hwset = decode_error_i; + + assign sel_reg = reg_req_i.addr[$clog2(RegStrbWidth) +: RegsBits]; + assign sel_reg_mapped = (sel_reg < NumRegs); + + assign reg_rsp_o.ready = !reg_req_i.valid || !sel_reg_mapped || cfg_reg_rsp.ready; + assign reg_rsp_o.error = ~sel_reg_mapped | cfg_reg_rsp.error; + assign reg_rsp_o.rdata = sel_reg_mapped ? RegDataWidth'(cfg_reg_rsp.rdata) : '0; + + assign cfg_reg_req.valid = reg_req_i.valid && sel_reg_mapped; + assign cfg_reg_req.addr = {sel_reg, 2'b00}; + assign cfg_reg_req.write = reg_req_i.write; + assign cfg_reg_req.wdata = 32'(reg_req_i.wdata); + assign cfg_reg_req.wstrb = cfg_strb_t'(reg_req_i.wstrb); + + //////////////////////// + // Chip address rules // + //////////////////////// + + logic [hyperbus_pkg::HyperNumChips-1:0][9:0] chip_base; + logic [hyperbus_pkg::HyperNumChips-1:0][9:0] chip_bound; + + assign chip_base[0] = cfg_hwif_out.chip0_base.value.value; + assign chip_base[1] = cfg_hwif_out.chip1_base.value.value; + assign chip_base[2] = cfg_hwif_out.chip2_base.value.value; + assign chip_base[3] = cfg_hwif_out.chip3_base.value.value; + assign chip_base[4] = cfg_hwif_out.chip4_base.value.value; + assign chip_base[5] = cfg_hwif_out.chip5_base.value.value; + assign chip_base[6] = cfg_hwif_out.chip6_base.value.value; + assign chip_base[7] = cfg_hwif_out.chip7_base.value.value; + + assign chip_bound[0] = cfg_hwif_out.chip0_bound.value.value; + assign chip_bound[1] = cfg_hwif_out.chip1_bound.value.value; + assign chip_bound[2] = cfg_hwif_out.chip2_bound.value.value; + assign chip_bound[3] = cfg_hwif_out.chip3_bound.value.value; + assign chip_bound[4] = cfg_hwif_out.chip4_bound.value.value; + assign chip_bound[5] = cfg_hwif_out.chip5_bound.value.value; + assign chip_bound[6] = cfg_hwif_out.chip6_bound.value.value; + assign chip_bound[7] = cfg_hwif_out.chip7_bound.value.value; + + always_comb begin : proc_chip_rules + chip_rules_all = '0; + for (int unsigned i = 0; i < hyperbus_pkg::HyperNumChips; i++) begin + chip_rules_all[i].idx = unsigned'(i); + chip_rules_all[i].start_addr = {chip_base[i], 22'b0}; + chip_rules_all[i].end_addr = {chip_bound[i], 22'b0}; end end - // Generate write mask - for (genvar i = 0; unsigned'(i) < RegStrbWidth; ++i ) begin : gen_wmask - assign wmask[8*i +: 8] = {8{reg_req_i.wstrb[i]}}; - end - - // Write to register - always_comb begin : proc_comb_write - logic chip_reg; - logic [$clog2(NumChips)-1:0] sel_chip; - cfg_d = cfg_q; - crange_d = crange_q; - if (reg_req_i.valid & reg_req_i.write & sel_reg_mapped) begin - case (sel_reg) - 'h0: cfg_d.t_latency_access = (~wmask & cfg_q.t_latency_access ) | (wmask & reg_req_i.wdata); - 'h1: cfg_d.en_latency_additional = (~wmask & cfg_q.en_latency_additional ) | (wmask & reg_req_i.wdata); - 'h2: cfg_d.t_burst_max = (~wmask & cfg_q.t_burst_max ) | (wmask & reg_req_i.wdata); - 'h3: cfg_d.t_read_write_recovery = (~wmask & cfg_q.t_read_write_recovery ) | (wmask & reg_req_i.wdata); - 'h4: cfg_d.t_rx_clk_delay = (~wmask & cfg_q.t_rx_clk_delay ) | (wmask & reg_req_i.wdata); - 'h5: cfg_d.t_tx_clk_delay = (~wmask & cfg_q.t_tx_clk_delay ) | (wmask & reg_req_i.wdata); - 'h6: cfg_d.address_mask_msb = (~wmask & cfg_q.address_mask_msb ) | (wmask & reg_req_i.wdata); - 'h7: cfg_d.address_space = (~wmask & cfg_q.address_space ) | (wmask & reg_req_i.wdata); - 'h8: cfg_d.phys_in_use = (NumPhys==1) ? 0 : ( (~wmask & cfg_q.phys_in_use ) | (wmask & reg_req_i.wdata) ); - 'h9: cfg_d.which_phy = (NumPhys==1) ? 0 : ( (~wmask & cfg_q.which_phy ) | (wmask & reg_req_i.wdata) ); - 'ha: cfg_d.t_csh_cycles = (~wmask & cfg_q.t_csh_cycles ) | (wmask & reg_req_i.wdata); - 'hb: cfg_d.csn_to_ck_cycles = (~wmask & cfg_q.csn_to_ck_cycles ) | (wmask & reg_req_i.wdata); - default: begin - {sel_chip, chip_reg} = sel_reg - NumBaseRegs; - crange_d[sel_chip][chip_reg] = (~wmask & crange_q[sel_chip][chip_reg]) | (wmask & reg_req_i.wdata); - end - endcase // sel_reg - end - end - - for (genvar i = 0; unsigned'(i) < NumChips; i++) begin : gen_crange_rstval - assign crange_rstval[i][0] = RstChipBase + (RstChipSpace * i); - assign crange_rstval[i][1] = RstChipBase + (RstChipSpace * (i+1)); // Address decoder: end noninclusive - end - - // Registers - `FFARN(cfg_q, cfg_d, RstCfg, clk_i, rst_ni); - `FFARN(crange_q, crange_d, crange_rstval, clk_i, rst_ni); - - // Outputs - assign cfg_o = cfg_q; - for (genvar i = 0; unsigned'(i) < NumChips; ++i) begin : gen_crange_out - assign chip_rules_o[i].idx = unsigned'(i); // No overlap: keep indices sequential - assign chip_rules_o[i].start_addr = crange_q[i][0]; - assign chip_rules_o[i].end_addr = crange_q[i][1]; + //////////////////////// + // Generated APB bank // + //////////////////////// + + reg_to_apb #( + .reg_req_t ( cfg_reg_req_t ), + .reg_rsp_t ( cfg_reg_rsp_t ), + .apb_req_t ( cfg_apb_req_t ), + .apb_rsp_t ( cfg_apb_rsp_t ) + ) i_reg_to_apb ( + .clk_i ( clk_i ), + .rst_ni ( rst_ni ), + .reg_req_i ( cfg_reg_req ), + .reg_rsp_o ( cfg_reg_rsp ), + .apb_req_o ( cfg_apb_req ), + .apb_rsp_i ( cfg_apb_rsp ) + ); + + hyperbus_cfg_regblock i_cfg_regblock ( + .clk ( clk_i ), + .arst_n ( rst_ni ), + .s_apb_psel ( cfg_apb_req.psel ), + .s_apb_penable ( cfg_apb_req.penable ), + .s_apb_pwrite ( cfg_apb_req.pwrite ), + .s_apb_pprot ( cfg_apb_req.pprot ), + .s_apb_paddr ( cfg_apb_req.paddr ), + .s_apb_pwdata ( cfg_apb_req.pwdata ), + .s_apb_pstrb ( cfg_apb_req.pstrb ), + .s_apb_pready ( cfg_apb_rsp.pready ), + .s_apb_prdata ( cfg_apb_rsp.prdata ), + .s_apb_pslverr ( cfg_apb_rsp.pslverr ), + .hwif_in ( cfg_hwif_in ), + .hwif_out ( cfg_hwif_out ) + ); + + /////////////////////////// + // Typed configuration // + /////////////////////////// + + always_comb begin : proc_cfg_output + frontend_cfg_o = '0; + phy_cfg_o = '0; + + frontend_cfg_o.address_mask_msb = cfg_hwif_out.address_mask_msb.value.value; + frontend_cfg_o.address_space = cfg_hwif_out.address_space.value.value; + frontend_cfg_o.dual_phy = (NumPhys == 2) && cfg_hwif_out.dual_phy.value.value; + frontend_cfg_o.phy_clock_div = cfg_hwif_out.phy_clock_div.value.value; + + phy_cfg_o.chip.t_latency_access = cfg_hwif_out.t_latency_access.value.value; + phy_cfg_o.chip.en_latency_additional = cfg_hwif_out.en_latency_additional.value.value; + phy_cfg_o.chip.t_burst_max = cfg_hwif_out.t_burst_max.value.value; + phy_cfg_o.chip.t_read_write_recovery = cfg_hwif_out.t_read_write_recovery.value.value; + phy_cfg_o.chip.t_rx_clk_delay = { + cfg_hwif_out.t_rx_clk_delay.coarse.value, + cfg_hwif_out.t_rx_clk_delay.fine.value + }; + phy_cfg_o.chip.t_csh_cycles = cfg_hwif_out.t_csh_cycles.value.value; + phy_cfg_o.chip.csn_to_ck_cycles = cfg_hwif_out.csn_to_ck_cycles.value.value; + phy_cfg_o.t_tx_clk_delay = { + cfg_hwif_out.t_tx_clk_delay.coarse.value, + cfg_hwif_out.t_tx_clk_delay.fine.value + }; + phy_cfg_o.dual_phy = (NumPhys == 2) && cfg_hwif_out.dual_phy.value.value; end - // pragma translate_off - `ifndef VERILATOR - initial assert (RegDataWidth >= 16 && $countones(RegDataWidth) == 1) - else $error("RegDataWidth must be a power of two bigger than 16."); - `endif - // pragma translate_on + assign chip_rules_o = chip_rules_all; endmodule : hyperbus_cfg_regs 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..c751863 --- /dev/null +++ b/src/hyperbus_iso_bridge.sv @@ -0,0 +1,158 @@ +// 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 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 +); + + //////////////////////////// + // Configuration crossing // + //////////////////////////// + + hyperbus_pkg::phy_cfg_t cfg_apply_data_q; + logic cfg_apply_accepted; + logic cfg_apply_pending_d; + logic cfg_apply_pending_q; + + assign cfg_apply_accepted = frontend_cfg_apply_valid_i && frontend_cfg_apply_ready_o; + assign frontend_cfg_apply_done_o = + cfg_apply_pending_q && frontend_cfg_apply_ready_o; + + 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) + + 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; + + ////////////////////// + // Command crossing // + ////////////////////// + + hyper_cmd_t cmd_data_q; + logic cmd_accepted; + + assign cmd_accepted = frontend_req_i.cmd_valid && frontend_rsp_o.cmd_ready; + `FFLARN(cmd_data_q, frontend_req_i.cmd, cmd_accepted, '0, clk_sys_i, rst_sys_ni) + + isochronous_4phase_handshake i_iso_cmd ( + .src_clk_i ( clk_sys_i ), + .src_rst_ni ( rst_sys_ni ), + .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; + + ///////////////////////////// + // Write-response crossing // + ///////////////////////////// + + hyper_wrsp_t wrsp_data_q; + logic wrsp_accepted; + + assign wrsp_accepted = backend_rsp_i.wrsp_valid && backend_req_o.wrsp_ready; + `FFLARN(wrsp_data_q, backend_rsp_i.wrsp, wrsp_accepted, '0, clk_phy_i, rst_phy_ni) + + isochronous_4phase_handshake i_iso_wrsp ( + .src_clk_i ( clk_phy_i ), + .src_rst_ni ( rst_phy_ni ), + .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; + + /////////////////////// + // TX data crossing // + /////////////////////// + + isochronous_spill_register #( + .T ( hyper_tx_t ) + ) i_iso_tx ( + .src_clk_i ( clk_sys_i ), + .src_rst_ni ( rst_sys_ni ), + .src_valid_i ( frontend_req_i.tx_valid ), + .src_ready_o ( frontend_rsp_o.tx_ready ), + .src_data_i ( frontend_req_i.tx ), + .dst_clk_i ( clk_phy_i ), + .dst_rst_ni ( rst_phy_ni ), + .dst_valid_o ( backend_req_o.tx_valid ), + .dst_ready_i ( backend_rsp_i.tx_ready ), + .dst_data_o ( backend_req_o.tx ) + ); + + /////////////////////// + // RX data crossing // + /////////////////////// + + isochronous_spill_register #( + .T ( hyper_rx_t ) + ) i_iso_rx ( + .src_clk_i ( clk_phy_i ), + .src_rst_ni ( rst_phy_ni ), + .src_valid_i ( backend_rsp_i.rx_valid ), + .src_ready_o ( backend_req_o.rx_ready ), + .src_data_i ( backend_rsp_i.rx ), + .dst_clk_i ( clk_sys_i ), + .dst_rst_ni ( rst_sys_ni ), + .dst_valid_o ( frontend_rsp_o.rx_valid ), + .dst_ready_i ( frontend_req_i.rx_ready ), + .dst_data_o ( frontend_rsp_o.rx ) + ); + +endmodule diff --git a/src/hyperbus_isochronous.sv b/src/hyperbus_isochronous.sv new file mode 100644 index 0000000..eeb4da8 --- /dev/null +++ b/src/hyperbus_isochronous.sv @@ -0,0 +1,322 @@ +// 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 NumPhys = 2, + parameter int unsigned AxiAddrWidth = -1, + parameter int unsigned AxiDataWidth = -1, + parameter int unsigned AxiIdWidth = -1, + parameter int unsigned AxiUserWidth = -1, + parameter type axi_req_t = logic, + parameter type axi_rsp_t = logic, + parameter int unsigned RegDataWidth = -1, + parameter type reg_req_t = logic, + parameter type reg_rsp_t = logic, + parameter type axi_rule_t = logic, + parameter int unsigned HostCommandDepth = 8, + parameter int unsigned HostWriteBufferBytes = 128, + parameter int unsigned PhyStartupCycles = 300 * 200, + parameter int unsigned SyncStages = 2 +) ( + input logic clk_sys_i, + input logic rst_sys_ni, +`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][hyperbus_pkg::HyperNumChips-1:0] hyper_cs_no, + output logic [NumPhys-1:0] hyper_ck_o, + output logic [NumPhys-1:0] hyper_ck_no, + output logic [NumPhys-1:0] hyper_rwds_o, + 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) + + ///////////////////// + // Clock and reset // + ///////////////////// + + logic clk_backend; + logic rst_backend_n; + + //////////////////////// + // Configuration path // + //////////////////////// + + 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 [hyperbus_pkg::HyperNumChips-1:0] frontend_chip_rules; + + //////////////////// + // Dataflow links // + //////////////////// + + 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; + + ////////////////////////// + // Backend clock divider // + ////////////////////////// + + 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 ( ) + ); + + //////////////////////////// + // Configuration frontend // + //////////////////////////// + + hyperbus_cfg_frontend #( + .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 ( 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 ) + ); + + ////////////////// + // AXI frontend // + ////////////////// + + 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 ) + ); + + //////////// + // Midend // + //////////// + + hyperbus_midend #( + .HostAddrWidth ( AxiAddrWidth ), + .HostDataWidth ( AxiDataWidth ), + .NumPhys ( NumPhys ), + .HostCommandDepth ( HostCommandDepth ), + .HostWriteBufferBytes ( HostWriteBufferBytes ), + .host_cmd_t ( host_cmd_t ), + .host_w_t ( host_w_t ), + .host_r_t ( host_r_t ), + .host_wrsp_t ( host_wrsp_t ), + .host_req_t ( host_req_t ), + .host_rsp_t ( host_rsp_t ), + .hyper_rx_t ( hyper_rx_t ), + .hyper_tx_t ( hyper_tx_t ), + .hyper_cmd_t ( hyper_cmd_t ), + .hyper_req_t ( hyper_req_t ), + .hyper_rsp_t ( hyper_rsp_t ), + .rule_t ( axi_rule_t ) + ) i_midend ( + .clk_i ( clk_sys_i ), + .rst_ni ( rst_sys_ni ), + .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 ) + ); + + //////////////////////// + // Isochronous bridge // + //////////////////////// + + hyperbus_iso_bridge #( + .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 ) + ); + + ///////////// + // Backend // + ///////////// + + hyperbus_backend #( + .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..8e2702f --- /dev/null +++ b/src/hyperbus_midend.sv @@ -0,0 +1,719 @@ +// 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 NumPhys = -1, + parameter int unsigned HostCommandDepth = 8, + parameter int unsigned HostWriteBufferBytes = 128, + parameter type host_cmd_t = logic, + parameter type host_w_t = logic, + parameter type host_r_t = logic, + parameter type host_wrsp_t = logic, + parameter type host_req_t = logic, + parameter type host_rsp_t = logic, + parameter type hyper_rx_t = logic, + parameter type hyper_tx_t = logic, + parameter type hyper_cmd_t = logic, + parameter type hyper_req_t = logic, + parameter type hyper_rsp_t = logic, + parameter type rule_t = logic +) ( + input logic clk_i, + input logic rst_ni, + + 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 [hyperbus_pkg::HyperNumChips-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 WriteFifoDepth = HostWriteBufferBytes / HostDataBytes; + localparam int unsigned ReadFifoDepth = 4; + localparam int unsigned WriteRspFifoDepth = 4; + localparam int unsigned ChipSelWidth = + cf_math_pkg::idx_width(hyperbus_pkg::HyperNumChips); + + `ASSERT_INIT(NumPhysValid, NumPhys == 1 || NumPhys == 2) + `ASSERT_INIT(HostAddrWidthValid, HostAddrWidth >= HostBusAddrWidth) + `ASSERT_INIT(HostDataWidthValid, + HostDataWidth >= PhyDataWidth && HostDataWidth <= 1024 && + (HostDataWidth & (HostDataWidth - 1)) == 0 && + (HostDataWidth % PhyDataWidth) == 0) + `ASSERT_INIT(HostCommandDepthValid, HostCommandDepth >= 1) + `ASSERT_INIT(HostWriteBufferSizeValid, + HostWriteBufferBytes >= HostDataBytes && + (HostWriteBufferBytes % HostDataBytes) == 0) + + typedef logic [HostAddrWidth-1:0] host_addr_t; + typedef logic [HostAddrWidth:0] host_ext_addr_t; + typedef logic [ChipSelWidth-1:0] chip_sel_idx_t; + + typedef struct packed { + hyper_cmd_t cmd; + hyperbus_pkg::hyper_host_size_t size; + logic [HostBusAddrWidth-1:0] start_addr; + hyperbus_pkg::hyper_blen_t beats; + logic write; + logic start_adapter; + } command_stage_t; + + ///////////////////////// + // Host stream buffers // + ///////////////////////// + + // Buffer the protocol-neutral host streams at the midend boundary. + host_cmd_t host_cmd_i; + logic host_req_valid_i; + logic host_req_ready_o; + 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; + + stream_fifo #( + .FALL_THROUGH ( 1'b0 ), + .DEPTH ( HostCommandDepth ), + .T ( host_cmd_t ) + ) i_host_cmd_fifo ( + .clk_i, + .rst_ni, + .flush_i ( 1'b0 ), + .testmode_i ( 1'b0 ), + .usage_o ( ), + .data_i ( host_link_req_i.cmd ), + .valid_i ( host_link_req_i.cmd_valid ), + .ready_o ( host_link_rsp_o.cmd_ready ), + .data_o ( host_cmd_i ), + .valid_o ( host_req_valid_i ), + .ready_i ( host_req_ready_o ) + ); + + stream_fifo #( + .FALL_THROUGH ( 1'b0 ), + .DEPTH ( WriteFifoDepth ), + .T ( host_w_t ) + ) i_host_w_fifo ( + .clk_i, + .rst_ni, + .flush_i ( 1'b0 ), + .testmode_i ( 1'b0 ), + .usage_o ( ), + .data_i ( host_link_req_i.w ), + .valid_i ( host_link_req_i.w_valid ), + .ready_o ( host_link_rsp_o.w_ready ), + .data_o ( host_w_i ), + .valid_o ( host_w_valid_i ), + .ready_i ( host_w_ready_o ) + ); + + stream_fifo #( + .FALL_THROUGH ( 1'b0 ), + .DEPTH ( ReadFifoDepth ), + .T ( host_r_t ) + ) i_host_r_fifo ( + .clk_i, + .rst_ni, + .flush_i ( 1'b0 ), + .testmode_i ( 1'b0 ), + .usage_o ( ), + .data_i ( host_r_o ), + .valid_i ( host_r_valid_o ), + .ready_o ( host_r_ready_i ), + .data_o ( host_link_rsp_o.r ), + .valid_o ( host_link_rsp_o.r_valid ), + .ready_i ( host_link_req_i.r_ready ) + ); + + stream_fifo #( + .FALL_THROUGH ( 1'b0 ), + .DEPTH ( WriteRspFifoDepth ), + .T ( host_wrsp_t ) + ) i_host_wrsp_fifo ( + .clk_i, + .rst_ni, + .flush_i ( 1'b0 ), + .testmode_i ( 1'b0 ), + .usage_o ( ), + .data_i ( host_wrsp_o ), + .valid_i ( host_wrsp_valid_o ), + .ready_o ( host_wrsp_ready_i ), + .data_o ( host_link_rsp_o.wrsp ), + .valid_o ( host_link_rsp_o.wrsp_valid ), + .ready_i ( host_link_req_i.wrsp_ready ) + ); + + ////////////////// + // Backend link // + ////////////////// + + hyper_rx_t rx_i; + hyper_tx_t tx_o; + hyper_cmd_t cmd_o; + logic rx_valid_i, rx_ready_o; + logic tx_valid_o, tx_ready_i; + logic wrsp_error_i, wrsp_valid_i, wrsp_ready_o; + logic cmd_valid_o, cmd_ready_i; + command_stage_t command_stage_in, command_stage_out; + logic command_stage_ready, command_stage_valid; + + assign rx_i = hyper_link_rsp_i.rx; + assign rx_valid_i = hyper_link_rsp_i.rx_valid; + 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 = command_stage_out.cmd; + assign hyper_link_req_o.cmd_valid = command_stage_valid; + + /////////////////////// + // Request selection // + /////////////////////// + + host_cmd_t cmd_req; + logic normal_req; + logic host_req_accepted; + logic command_accepted; + logic backend_command_accepted; + logic adapter_started; + logic normal_cmd_accepted; + logic normal_cmd_valid; + logic normal_req_ready; + + //////////////////// + // Address decode // + //////////////////// + + chip_sel_idx_t cmd_chip_sel_idx; + chip_sel_idx_t cmd_end_chip_sel_idx; + host_addr_t req_phy_first_addr; + host_addr_t req_phy_last_addr; + host_ext_addr_t req_phy_first_ext; + host_ext_addr_t req_phy_end_addr; + host_ext_addr_t req_last_addr; + host_ext_addr_t req_phy_bytes; + host_ext_addr_t cmd_rule_end_addr; + hyperbus_pkg::hyper_blen_t req_phy_burst; + logic cmd_dec_valid; + logic cmd_end_dec_valid; + logic req_addr_overflow; + logic req_range_valid; + logic req_decode_error; + logic atomic_range_valid; + + ////////////////////////// + // Command segmentation // + ////////////////////////// + + host_cmd_t segment_req_d, segment_req_q; + hyperbus_pkg::hyper_blen_t segment_remaining_d, segment_remaining_q; + hyperbus_pkg::hyper_blen_t cmd_remaining; + hyperbus_pkg::hyper_blen_t cmd_segment_burst; + host_ext_addr_t cmd_rule_capacity; + host_addr_t cmd_next_addr; + logic segment_pending_d, segment_pending_q; + logic segment_final_d, segment_final_q; + logic segment_wrsp_error_d, segment_wrsp_error_q; + logic cmd_segment_final; + logic normal_segment_complete; + logic normal_read_segment_complete; + logic normal_write_segment_complete; + + ////////////////// + // Atomic path // + ////////////////// + + host_cmd_t atomic_cmd; + host_w_t atomic_host_w; + host_r_t atomic_host_r; + host_wrsp_t atomic_host_wrsp; + logic atomic_req_accepted; + logic atomic_request_error; + logic atomic_cmd_valid; + logic atomic_active; + logic atomic_completed; + logic atomic_request_valid; + logic atomic_host_w_valid, atomic_host_w_ready; + logic atomic_host_r_valid; + logic atomic_host_wrsp_valid; + logic atomic_read_ready, atomic_wrsp_ready; + + /////////////////// + // Data adapters // + /////////////////// + + host_r_t converted_host_r; + host_w_t converted_host_w; + logic converted_host_r_valid, converted_host_r_ready; + logic converted_host_w_valid, converted_host_w_ready; + logic adapter_rx_last; + + ///////////////////// + // Error responses // + ///////////////////// + + typedef enum logic [1:0] { + ErrorIdle, + ErrorRead, + ErrorWrite, + ErrorWriteResp + } error_state_e; + + error_state_e error_state_d, error_state_q; + hyperbus_pkg::hyper_blen_t error_beats_d, error_beats_q; + logic error_req_accepted; + + ////////////////////////// + // Transaction lifetime // + ////////////////////////// + + logic trans_active_d, trans_active_q; + logic trans_active_set, trans_active_reset; + logic normal_transaction_completed; + + assign normal_req = host_cmd_i.atomic_op == hyperbus_pkg::HyperAtomicNone; + assign atomic_request_valid = + (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 normal_cmd_valid = host_req_valid_i && !trans_active_q && cmd_dec_valid && + req_range_valid && normal_req; + assign normal_req_ready = req_decode_error || (cmd_dec_valid && command_stage_ready); + assign cmd_valid_o = atomic_cmd_valid || segment_pending_q || normal_cmd_valid; + assign host_req_ready_o = !trans_active_q && (!normal_req || normal_req_ready); + assign host_req_accepted = host_req_valid_i && host_req_ready_o; + assign command_accepted = cmd_valid_o && command_stage_ready; + assign backend_command_accepted = command_stage_valid && cmd_ready_i; + assign normal_cmd_accepted = command_accepted && !atomic_cmd_valid; + assign adapter_started = backend_command_accepted && command_stage_out.start_adapter; + assign error_req_accepted = host_req_accepted && req_decode_error; + assign atomic_req_accepted = host_req_accepted && !normal_req; + assign atomic_request_error = atomic_req_accepted && !atomic_request_valid; + assign decode_error_o = error_req_accepted || atomic_request_error; + assign trans_active_o = trans_active_q || host_req_valid_i || host_w_valid_i || + host_link_rsp_o.r_valid || host_link_rsp_o.wrsp_valid || + command_stage_valid; + + always_comb begin : proc_cmd_cs + cmd_o.cs = '0; + 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_phy_last_addr = host_addr_t'(req_phy_end_addr - 1'b1); + req_addr_overflow = (req_last_addr > (host_ext_addr_t'(1) << HostAddrWidth)) || + (req_phy_end_addr > (host_ext_addr_t'(1) << HostAddrWidth)); + end + + addr_decode #( + .NoIndices ( hyperbus_pkg::HyperNumChips ), + .NoRules ( hyperbus_pkg::HyperNumChips ), + .addr_t ( host_addr_t ), + .rule_t ( rule_t ), + .idx_t ( chip_sel_idx_t ) + ) i_start_addr_decode ( + .addr_i ( req_phy_first_addr ), + .addr_map_i ( chip_rules_i ), + .idx_o ( cmd_chip_sel_idx ), + .dec_valid_o ( cmd_dec_valid ), + .dec_error_o ( ), + .en_default_idx_i ( 1'b0 ), + .default_idx_i ( '0 ) + ); + + addr_decode #( + .NoIndices ( hyperbus_pkg::HyperNumChips ), + .NoRules ( hyperbus_pkg::HyperNumChips ), + .addr_t ( host_addr_t ), + .rule_t ( rule_t ), + .idx_t ( chip_sel_idx_t ) + ) i_end_addr_decode ( + .addr_i ( req_phy_last_addr ), + .addr_map_i ( chip_rules_i ), + .idx_o ( cmd_end_chip_sel_idx ), + .dec_valid_o ( cmd_end_dec_valid ), + .dec_error_o ( ), + .en_default_idx_i ( 1'b0 ), + .default_idx_i ( '0 ) + ); + + // Software keeps ranges ordered and non-overlapping; transactions may cross contiguous ranges. + always_comb begin : proc_req_rule_range + cmd_rule_end_addr = req_phy_first_ext; + if (cmd_dec_valid) begin + cmd_rule_end_addr = (chip_rules_i[cmd_chip_sel_idx].end_addr == '0) ? + (host_ext_addr_t'(1) << HostAddrWidth) : + host_ext_addr_t'( + chip_rules_i[cmd_chip_sel_idx].end_addr); + end + + req_range_valid = !req_addr_overflow && cmd_dec_valid && cmd_end_dec_valid && + (cmd_end_chip_sel_idx >= cmd_chip_sel_idx); + for (int unsigned i = 0; i < hyperbus_pkg::HyperNumChips - 1; i++) begin + if ((i >= cmd_chip_sel_idx) && (i < cmd_end_chip_sel_idx) && + (chip_rules_i[i].end_addr != chip_rules_i[i+1].start_addr)) begin + req_range_valid = 1'b0; + end + end + end + + assign atomic_range_valid = req_range_valid && + (cmd_chip_sel_idx == cmd_end_chip_sel_idx); + + assign req_decode_error = normal_req && + (!cmd_dec_valid || + (!atomic_active && !segment_pending_q && !req_range_valid)); + + assign cmd_o.trans.write = cmd_req.write; + assign cmd_o.trans.burst_type = 1'b1; // Wrapping HyperBus bursts are not supported. + assign cmd_o.trans.address_space = frontend_cfg_i.address_space; + host_addr_t cmd_phy_address; + host_addr_t masked_req_address; + + always_comb begin : proc_cmd_address + masked_req_address = req_phy_first_addr & + ((host_addr_t'(1) << frontend_cfg_i.address_mask_msb) - 1); + cmd_phy_address = masked_req_address >> 1; + if (NumPhys == 2) begin + cmd_phy_address = masked_req_address >> 2; + if (!frontend_cfg_i.dual_phy) begin + cmd_phy_address = cmd_phy_address << 1; + end + end + end + + assign cmd_o.trans.address = cmd_phy_address; + + always_comb begin : proc_cmd_segment + cmd_remaining = segment_pending_q ? segment_remaining_q : req_phy_burst; + 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 command_stage_in.cmd = cmd_o; + assign command_stage_in.size = cmd_req.size; + assign command_stage_in.start_addr = cmd_req.addr[HostBusAddrWidth-1:0]; + assign command_stage_in.beats = cmd_req.beats; + assign command_stage_in.write = cmd_req.write; + assign command_stage_in.start_adapter = atomic_cmd_valid || !segment_pending_q; + + stream_register #( + .T ( command_stage_t ) + ) i_command_stage ( + .clk_i, + .rst_ni, + .clr_i ( 1'b0 ), + .testmode_i ( 1'b0 ), + .valid_i ( cmd_valid_o ), + .ready_o ( command_stage_ready ), + .data_i ( command_stage_in ), + .valid_o ( command_stage_valid ), + .ready_i ( cmd_ready_i ), + .data_o ( command_stage_out ) + ); + + assign adapter_rx_last = rx_i.last && (atomic_active || segment_final_q); + + hyperbus_read_adapter #( + .HostDataWidth ( HostDataWidth ), + .BurstLength ( hyperbus_pkg::HyperBurstWidth ), + .T ( host_r_t ), + .NumPhys ( NumPhys ) + ) i_read_adapter ( + .clk_i, + .rst_ni, + .size_i ( command_stage_out.size ), + .start_i ( adapter_started && !command_stage_out.write ), + .dual_phy_i ( frontend_cfg_i.dual_phy ), + .start_addr_i ( command_stage_out.start_addr ), + .burst_len_i ( command_stage_out.beats ), + .phy_valid_i ( rx_valid_i ), + .phy_ready_o ( rx_ready_o ), + .data_i ( rx_i.data ), + .last_i ( adapter_rx_last ), + .error_i ( rx_i.error ), + .host_valid_o ( converted_host_r_valid ), + .host_ready_i ( converted_host_r_ready ), + .data_o ( converted_host_r ) + ); + + hyperbus_write_adapter #( + .HostDataWidth ( HostDataWidth ), + .T ( host_w_t ), + .NumPhys ( NumPhys ) + ) i_write_adapter ( + .clk_i, + .rst_ni, + .size_i ( command_stage_out.size ), + .start_i ( adapter_started && command_stage_out.write ), + .dual_phy_i ( frontend_cfg_i.dual_phy ), + .start_addr_i ( command_stage_out.start_addr ), + .data_i ( converted_host_w ), + .host_valid_i ( converted_host_w_valid ), + .host_ready_o ( converted_host_w_ready ), + .data_o ( tx_o.data ), + .last_o ( tx_o.last ), + .strb_o ( tx_o.strb ), + .phy_valid_o ( tx_valid_o ), + .phy_ready_i ( tx_ready_i ) + ); + + hyperbus_atomic_handler #( + .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 ( command_stage_ready ), + .host_w_i ( host_w_i ), + .host_w_valid_i ( host_w_valid_i ), + .host_w_ready_o ( atomic_host_w_ready ), + .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_read_segment_complete = !segment_req_q.write && + rx_valid_i && rx_ready_o && rx_i.last; + assign normal_write_segment_complete = segment_req_q.write && + wrsp_valid_i && wrsp_ready_o; + assign normal_segment_complete = trans_active_q && !atomic_active && + (normal_read_segment_complete || normal_write_segment_complete); + + 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) + // Capture a rejected command and select its response stream. + ErrorIdle: begin + if (error_req_accepted) begin + error_beats_d = host_cmd_i.beats; + error_state_d = host_cmd_i.write ? ErrorWrite : ErrorRead; + end + end + // Return one decode-error beat per requested read beat. + ErrorRead: begin + if (host_r_valid_o && host_r_ready_i) begin + error_beats_d = error_beats_q - 1'b1; + if (error_beats_q == 1) begin + error_state_d = ErrorIdle; + end + end + end + // Drain all write data belonging to a rejected write command. + ErrorWrite: begin + if (host_w_valid_i && host_w_ready_o && host_w_i.last) begin + error_state_d = ErrorWriteResp; + end + end + // Return the final decode-error write response. + ErrorWriteResp: begin + if (host_wrsp_valid_o && host_wrsp_ready_i) begin + error_state_d = ErrorIdle; + 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 normal_transaction_completed = + (host_r_valid_o && host_r_ready_i && host_r_o.last) || + (host_wrsp_valid_o && host_wrsp_ready_i); + assign trans_active_reset = (atomic_active && atomic_completed) || + (!atomic_active && normal_transaction_completed); + + always_comb begin : proc_trans_active + trans_active_d = trans_active_q; + + 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, backend_command_accepted |-> + (command_stage_out.cmd.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<200 is outside the spec and is unlikely to work with any HyperBus devices - automatic hyper_cfg_t cfg = hyper_cfg_t'{ - t_latency_access: 'h6, - en_latency_additional: 'b0, - t_burst_max: ((MinFreqMhz*35)/10), // t_{csm}: At lowest legal clock (100 MHz) 3.5us (0.5us safety margin) - t_read_write_recovery: 'h6, - t_rx_clk_delay: 'h10, - t_tx_clk_delay: 'h10, - address_mask_msb: 'd25, // 2^(address mask MSB) = single chip size [bytes] - address_space: 'b0, - phys_in_use: NumPhys-1, - which_phy: NumPhys-1, - t_csh_cycles: 'h1, - csn_to_ck_cycles: 4'h0 // additional cycles from CS_N going low to start of hyper_ck - }; - - return cfg; - endfunction - endpackage diff --git a/src/hyperbus_read_adapter.sv b/src/hyperbus_read_adapter.sv new file mode 100644 index 0000000..3e7aac5 --- /dev/null +++ b/src/hyperbus_read_adapter.sv @@ -0,0 +1,253 @@ +// 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 + +`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 [1:0] { + Idle, + WaitPhy, + CollectPhy, + EmitHost + } read_adapter_state_e; + + ////////////////////// + // Persistent state // + ////////////////////// + + read_adapter_state_e state_d, state_q; + logic [BurstLength-1:0] byte_host_addr_d, byte_host_addr_q; + logic [BurstLength-1:0] byte_phy_cnt_d, byte_phy_cnt_q; + logic [BurstLength-1:0] last_addr_d, last_addr_q; + logic [3:0] size_d, size_q; + T data_buffer_d, data_buffer_q; + + ////////////////////// + // Address tracking // + ////////////////////// + + logic [WordCntWidth-1:0] word_cnt; + logic enough_data; + logic enough_data_q; + logic sent_available_data; + logic [BurstLength-1:0] next_host_addr; + logic host_last; + logic host_accepted; + logic converted_accepted; + logic [16*NumPhys-1:0] converted_data; + logic converted_last; + logic converted_error; + 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 host_accepted = host_valid_o && host_ready_i; + assign converted_accepted = converted_valid && converted_ready; + + ////////////////////// + // Host read output // + ////////////////////// + + assign data_o.data = data_buffer_q.data; + assign data_o.resp = data_buffer_q.resp; + 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_accepted) begin + byte_host_addr_d = ((byte_host_addr_q >> size_q) << size_q) + (1 << size_q); + end + if (converted_accepted) begin + byte_phy_cnt_d = byte_phy_cnt_q + NumPhys * 2; + end + end + + ///////////////////// + // Beat assembly // + ///////////////////// + + always_comb begin : proc_sample + data_buffer_d = data_buffer_q; + + if (state_q == Idle) begin + data_buffer_d.last = 1'b0; + data_buffer_d.data = '0; + data_buffer_d.resp = hyperbus_pkg::HyperRespOkay; + end else begin + if (host_accepted) begin + data_buffer_d.resp = hyperbus_pkg::HyperRespOkay; + end + if (converted_accepted) begin + data_buffer_d.data[word_cnt*(16*NumPhys) +: (16*NumPhys)] = converted_data; + if (converted_error) begin + data_buffer_d.resp = hyperbus_pkg::HyperRespAccessError; + end + data_buffer_d.last = converted_last; + end + end + end + + /////////////////////////// + // Adapter state machine // + /////////////////////////// + + always_comb begin : proc_fsm + state_d = state_q; + host_valid_o = 1'b0; + converted_ready = 1'b0; + + unique case (state_q) + // Wait for a command to initialize address tracking. + Idle: begin + if (start_i) begin + state_d = WaitPhy; + end + end + // Accept the first physical word of the host beat. + WaitPhy: begin + converted_ready = 1'b1; + if (converted_valid) begin + state_d = CollectPhy; + end + end + // Accumulate physical words until one complete host beat is available. + CollectPhy: begin + converted_ready = 1'b1; + if (enough_data) begin + state_d = EmitHost; + end + end + // Hold the assembled host beat until accepted. + EmitHost: begin + host_valid_o = enough_data_q; + converted_ready = !enough_data_q; + if (host_accepted) begin + if (data_o.last || (last_addr_q == byte_host_addr_d)) begin + state_d = Idle; + end else if (sent_available_data) begin + converted_ready = 1'b1; + state_d = converted_valid ? CollectPhy : WaitPhy; + end + end + end + default: begin + state_d = Idle; + end + endcase + end + + ////////////////////////// + // Physical-width adapter // + ////////////////////////// + + if (NumPhys == 2) begin : gen_dual_phy + logic [15:0] lower_data_d, lower_data_q; + logic lower_error_d, lower_error_q; + 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 + + ///////////////////// + // State registers // + ///////////////////// + + `FFARN(state_q, state_d, Idle, clk_i, rst_ni) + `FFARN(data_buffer_q, data_buffer_d, '0, clk_i, rst_ni) + `FFARN(byte_host_addr_q, byte_host_addr_d, '0, clk_i, rst_ni) + `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..bad36a5 --- /dev/null +++ b/src/hyperbus_synchronous.sv @@ -0,0 +1,234 @@ +// 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 NumPhys = 2, + parameter int unsigned AxiAddrWidth = -1, + parameter int unsigned AxiDataWidth = -1, + parameter int unsigned AxiIdWidth = -1, + parameter int unsigned AxiUserWidth = -1, + parameter type axi_req_t = logic, + parameter type axi_rsp_t = logic, + parameter int unsigned RegDataWidth = -1, + parameter type reg_req_t = logic, + parameter type reg_rsp_t = logic, + parameter type axi_rule_t = logic, + parameter int unsigned HostCommandDepth = 8, + parameter int unsigned HostWriteBufferBytes = 128, + parameter int unsigned PhyStartupCycles = 300 * 200, + parameter int unsigned SyncStages = 2 +) ( + input logic clk_sys_i, + input logic rst_sys_ni, +`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][hyperbus_pkg::HyperNumChips-1:0] hyper_cs_no, + output logic [NumPhys-1:0] hyper_ck_o, + output logic [NumPhys-1:0] hyper_ck_no, + output logic [NumPhys-1:0] hyper_rwds_o, + 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) + + ///////////////////// + // Clock and reset // + ///////////////////// + + logic clk_backend; + logic rst_backend_n; + + //////////////////////// + // Configuration path // + //////////////////////// + + 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 [hyperbus_pkg::HyperNumChips-1:0] frontend_chip_rules; + + //////////////////// + // Dataflow links // + //////////////////// + + 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; + + //////////////////////////// + // Configuration frontend // + //////////////////////////// + + hyperbus_cfg_frontend #( + .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 ) + ); + + ////////////////// + // AXI frontend // + ////////////////// + + 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 ) + ); + + //////////// + // Midend // + //////////// + + hyperbus_midend #( + .HostAddrWidth ( AxiAddrWidth ), + .HostDataWidth ( AxiDataWidth ), + .NumPhys ( NumPhys ), + .HostCommandDepth ( HostCommandDepth ), + .HostWriteBufferBytes ( HostWriteBufferBytes ), + .host_cmd_t ( host_cmd_t ), + .host_w_t ( host_w_t ), + .host_r_t ( host_r_t ), + .host_wrsp_t ( host_wrsp_t ), + .host_req_t ( host_req_t ), + .host_rsp_t ( host_rsp_t ), + .hyper_rx_t ( hyper_rx_t ), + .hyper_tx_t ( hyper_tx_t ), + .hyper_cmd_t ( hyper_cmd_t ), + .hyper_req_t ( hyper_req_t ), + .hyper_rsp_t ( hyper_rsp_t ), + .rule_t ( axi_rule_t ) + ) i_midend ( + .clk_i ( clk_sys_i ), + .rst_ni ( rst_sys_ni ), + .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 ) + ); + + ///////////// + // Backend // + ///////////// + + hyperbus_backend #( + .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 8b1935e..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 ), - .RegAddrWidth ( RegAddrWidth ), - .RegDataWidth ( RegDataWidth ), - .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 [1:0] { + Idle, + CollectHost, + EmitPhy + } write_adapter_state_e; + + typedef struct packed { + logic [HostDataWidth/8-1:0] strb; + logic [HostDataWidth-1:0] data; + logic last; + } write_buffer_t; + + ////////////////////// + // Persistent state // + ////////////////////// + + write_adapter_state_e state_d, state_q; + write_buffer_t data_buffer_d, data_buffer_q; + logic first_tx_d, first_tx_q; + logic [NumPhys*2-1:0] mask_strobe_d, mask_strobe_q; + logic [AddrWidth-1:0] byte_idx_d, byte_idx_q; + logic [3:0] size_d, size_q; + logic [AddrWidth-1:0] cnt_data_phy_d, cnt_data_phy_q; + + ////////////////////// + // Address tracking // + ////////////////////// + + logic upsize; + logic enough_data; + logic [WordCntWidth-1:0] word_cnt; + logic [AddrWidth-1:0] cnt_data_phy_next; + logic keep_sending; + logic [16*NumPhys-1:0] converted_data; + logic [2*NumPhys-1:0] converted_strb; + logic converted_last; + logic converted_valid; + logic converted_ready; + logic host_accepted; + logic converted_accepted; + + assign upsize = ((size_q == 1) && (NumPhys == 2)) || (size_q == 0); + assign enough_data = !upsize; + assign cnt_data_phy_next = cnt_data_phy_q + NumPhys * 2; + // Determine whether another PHY word follows without depending on downstream readiness. + assign keep_sending = (size_q > ($clog2(NumPhys) + 1)) && + (cnt_data_phy_next != byte_idx_q); + assign word_cnt = cnt_data_phy_q >> ($clog2(NumPhys) + 1); + + assign converted_data = data_buffer_q.data[(16*NumPhys)*word_cnt +: (16*NumPhys)]; + assign converted_strb = data_buffer_q.strb[(2*NumPhys)*word_cnt +: (2*NumPhys)] & + mask_strobe_q; + assign converted_last = data_buffer_q.last && (!keep_sending || upsize); + assign host_accepted = host_valid_i && host_ready_o; + assign converted_accepted = converted_valid && converted_ready; + + //////////////////////// + // Control conditions // + //////////////////////// + + logic collect_complete; + logic collect_partial_last; + logic phy_last_accepted; + logic wide_host_word_emitted; + logic wide_host_word_can_emit; + logic wide_host_word_needs_collect; + logic wide_host_word_without_data; + logic narrow_group_emitted; + assign collect_complete = host_accepted && + (enough_data || (byte_idx_d[NumPhys-1:0] == '0) || data_i.last); + assign collect_partial_last = host_accepted && !enough_data && + (byte_idx_d[NumPhys-1:0] != '0) && data_i.last; + + assign phy_last_accepted = converted_accepted && converted_last; + assign wide_host_word_emitted = converted_accepted && !converted_last && + (size_d >= NumPhys) && (cnt_data_phy_d == byte_idx_q); + assign wide_host_word_can_emit = wide_host_word_emitted && host_valid_i && enough_data; + assign wide_host_word_needs_collect = wide_host_word_emitted && + host_valid_i && !enough_data; + assign wide_host_word_without_data = wide_host_word_emitted && !host_valid_i; + assign narrow_group_emitted = converted_accepted && !converted_last && + (size_d < NumPhys) && + (cnt_data_phy_d[NumPhys-1:0] == '0); + + always_comb begin : proc_counters + byte_idx_d = byte_idx_q; + 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_accepted) begin + byte_idx_d = ((byte_idx_q >> size_d) << size_d) + (1 << size_d); + first_tx_d = 1'b0; + end + if (converted_accepted) begin + cnt_data_phy_d = cnt_data_phy_q + NumPhys * 2; + end + end + + /////////////////// + // Beat assembly // + /////////////////// + + always_comb begin : proc_sample + data_buffer_d = data_buffer_q; + + if (state_q == Idle) begin + data_buffer_d.last = 1'b0; + end else if (host_accepted) begin + if (!upsize) begin + // A full host beat remains buffered until all PHY words are emitted. + data_buffer_d.data = data_i.data; + 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 + + // Mask bytes beyond a short final host beat. + always_comb begin : proc_mask_strobe + mask_strobe_d = mask_strobe_q; + + if (state_q == Idle) begin + mask_strobe_d = '1; + end + if (collect_partial_last) begin + for (int unsigned i = 0; i < NumPhys * 2; i++) begin + mask_strobe_d[i] = i < byte_idx_d[NumPhys-1:0]; + end + end + end + + /////////////////////////// + // Adapter state machine // + /////////////////////////// + + always_comb begin : proc_fsm + state_d = state_q; + host_ready_o = 1'b0; + converted_valid = 1'b0; + + unique case (state_q) + // Wait for a command to initialize address tracking. + Idle: begin + if (start_i) begin + state_d = CollectHost; + end + end + // Gather enough host bytes to form the next physical word. + CollectHost: begin + host_ready_o = 1'b1; + if (collect_complete) begin + state_d = EmitPhy; + end + end + // Emit physical words and return for more host data when needed. + EmitPhy: begin + converted_valid = 1'b1; + if (phy_last_accepted && start_i) begin + state_d = CollectHost; + end else if (phy_last_accepted) begin + state_d = Idle; + end else if (wide_host_word_can_emit) begin + host_ready_o = 1'b1; + state_d = EmitPhy; + end else if (wide_host_word_needs_collect) begin + host_ready_o = 1'b1; + state_d = CollectHost; + end else if (wide_host_word_without_data) begin + state_d = CollectHost; + end else if (narrow_group_emitted) begin + host_ready_o = !upsize; + state_d = CollectHost; + end + end + default: begin + state_d = Idle; + end + endcase + end + + ////////////////////////// + // Physical-width adapter // + ////////////////////////// + + 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 + + ///////////////////// + // State registers // + ///////////////////// + + `FFARN(state_q, state_d, Idle, clk_i, rst_ni) + `FFARN(data_buffer_q, data_buffer_d, '0, clk_i, rst_ni) + `FFARN(byte_idx_q, byte_idx_d, '0, clk_i, rst_ni) + `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/src/regs/hyperbus_cfg_regblock.sv b/src/regs/hyperbus_cfg_regblock.sv new file mode 100644 index 0000000..cdc9c01 --- /dev/null +++ b/src/regs/hyperbus_cfg_regblock.sv @@ -0,0 +1,1382 @@ +// Generated by PeakRDL-regblock - A free and open-source SystemVerilog generator +// https://github.com/SystemRDL/PeakRDL-regblock + +module hyperbus_cfg_regblock ( + input wire clk, + input wire arst_n, + + input wire s_apb_psel, + input wire s_apb_penable, + input wire s_apb_pwrite, + input wire [2:0] s_apb_pprot, + input wire [6:0] s_apb_paddr, + input wire [31:0] s_apb_pwdata, + input wire [3:0] s_apb_pstrb, + output logic s_apb_pready, + output logic [31:0] s_apb_prdata, + output logic s_apb_pslverr, + + input hyperbus_cfg_regblock_pkg::hyperbus_cfg_regs__in_t hwif_in, + output hyperbus_cfg_regblock_pkg::hyperbus_cfg_regs__out_t hwif_out + ); + + //-------------------------------------------------------------------------- + // CPU Bus interface logic + //-------------------------------------------------------------------------- + logic cpuif_req; + logic cpuif_req_is_wr; + logic [6:0] cpuif_addr; + logic [31:0] cpuif_wr_data; + logic [31:0] cpuif_wr_biten; + logic cpuif_req_stall_wr; + logic cpuif_req_stall_rd; + + logic cpuif_rd_ack; + logic cpuif_rd_err; + logic [31:0] cpuif_rd_data; + + logic cpuif_wr_ack; + logic cpuif_wr_err; + + // Request + logic is_active; + always_ff @(posedge clk or negedge arst_n) begin + if(~arst_n) begin + is_active <= '0; + cpuif_req <= '0; + cpuif_req_is_wr <= '0; + cpuif_addr <= '0; + cpuif_wr_data <= '0; + cpuif_wr_biten <= '0; + end else begin + if(~is_active) begin + if(s_apb_psel) begin + is_active <= '1; + cpuif_req <= '1; + cpuif_req_is_wr <= s_apb_pwrite; + cpuif_addr <= {s_apb_paddr[6:2], 2'b0}; + cpuif_wr_data <= s_apb_pwdata; + for(int i=0; i<4; i++) begin + cpuif_wr_biten[i*8 +: 8] <= {8{s_apb_pstrb[i]}}; + end + end + end else begin + cpuif_req <= '0; + if(cpuif_rd_ack || cpuif_wr_ack) begin + is_active <= '0; + end + end + end + end + + // Response + assign s_apb_pready = cpuif_rd_ack | cpuif_wr_ack; + assign s_apb_prdata = cpuif_rd_data; + assign s_apb_pslverr = cpuif_rd_err | cpuif_wr_err; + + logic cpuif_req_masked; + + // Read & write latencies are balanced. Stalls not required + assign cpuif_req_stall_rd = '0; + assign cpuif_req_stall_wr = '0; + assign cpuif_req_masked = cpuif_req + & !(!cpuif_req_is_wr & cpuif_req_stall_rd) + & !(cpuif_req_is_wr & cpuif_req_stall_wr); + + //-------------------------------------------------------------------------- + // Address Decode + //-------------------------------------------------------------------------- + typedef struct { + logic t_latency_access; + logic en_latency_additional; + logic t_burst_max; + logic t_read_write_recovery; + logic t_rx_clk_delay; + logic t_tx_clk_delay; + logic address_mask_msb; + logic address_space; + logic dual_phy; + logic t_csh_cycles; + logic csn_to_ck_cycles; + logic chip0_base; + logic chip0_bound; + logic chip1_base; + logic chip1_bound; + logic chip2_base; + logic chip2_bound; + logic chip3_base; + logic chip3_bound; + logic flush; + logic status; + logic chip4_base; + logic chip4_bound; + logic chip5_base; + logic chip5_bound; + logic chip6_base; + logic chip6_bound; + logic chip7_base; + logic chip7_bound; + logic phy_clock_div; + } decoded_reg_strb_t; + decoded_reg_strb_t decoded_reg_strb; + logic decoded_err; + logic [6:0] decoded_addr; + logic decoded_req; + logic decoded_req_is_wr; + logic [31:0] decoded_wr_data; + logic [31:0] decoded_wr_biten; + + always_comb begin + automatic logic is_valid_addr; + automatic logic is_valid_rw; + is_valid_addr = '1; // No valid address check + is_valid_rw = '1; // No valid RW check + decoded_reg_strb.t_latency_access = cpuif_req_masked & (cpuif_addr == 7'h0); + decoded_reg_strb.en_latency_additional = cpuif_req_masked & (cpuif_addr == 7'h4); + decoded_reg_strb.t_burst_max = cpuif_req_masked & (cpuif_addr == 7'h8); + decoded_reg_strb.t_read_write_recovery = cpuif_req_masked & (cpuif_addr == 7'hc); + decoded_reg_strb.t_rx_clk_delay = cpuif_req_masked & (cpuif_addr == 7'h10); + decoded_reg_strb.t_tx_clk_delay = cpuif_req_masked & (cpuif_addr == 7'h14); + decoded_reg_strb.address_mask_msb = cpuif_req_masked & (cpuif_addr == 7'h18); + decoded_reg_strb.address_space = cpuif_req_masked & (cpuif_addr == 7'h1c); + decoded_reg_strb.dual_phy = cpuif_req_masked & (cpuif_addr == 7'h20); + decoded_reg_strb.t_csh_cycles = cpuif_req_masked & (cpuif_addr == 7'h28); + decoded_reg_strb.csn_to_ck_cycles = cpuif_req_masked & (cpuif_addr == 7'h2c); + decoded_reg_strb.chip0_base = cpuif_req_masked & (cpuif_addr == 7'h30); + decoded_reg_strb.chip0_bound = cpuif_req_masked & (cpuif_addr == 7'h34); + decoded_reg_strb.chip1_base = cpuif_req_masked & (cpuif_addr == 7'h38); + decoded_reg_strb.chip1_bound = cpuif_req_masked & (cpuif_addr == 7'h3c); + decoded_reg_strb.chip2_base = cpuif_req_masked & (cpuif_addr == 7'h40); + decoded_reg_strb.chip2_bound = cpuif_req_masked & (cpuif_addr == 7'h44); + decoded_reg_strb.chip3_base = cpuif_req_masked & (cpuif_addr == 7'h48); + decoded_reg_strb.chip3_bound = cpuif_req_masked & (cpuif_addr == 7'h4c); + decoded_reg_strb.flush = cpuif_req_masked & (cpuif_addr == 7'h50) & cpuif_req_is_wr; + decoded_reg_strb.status = cpuif_req_masked & (cpuif_addr == 7'h54); + decoded_reg_strb.chip4_base = cpuif_req_masked & (cpuif_addr == 7'h58); + decoded_reg_strb.chip4_bound = cpuif_req_masked & (cpuif_addr == 7'h5c); + decoded_reg_strb.chip5_base = cpuif_req_masked & (cpuif_addr == 7'h60); + decoded_reg_strb.chip5_bound = cpuif_req_masked & (cpuif_addr == 7'h64); + decoded_reg_strb.chip6_base = cpuif_req_masked & (cpuif_addr == 7'h68); + decoded_reg_strb.chip6_bound = cpuif_req_masked & (cpuif_addr == 7'h6c); + decoded_reg_strb.chip7_base = cpuif_req_masked & (cpuif_addr == 7'h70); + decoded_reg_strb.chip7_bound = cpuif_req_masked & (cpuif_addr == 7'h74); + decoded_reg_strb.phy_clock_div = cpuif_req_masked & (cpuif_addr == 7'h78); + decoded_err = '0; + end + + // Pass down signals to next stage + assign decoded_addr = cpuif_addr; + assign decoded_req = cpuif_req_masked; + assign decoded_req_is_wr = cpuif_req_is_wr; + assign decoded_wr_data = cpuif_wr_data; + assign decoded_wr_biten = cpuif_wr_biten; + + //-------------------------------------------------------------------------- + // Field logic + //-------------------------------------------------------------------------- + typedef struct { + struct { + struct { + logic [3:0] next; + logic load_next; + } value; + } t_latency_access; + struct { + struct { + logic next; + logic load_next; + } value; + } en_latency_additional; + struct { + struct { + logic [15:0] next; + logic load_next; + } value; + } t_burst_max; + struct { + struct { + logic [3:0] next; + logic load_next; + } value; + } t_read_write_recovery; + struct { + struct { + logic [4:0] next; + logic load_next; + } fine; + struct { + logic [2:0] next; + logic load_next; + } coarse; + } t_rx_clk_delay; + struct { + struct { + logic [4:0] next; + logic load_next; + } fine; + struct { + logic [2:0] next; + logic load_next; + } coarse; + } t_tx_clk_delay; + struct { + struct { + logic [4:0] next; + logic load_next; + } value; + } address_mask_msb; + struct { + struct { + logic next; + logic load_next; + } value; + } address_space; + struct { + struct { + logic next; + logic load_next; + } value; + } dual_phy; + struct { + struct { + logic [3:0] next; + logic load_next; + } value; + } t_csh_cycles; + struct { + struct { + logic [3:0] next; + logic load_next; + } value; + } csn_to_ck_cycles; + struct { + struct { + logic [9:0] next; + logic load_next; + } value; + } chip0_base; + struct { + struct { + logic [9:0] next; + logic load_next; + } value; + } chip0_bound; + struct { + struct { + logic [9:0] next; + logic load_next; + } value; + } chip1_base; + struct { + struct { + logic [9:0] next; + logic load_next; + } value; + } chip1_bound; + struct { + struct { + logic [9:0] next; + logic load_next; + } value; + } chip2_base; + struct { + struct { + logic [9:0] next; + logic load_next; + } value; + } chip2_bound; + struct { + struct { + logic [9:0] next; + logic load_next; + } value; + } chip3_base; + struct { + struct { + logic [9:0] next; + logic load_next; + } value; + } chip3_bound; + struct { + struct { + logic next; + logic load_next; + } request; + } flush; + struct { + struct { + logic next; + logic load_next; + } decode_error; + } status; + struct { + struct { + logic [9:0] next; + logic load_next; + } value; + } chip4_base; + struct { + struct { + logic [9:0] next; + logic load_next; + } value; + } chip4_bound; + struct { + struct { + logic [9:0] next; + logic load_next; + } value; + } chip5_base; + struct { + struct { + logic [9:0] next; + logic load_next; + } value; + } chip5_bound; + struct { + struct { + logic [9:0] next; + logic load_next; + } value; + } chip6_base; + struct { + struct { + logic [9:0] next; + logic load_next; + } value; + } chip6_bound; + struct { + struct { + logic [9:0] next; + logic load_next; + } value; + } chip7_base; + struct { + struct { + logic [9:0] next; + logic load_next; + } value; + } chip7_bound; + struct { + struct { + logic [7:0] next; + logic load_next; + } value; + } phy_clock_div; + } field_combo_t; + field_combo_t field_combo; + + typedef struct { + struct { + struct { + logic [3:0] value; + } value; + } t_latency_access; + struct { + struct { + logic value; + } value; + } en_latency_additional; + struct { + struct { + logic [15:0] value; + } value; + } t_burst_max; + struct { + struct { + logic [3:0] value; + } value; + } t_read_write_recovery; + struct { + struct { + logic [4:0] value; + } fine; + struct { + logic [2:0] value; + } coarse; + } t_rx_clk_delay; + struct { + struct { + logic [4:0] value; + } fine; + struct { + logic [2:0] value; + } coarse; + } t_tx_clk_delay; + struct { + struct { + logic [4:0] value; + } value; + } address_mask_msb; + struct { + struct { + logic value; + } value; + } address_space; + struct { + struct { + logic value; + } value; + } dual_phy; + struct { + struct { + logic [3:0] value; + } value; + } t_csh_cycles; + struct { + struct { + logic [3:0] value; + } value; + } csn_to_ck_cycles; + struct { + struct { + logic [9:0] value; + } value; + } chip0_base; + struct { + struct { + logic [9:0] value; + } value; + } chip0_bound; + struct { + struct { + logic [9:0] value; + } value; + } chip1_base; + struct { + struct { + logic [9:0] value; + } value; + } chip1_bound; + struct { + struct { + logic [9:0] value; + } value; + } chip2_base; + struct { + struct { + logic [9:0] value; + } value; + } chip2_bound; + struct { + struct { + logic [9:0] value; + } value; + } chip3_base; + struct { + struct { + logic [9:0] value; + } value; + } chip3_bound; + struct { + struct { + logic value; + } request; + } flush; + struct { + struct { + logic value; + } decode_error; + } status; + struct { + struct { + logic [9:0] value; + } value; + } chip4_base; + struct { + struct { + logic [9:0] value; + } value; + } chip4_bound; + struct { + struct { + logic [9:0] value; + } value; + } chip5_base; + struct { + struct { + logic [9:0] value; + } value; + } chip5_bound; + struct { + struct { + logic [9:0] value; + } value; + } chip6_base; + struct { + struct { + logic [9:0] value; + } value; + } chip6_bound; + struct { + struct { + logic [9:0] value; + } value; + } chip7_base; + struct { + struct { + logic [9:0] value; + } value; + } chip7_bound; + struct { + struct { + logic [7:0] value; + } value; + } phy_clock_div; + } field_storage_t; + field_storage_t field_storage; + + // Field: hyperbus_cfg_regs.t_latency_access.value + always_comb begin + automatic logic [3:0] next_c; + automatic logic load_next_c; + next_c = field_storage.t_latency_access.value.value; + load_next_c = '0; + if(decoded_reg_strb.t_latency_access && decoded_req_is_wr) begin // SW write + next_c = (field_storage.t_latency_access.value.value & ~decoded_wr_biten[3:0]) | (decoded_wr_data[3:0] & decoded_wr_biten[3:0]); + load_next_c = '1; + end + field_combo.t_latency_access.value.next = next_c; + field_combo.t_latency_access.value.load_next = load_next_c; + end + always_ff @(posedge clk or negedge arst_n) begin + if(~arst_n) begin + field_storage.t_latency_access.value.value <= 4'h6; + end else begin + if(field_combo.t_latency_access.value.load_next) begin + field_storage.t_latency_access.value.value <= field_combo.t_latency_access.value.next; + end + end + end + assign hwif_out.t_latency_access.value.value = field_storage.t_latency_access.value.value; + // Field: hyperbus_cfg_regs.en_latency_additional.value + always_comb begin + automatic logic [0:0] next_c; + automatic logic load_next_c; + next_c = field_storage.en_latency_additional.value.value; + load_next_c = '0; + if(decoded_reg_strb.en_latency_additional && decoded_req_is_wr) begin // SW write + next_c = (field_storage.en_latency_additional.value.value & ~decoded_wr_biten[0:0]) | (decoded_wr_data[0:0] & decoded_wr_biten[0:0]); + load_next_c = '1; + end + field_combo.en_latency_additional.value.next = next_c; + field_combo.en_latency_additional.value.load_next = load_next_c; + end + always_ff @(posedge clk or negedge arst_n) begin + if(~arst_n) begin + field_storage.en_latency_additional.value.value <= 1'h0; + end else begin + if(field_combo.en_latency_additional.value.load_next) begin + field_storage.en_latency_additional.value.value <= field_combo.en_latency_additional.value.next; + end + end + end + assign hwif_out.en_latency_additional.value.value = field_storage.en_latency_additional.value.value; + // Field: hyperbus_cfg_regs.t_burst_max.value + always_comb begin + automatic logic [15:0] next_c; + automatic logic load_next_c; + next_c = field_storage.t_burst_max.value.value; + load_next_c = '0; + if(decoded_reg_strb.t_burst_max && decoded_req_is_wr) begin // SW write + next_c = (field_storage.t_burst_max.value.value & ~decoded_wr_biten[15:0]) | (decoded_wr_data[15:0] & decoded_wr_biten[15:0]); + load_next_c = '1; + end + field_combo.t_burst_max.value.next = next_c; + field_combo.t_burst_max.value.load_next = load_next_c; + end + always_ff @(posedge clk or negedge arst_n) begin + if(~arst_n) begin + field_storage.t_burst_max.value.value <= 16'h15e; + end else begin + if(field_combo.t_burst_max.value.load_next) begin + field_storage.t_burst_max.value.value <= field_combo.t_burst_max.value.next; + end + end + end + assign hwif_out.t_burst_max.value.value = field_storage.t_burst_max.value.value; + // Field: hyperbus_cfg_regs.t_read_write_recovery.value + always_comb begin + automatic logic [3:0] next_c; + automatic logic load_next_c; + next_c = field_storage.t_read_write_recovery.value.value; + load_next_c = '0; + if(decoded_reg_strb.t_read_write_recovery && decoded_req_is_wr) begin // SW write + next_c = (field_storage.t_read_write_recovery.value.value & ~decoded_wr_biten[3:0]) | (decoded_wr_data[3:0] & decoded_wr_biten[3:0]); + load_next_c = '1; + end + field_combo.t_read_write_recovery.value.next = next_c; + field_combo.t_read_write_recovery.value.load_next = load_next_c; + end + always_ff @(posedge clk or negedge arst_n) begin + if(~arst_n) begin + field_storage.t_read_write_recovery.value.value <= 4'h6; + end else begin + if(field_combo.t_read_write_recovery.value.load_next) begin + field_storage.t_read_write_recovery.value.value <= field_combo.t_read_write_recovery.value.next; + end + end + end + assign hwif_out.t_read_write_recovery.value.value = field_storage.t_read_write_recovery.value.value; + // Field: hyperbus_cfg_regs.t_rx_clk_delay.fine + always_comb begin + automatic logic [4:0] next_c; + automatic logic load_next_c; + next_c = field_storage.t_rx_clk_delay.fine.value; + load_next_c = '0; + if(decoded_reg_strb.t_rx_clk_delay && decoded_req_is_wr) begin // SW write + next_c = (field_storage.t_rx_clk_delay.fine.value & ~decoded_wr_biten[4:0]) | (decoded_wr_data[4:0] & decoded_wr_biten[4:0]); + load_next_c = '1; + end + field_combo.t_rx_clk_delay.fine.next = next_c; + field_combo.t_rx_clk_delay.fine.load_next = load_next_c; + end + always_ff @(posedge clk or negedge arst_n) begin + if(~arst_n) begin + field_storage.t_rx_clk_delay.fine.value <= 5'h10; + end else begin + if(field_combo.t_rx_clk_delay.fine.load_next) begin + field_storage.t_rx_clk_delay.fine.value <= field_combo.t_rx_clk_delay.fine.next; + end + end + end + assign hwif_out.t_rx_clk_delay.fine.value = field_storage.t_rx_clk_delay.fine.value; + // Field: hyperbus_cfg_regs.t_rx_clk_delay.coarse + always_comb begin + automatic logic [2:0] next_c; + automatic logic load_next_c; + next_c = field_storage.t_rx_clk_delay.coarse.value; + load_next_c = '0; + if(decoded_reg_strb.t_rx_clk_delay && decoded_req_is_wr) begin // SW write + next_c = (field_storage.t_rx_clk_delay.coarse.value & ~decoded_wr_biten[7:5]) | (decoded_wr_data[7:5] & decoded_wr_biten[7:5]); + load_next_c = '1; + end + field_combo.t_rx_clk_delay.coarse.next = next_c; + field_combo.t_rx_clk_delay.coarse.load_next = load_next_c; + end + always_ff @(posedge clk or negedge arst_n) begin + if(~arst_n) begin + field_storage.t_rx_clk_delay.coarse.value <= 3'h0; + end else begin + if(field_combo.t_rx_clk_delay.coarse.load_next) begin + field_storage.t_rx_clk_delay.coarse.value <= field_combo.t_rx_clk_delay.coarse.next; + end + end + end + assign hwif_out.t_rx_clk_delay.coarse.value = field_storage.t_rx_clk_delay.coarse.value; + // Field: hyperbus_cfg_regs.t_tx_clk_delay.fine + always_comb begin + automatic logic [4:0] next_c; + automatic logic load_next_c; + next_c = field_storage.t_tx_clk_delay.fine.value; + load_next_c = '0; + if(decoded_reg_strb.t_tx_clk_delay && decoded_req_is_wr) begin // SW write + next_c = (field_storage.t_tx_clk_delay.fine.value & ~decoded_wr_biten[4:0]) | (decoded_wr_data[4:0] & decoded_wr_biten[4:0]); + load_next_c = '1; + end + field_combo.t_tx_clk_delay.fine.next = next_c; + field_combo.t_tx_clk_delay.fine.load_next = load_next_c; + end + always_ff @(posedge clk or negedge arst_n) begin + if(~arst_n) begin + field_storage.t_tx_clk_delay.fine.value <= 5'h10; + end else begin + if(field_combo.t_tx_clk_delay.fine.load_next) begin + field_storage.t_tx_clk_delay.fine.value <= field_combo.t_tx_clk_delay.fine.next; + end + end + end + assign hwif_out.t_tx_clk_delay.fine.value = field_storage.t_tx_clk_delay.fine.value; + // Field: hyperbus_cfg_regs.t_tx_clk_delay.coarse + always_comb begin + automatic logic [2:0] next_c; + automatic logic load_next_c; + next_c = field_storage.t_tx_clk_delay.coarse.value; + load_next_c = '0; + if(decoded_reg_strb.t_tx_clk_delay && decoded_req_is_wr) begin // SW write + next_c = (field_storage.t_tx_clk_delay.coarse.value & ~decoded_wr_biten[7:5]) | (decoded_wr_data[7:5] & decoded_wr_biten[7:5]); + load_next_c = '1; + end + field_combo.t_tx_clk_delay.coarse.next = next_c; + field_combo.t_tx_clk_delay.coarse.load_next = load_next_c; + end + always_ff @(posedge clk or negedge arst_n) begin + if(~arst_n) begin + field_storage.t_tx_clk_delay.coarse.value <= 3'h0; + end else begin + if(field_combo.t_tx_clk_delay.coarse.load_next) begin + field_storage.t_tx_clk_delay.coarse.value <= field_combo.t_tx_clk_delay.coarse.next; + end + end + end + assign hwif_out.t_tx_clk_delay.coarse.value = field_storage.t_tx_clk_delay.coarse.value; + // Field: hyperbus_cfg_regs.address_mask_msb.value + always_comb begin + automatic logic [4:0] next_c; + automatic logic load_next_c; + next_c = field_storage.address_mask_msb.value.value; + load_next_c = '0; + if(decoded_reg_strb.address_mask_msb && decoded_req_is_wr) begin // SW write + next_c = (field_storage.address_mask_msb.value.value & ~decoded_wr_biten[4:0]) | (decoded_wr_data[4:0] & decoded_wr_biten[4:0]); + load_next_c = '1; + end + field_combo.address_mask_msb.value.next = next_c; + field_combo.address_mask_msb.value.load_next = load_next_c; + end + always_ff @(posedge clk or negedge arst_n) begin + if(~arst_n) begin + field_storage.address_mask_msb.value.value <= 5'h19; + end else begin + if(field_combo.address_mask_msb.value.load_next) begin + field_storage.address_mask_msb.value.value <= field_combo.address_mask_msb.value.next; + end + end + end + assign hwif_out.address_mask_msb.value.value = field_storage.address_mask_msb.value.value; + // Field: hyperbus_cfg_regs.address_space.value + always_comb begin + automatic logic [0:0] next_c; + automatic logic load_next_c; + next_c = field_storage.address_space.value.value; + load_next_c = '0; + if(decoded_reg_strb.address_space && decoded_req_is_wr) begin // SW write + next_c = (field_storage.address_space.value.value & ~decoded_wr_biten[0:0]) | (decoded_wr_data[0:0] & decoded_wr_biten[0:0]); + load_next_c = '1; + end + field_combo.address_space.value.next = next_c; + field_combo.address_space.value.load_next = load_next_c; + end + always_ff @(posedge clk or negedge arst_n) begin + if(~arst_n) begin + field_storage.address_space.value.value <= 1'h0; + end else begin + if(field_combo.address_space.value.load_next) begin + field_storage.address_space.value.value <= field_combo.address_space.value.next; + end + end + end + assign hwif_out.address_space.value.value = field_storage.address_space.value.value; + // Field: hyperbus_cfg_regs.dual_phy.value + always_comb begin + automatic logic [0:0] next_c; + automatic logic load_next_c; + next_c = field_storage.dual_phy.value.value; + load_next_c = '0; + if(decoded_reg_strb.dual_phy && decoded_req_is_wr) begin // SW write + next_c = (field_storage.dual_phy.value.value & ~decoded_wr_biten[0:0]) | (decoded_wr_data[0:0] & decoded_wr_biten[0:0]); + load_next_c = '1; + end + field_combo.dual_phy.value.next = next_c; + field_combo.dual_phy.value.load_next = load_next_c; + end + always_ff @(posedge clk or negedge arst_n) begin + if(~arst_n) begin + field_storage.dual_phy.value.value <= 1'h1; + end else begin + if(field_combo.dual_phy.value.load_next) begin + field_storage.dual_phy.value.value <= field_combo.dual_phy.value.next; + end + end + end + assign hwif_out.dual_phy.value.value = field_storage.dual_phy.value.value; + // Field: hyperbus_cfg_regs.t_csh_cycles.value + always_comb begin + automatic logic [3:0] next_c; + automatic logic load_next_c; + next_c = field_storage.t_csh_cycles.value.value; + load_next_c = '0; + if(decoded_reg_strb.t_csh_cycles && decoded_req_is_wr) begin // SW write + next_c = (field_storage.t_csh_cycles.value.value & ~decoded_wr_biten[3:0]) | (decoded_wr_data[3:0] & decoded_wr_biten[3:0]); + load_next_c = '1; + end + field_combo.t_csh_cycles.value.next = next_c; + field_combo.t_csh_cycles.value.load_next = load_next_c; + end + always_ff @(posedge clk or negedge arst_n) begin + if(~arst_n) begin + field_storage.t_csh_cycles.value.value <= 4'h1; + end else begin + if(field_combo.t_csh_cycles.value.load_next) begin + field_storage.t_csh_cycles.value.value <= field_combo.t_csh_cycles.value.next; + end + end + end + assign hwif_out.t_csh_cycles.value.value = field_storage.t_csh_cycles.value.value; + // Field: hyperbus_cfg_regs.csn_to_ck_cycles.value + always_comb begin + automatic logic [3:0] next_c; + automatic logic load_next_c; + next_c = field_storage.csn_to_ck_cycles.value.value; + load_next_c = '0; + if(decoded_reg_strb.csn_to_ck_cycles && decoded_req_is_wr) begin // SW write + next_c = (field_storage.csn_to_ck_cycles.value.value & ~decoded_wr_biten[3:0]) | (decoded_wr_data[3:0] & decoded_wr_biten[3:0]); + load_next_c = '1; + end + field_combo.csn_to_ck_cycles.value.next = next_c; + field_combo.csn_to_ck_cycles.value.load_next = load_next_c; + end + always_ff @(posedge clk or negedge arst_n) begin + if(~arst_n) begin + field_storage.csn_to_ck_cycles.value.value <= 4'h0; + end else begin + if(field_combo.csn_to_ck_cycles.value.load_next) begin + field_storage.csn_to_ck_cycles.value.value <= field_combo.csn_to_ck_cycles.value.next; + end + end + end + assign hwif_out.csn_to_ck_cycles.value.value = field_storage.csn_to_ck_cycles.value.value; + // Field: hyperbus_cfg_regs.chip0_base.value + always_comb begin + automatic logic [9:0] next_c; + automatic logic load_next_c; + next_c = field_storage.chip0_base.value.value; + load_next_c = '0; + if(decoded_reg_strb.chip0_base && decoded_req_is_wr) begin // SW write + next_c = (field_storage.chip0_base.value.value & ~decoded_wr_biten[31:22]) | (decoded_wr_data[31:22] & decoded_wr_biten[31:22]); + load_next_c = '1; + end + field_combo.chip0_base.value.next = next_c; + field_combo.chip0_base.value.load_next = load_next_c; + end + always_ff @(posedge clk or negedge arst_n) begin + if(~arst_n) begin + field_storage.chip0_base.value.value <= 10'h0; + end else begin + if(field_combo.chip0_base.value.load_next) begin + field_storage.chip0_base.value.value <= field_combo.chip0_base.value.next; + end + end + end + assign hwif_out.chip0_base.value.value = field_storage.chip0_base.value.value; + // Field: hyperbus_cfg_regs.chip0_bound.value + always_comb begin + automatic logic [9:0] next_c; + automatic logic load_next_c; + next_c = field_storage.chip0_bound.value.value; + load_next_c = '0; + if(decoded_reg_strb.chip0_bound && decoded_req_is_wr) begin // SW write + next_c = (field_storage.chip0_bound.value.value & ~decoded_wr_biten[31:22]) | (decoded_wr_data[31:22] & decoded_wr_biten[31:22]); + load_next_c = '1; + end + field_combo.chip0_bound.value.next = next_c; + field_combo.chip0_bound.value.load_next = load_next_c; + end + always_ff @(posedge clk or negedge arst_n) begin + if(~arst_n) begin + field_storage.chip0_bound.value.value <= 10'h1; + end else begin + if(field_combo.chip0_bound.value.load_next) begin + field_storage.chip0_bound.value.value <= field_combo.chip0_bound.value.next; + end + end + end + assign hwif_out.chip0_bound.value.value = field_storage.chip0_bound.value.value; + // Field: hyperbus_cfg_regs.chip1_base.value + always_comb begin + automatic logic [9:0] next_c; + automatic logic load_next_c; + next_c = field_storage.chip1_base.value.value; + load_next_c = '0; + if(decoded_reg_strb.chip1_base && decoded_req_is_wr) begin // SW write + next_c = (field_storage.chip1_base.value.value & ~decoded_wr_biten[31:22]) | (decoded_wr_data[31:22] & decoded_wr_biten[31:22]); + load_next_c = '1; + end + field_combo.chip1_base.value.next = next_c; + field_combo.chip1_base.value.load_next = load_next_c; + end + always_ff @(posedge clk or negedge arst_n) begin + if(~arst_n) begin + field_storage.chip1_base.value.value <= 10'h1; + end else begin + if(field_combo.chip1_base.value.load_next) begin + field_storage.chip1_base.value.value <= field_combo.chip1_base.value.next; + end + end + end + assign hwif_out.chip1_base.value.value = field_storage.chip1_base.value.value; + // Field: hyperbus_cfg_regs.chip1_bound.value + always_comb begin + automatic logic [9:0] next_c; + automatic logic load_next_c; + next_c = field_storage.chip1_bound.value.value; + load_next_c = '0; + if(decoded_reg_strb.chip1_bound && decoded_req_is_wr) begin // SW write + next_c = (field_storage.chip1_bound.value.value & ~decoded_wr_biten[31:22]) | (decoded_wr_data[31:22] & decoded_wr_biten[31:22]); + load_next_c = '1; + end + field_combo.chip1_bound.value.next = next_c; + field_combo.chip1_bound.value.load_next = load_next_c; + end + always_ff @(posedge clk or negedge arst_n) begin + if(~arst_n) begin + field_storage.chip1_bound.value.value <= 10'h2; + end else begin + if(field_combo.chip1_bound.value.load_next) begin + field_storage.chip1_bound.value.value <= field_combo.chip1_bound.value.next; + end + end + end + assign hwif_out.chip1_bound.value.value = field_storage.chip1_bound.value.value; + // Field: hyperbus_cfg_regs.chip2_base.value + always_comb begin + automatic logic [9:0] next_c; + automatic logic load_next_c; + next_c = field_storage.chip2_base.value.value; + load_next_c = '0; + if(decoded_reg_strb.chip2_base && decoded_req_is_wr) begin // SW write + next_c = (field_storage.chip2_base.value.value & ~decoded_wr_biten[31:22]) | (decoded_wr_data[31:22] & decoded_wr_biten[31:22]); + load_next_c = '1; + end + field_combo.chip2_base.value.next = next_c; + field_combo.chip2_base.value.load_next = load_next_c; + end + always_ff @(posedge clk or negedge arst_n) begin + if(~arst_n) begin + field_storage.chip2_base.value.value <= 10'h2; + end else begin + if(field_combo.chip2_base.value.load_next) begin + field_storage.chip2_base.value.value <= field_combo.chip2_base.value.next; + end + end + end + assign hwif_out.chip2_base.value.value = field_storage.chip2_base.value.value; + // Field: hyperbus_cfg_regs.chip2_bound.value + always_comb begin + automatic logic [9:0] next_c; + automatic logic load_next_c; + next_c = field_storage.chip2_bound.value.value; + load_next_c = '0; + if(decoded_reg_strb.chip2_bound && decoded_req_is_wr) begin // SW write + next_c = (field_storage.chip2_bound.value.value & ~decoded_wr_biten[31:22]) | (decoded_wr_data[31:22] & decoded_wr_biten[31:22]); + load_next_c = '1; + end + field_combo.chip2_bound.value.next = next_c; + field_combo.chip2_bound.value.load_next = load_next_c; + end + always_ff @(posedge clk or negedge arst_n) begin + if(~arst_n) begin + field_storage.chip2_bound.value.value <= 10'h3; + end else begin + if(field_combo.chip2_bound.value.load_next) begin + field_storage.chip2_bound.value.value <= field_combo.chip2_bound.value.next; + end + end + end + assign hwif_out.chip2_bound.value.value = field_storage.chip2_bound.value.value; + // Field: hyperbus_cfg_regs.chip3_base.value + always_comb begin + automatic logic [9:0] next_c; + automatic logic load_next_c; + next_c = field_storage.chip3_base.value.value; + load_next_c = '0; + if(decoded_reg_strb.chip3_base && decoded_req_is_wr) begin // SW write + next_c = (field_storage.chip3_base.value.value & ~decoded_wr_biten[31:22]) | (decoded_wr_data[31:22] & decoded_wr_biten[31:22]); + load_next_c = '1; + end + field_combo.chip3_base.value.next = next_c; + field_combo.chip3_base.value.load_next = load_next_c; + end + always_ff @(posedge clk or negedge arst_n) begin + if(~arst_n) begin + field_storage.chip3_base.value.value <= 10'h3; + end else begin + if(field_combo.chip3_base.value.load_next) begin + field_storage.chip3_base.value.value <= field_combo.chip3_base.value.next; + end + end + end + assign hwif_out.chip3_base.value.value = field_storage.chip3_base.value.value; + // Field: hyperbus_cfg_regs.chip3_bound.value + always_comb begin + automatic logic [9:0] next_c; + automatic logic load_next_c; + next_c = field_storage.chip3_bound.value.value; + load_next_c = '0; + if(decoded_reg_strb.chip3_bound && decoded_req_is_wr) begin // SW write + next_c = (field_storage.chip3_bound.value.value & ~decoded_wr_biten[31:22]) | (decoded_wr_data[31:22] & decoded_wr_biten[31:22]); + load_next_c = '1; + end + field_combo.chip3_bound.value.next = next_c; + field_combo.chip3_bound.value.load_next = load_next_c; + end + always_ff @(posedge clk or negedge arst_n) begin + if(~arst_n) begin + field_storage.chip3_bound.value.value <= 10'h4; + end else begin + if(field_combo.chip3_bound.value.load_next) begin + field_storage.chip3_bound.value.value <= field_combo.chip3_bound.value.next; + end + end + end + assign hwif_out.chip3_bound.value.value = field_storage.chip3_bound.value.value; + // Field: hyperbus_cfg_regs.flush.request + always_comb begin + automatic logic [0:0] next_c; + automatic logic load_next_c; + next_c = field_storage.flush.request.value; + load_next_c = '0; + if(decoded_reg_strb.flush && decoded_req_is_wr) begin // SW write + next_c = (field_storage.flush.request.value & ~decoded_wr_biten[0:0]) | (decoded_wr_data[0:0] & decoded_wr_biten[0:0]); + load_next_c = '1; + end + field_combo.flush.request.next = next_c; + field_combo.flush.request.load_next = load_next_c; + end + always_ff @(posedge clk) begin + if(field_combo.flush.request.load_next) begin + field_storage.flush.request.value <= field_combo.flush.request.next; + end + end + assign hwif_out.flush.request.value = field_storage.flush.request.value; + // Field: hyperbus_cfg_regs.status.decode_error + always_comb begin + automatic logic [0:0] next_c; + automatic logic load_next_c; + next_c = field_storage.status.decode_error.value; + load_next_c = '0; + if(hwif_in.status.decode_error.hwset) begin // HW Set + next_c = '1; + load_next_c = '1; + end else if(decoded_reg_strb.status && decoded_req_is_wr) begin // SW write 1 clear + next_c = field_storage.status.decode_error.value & ~(decoded_wr_data[0:0] & decoded_wr_biten[0:0]); + load_next_c = '1; + end + field_combo.status.decode_error.next = next_c; + field_combo.status.decode_error.load_next = load_next_c; + end + always_ff @(posedge clk or negedge arst_n) begin + if(~arst_n) begin + field_storage.status.decode_error.value <= 1'h0; + end else begin + if(field_combo.status.decode_error.load_next) begin + field_storage.status.decode_error.value <= field_combo.status.decode_error.next; + end + end + end + assign hwif_out.status.decode_error.value = field_storage.status.decode_error.value; + // Field: hyperbus_cfg_regs.chip4_base.value + always_comb begin + automatic logic [9:0] next_c; + automatic logic load_next_c; + next_c = field_storage.chip4_base.value.value; + load_next_c = '0; + if(decoded_reg_strb.chip4_base && decoded_req_is_wr) begin // SW write + next_c = (field_storage.chip4_base.value.value & ~decoded_wr_biten[31:22]) | (decoded_wr_data[31:22] & decoded_wr_biten[31:22]); + load_next_c = '1; + end + field_combo.chip4_base.value.next = next_c; + field_combo.chip4_base.value.load_next = load_next_c; + end + always_ff @(posedge clk or negedge arst_n) begin + if(~arst_n) begin + field_storage.chip4_base.value.value <= 10'h4; + end else begin + if(field_combo.chip4_base.value.load_next) begin + field_storage.chip4_base.value.value <= field_combo.chip4_base.value.next; + end + end + end + assign hwif_out.chip4_base.value.value = field_storage.chip4_base.value.value; + // Field: hyperbus_cfg_regs.chip4_bound.value + always_comb begin + automatic logic [9:0] next_c; + automatic logic load_next_c; + next_c = field_storage.chip4_bound.value.value; + load_next_c = '0; + if(decoded_reg_strb.chip4_bound && decoded_req_is_wr) begin // SW write + next_c = (field_storage.chip4_bound.value.value & ~decoded_wr_biten[31:22]) | (decoded_wr_data[31:22] & decoded_wr_biten[31:22]); + load_next_c = '1; + end + field_combo.chip4_bound.value.next = next_c; + field_combo.chip4_bound.value.load_next = load_next_c; + end + always_ff @(posedge clk or negedge arst_n) begin + if(~arst_n) begin + field_storage.chip4_bound.value.value <= 10'h5; + end else begin + if(field_combo.chip4_bound.value.load_next) begin + field_storage.chip4_bound.value.value <= field_combo.chip4_bound.value.next; + end + end + end + assign hwif_out.chip4_bound.value.value = field_storage.chip4_bound.value.value; + // Field: hyperbus_cfg_regs.chip5_base.value + always_comb begin + automatic logic [9:0] next_c; + automatic logic load_next_c; + next_c = field_storage.chip5_base.value.value; + load_next_c = '0; + if(decoded_reg_strb.chip5_base && decoded_req_is_wr) begin // SW write + next_c = (field_storage.chip5_base.value.value & ~decoded_wr_biten[31:22]) | (decoded_wr_data[31:22] & decoded_wr_biten[31:22]); + load_next_c = '1; + end + field_combo.chip5_base.value.next = next_c; + field_combo.chip5_base.value.load_next = load_next_c; + end + always_ff @(posedge clk or negedge arst_n) begin + if(~arst_n) begin + field_storage.chip5_base.value.value <= 10'h5; + end else begin + if(field_combo.chip5_base.value.load_next) begin + field_storage.chip5_base.value.value <= field_combo.chip5_base.value.next; + end + end + end + assign hwif_out.chip5_base.value.value = field_storage.chip5_base.value.value; + // Field: hyperbus_cfg_regs.chip5_bound.value + always_comb begin + automatic logic [9:0] next_c; + automatic logic load_next_c; + next_c = field_storage.chip5_bound.value.value; + load_next_c = '0; + if(decoded_reg_strb.chip5_bound && decoded_req_is_wr) begin // SW write + next_c = (field_storage.chip5_bound.value.value & ~decoded_wr_biten[31:22]) | (decoded_wr_data[31:22] & decoded_wr_biten[31:22]); + load_next_c = '1; + end + field_combo.chip5_bound.value.next = next_c; + field_combo.chip5_bound.value.load_next = load_next_c; + end + always_ff @(posedge clk or negedge arst_n) begin + if(~arst_n) begin + field_storage.chip5_bound.value.value <= 10'h6; + end else begin + if(field_combo.chip5_bound.value.load_next) begin + field_storage.chip5_bound.value.value <= field_combo.chip5_bound.value.next; + end + end + end + assign hwif_out.chip5_bound.value.value = field_storage.chip5_bound.value.value; + // Field: hyperbus_cfg_regs.chip6_base.value + always_comb begin + automatic logic [9:0] next_c; + automatic logic load_next_c; + next_c = field_storage.chip6_base.value.value; + load_next_c = '0; + if(decoded_reg_strb.chip6_base && decoded_req_is_wr) begin // SW write + next_c = (field_storage.chip6_base.value.value & ~decoded_wr_biten[31:22]) | (decoded_wr_data[31:22] & decoded_wr_biten[31:22]); + load_next_c = '1; + end + field_combo.chip6_base.value.next = next_c; + field_combo.chip6_base.value.load_next = load_next_c; + end + always_ff @(posedge clk or negedge arst_n) begin + if(~arst_n) begin + field_storage.chip6_base.value.value <= 10'h6; + end else begin + if(field_combo.chip6_base.value.load_next) begin + field_storage.chip6_base.value.value <= field_combo.chip6_base.value.next; + end + end + end + assign hwif_out.chip6_base.value.value = field_storage.chip6_base.value.value; + // Field: hyperbus_cfg_regs.chip6_bound.value + always_comb begin + automatic logic [9:0] next_c; + automatic logic load_next_c; + next_c = field_storage.chip6_bound.value.value; + load_next_c = '0; + if(decoded_reg_strb.chip6_bound && decoded_req_is_wr) begin // SW write + next_c = (field_storage.chip6_bound.value.value & ~decoded_wr_biten[31:22]) | (decoded_wr_data[31:22] & decoded_wr_biten[31:22]); + load_next_c = '1; + end + field_combo.chip6_bound.value.next = next_c; + field_combo.chip6_bound.value.load_next = load_next_c; + end + always_ff @(posedge clk or negedge arst_n) begin + if(~arst_n) begin + field_storage.chip6_bound.value.value <= 10'h7; + end else begin + if(field_combo.chip6_bound.value.load_next) begin + field_storage.chip6_bound.value.value <= field_combo.chip6_bound.value.next; + end + end + end + assign hwif_out.chip6_bound.value.value = field_storage.chip6_bound.value.value; + // Field: hyperbus_cfg_regs.chip7_base.value + always_comb begin + automatic logic [9:0] next_c; + automatic logic load_next_c; + next_c = field_storage.chip7_base.value.value; + load_next_c = '0; + if(decoded_reg_strb.chip7_base && decoded_req_is_wr) begin // SW write + next_c = (field_storage.chip7_base.value.value & ~decoded_wr_biten[31:22]) | (decoded_wr_data[31:22] & decoded_wr_biten[31:22]); + load_next_c = '1; + end + field_combo.chip7_base.value.next = next_c; + field_combo.chip7_base.value.load_next = load_next_c; + end + always_ff @(posedge clk or negedge arst_n) begin + if(~arst_n) begin + field_storage.chip7_base.value.value <= 10'h7; + end else begin + if(field_combo.chip7_base.value.load_next) begin + field_storage.chip7_base.value.value <= field_combo.chip7_base.value.next; + end + end + end + assign hwif_out.chip7_base.value.value = field_storage.chip7_base.value.value; + // Field: hyperbus_cfg_regs.chip7_bound.value + always_comb begin + automatic logic [9:0] next_c; + automatic logic load_next_c; + next_c = field_storage.chip7_bound.value.value; + load_next_c = '0; + if(decoded_reg_strb.chip7_bound && decoded_req_is_wr) begin // SW write + next_c = (field_storage.chip7_bound.value.value & ~decoded_wr_biten[31:22]) | (decoded_wr_data[31:22] & decoded_wr_biten[31:22]); + load_next_c = '1; + end + field_combo.chip7_bound.value.next = next_c; + field_combo.chip7_bound.value.load_next = load_next_c; + end + always_ff @(posedge clk or negedge arst_n) begin + if(~arst_n) begin + field_storage.chip7_bound.value.value <= 10'h8; + end else begin + if(field_combo.chip7_bound.value.load_next) begin + field_storage.chip7_bound.value.value <= field_combo.chip7_bound.value.next; + end + end + end + assign hwif_out.chip7_bound.value.value = field_storage.chip7_bound.value.value; + // Field: hyperbus_cfg_regs.phy_clock_div.value + always_comb begin + automatic logic [7:0] next_c; + automatic logic load_next_c; + next_c = field_storage.phy_clock_div.value.value; + load_next_c = '0; + if(decoded_reg_strb.phy_clock_div && decoded_req_is_wr) begin // SW write + next_c = (field_storage.phy_clock_div.value.value & ~decoded_wr_biten[7:0]) | (decoded_wr_data[7:0] & decoded_wr_biten[7:0]); + load_next_c = '1; + end + field_combo.phy_clock_div.value.next = next_c; + field_combo.phy_clock_div.value.load_next = load_next_c; + end + always_ff @(posedge clk or negedge arst_n) begin + if(~arst_n) begin + field_storage.phy_clock_div.value.value <= 8'h8; + end else begin + if(field_combo.phy_clock_div.value.load_next) begin + field_storage.phy_clock_div.value.value <= field_combo.phy_clock_div.value.next; + end + end + end + assign hwif_out.phy_clock_div.value.value = field_storage.phy_clock_div.value.value; + + //-------------------------------------------------------------------------- + // Write response + //-------------------------------------------------------------------------- + assign cpuif_wr_ack = decoded_req & decoded_req_is_wr; + // Writes are always granted with no error response + assign cpuif_wr_err = '0; + + //-------------------------------------------------------------------------- + // Readback + //-------------------------------------------------------------------------- + + logic [6:0] rd_mux_addr; + assign rd_mux_addr = decoded_addr; + + logic readback_err; + logic readback_done; + logic [31:0] readback_data; + always_comb begin + automatic logic [31:0] readback_data_var; + readback_data_var = '0; + if(rd_mux_addr == 7'h0) begin + readback_data_var[3:0] = field_storage.t_latency_access.value.value; + end + if(rd_mux_addr == 7'h4) begin + readback_data_var[0] = field_storage.en_latency_additional.value.value; + end + if(rd_mux_addr == 7'h8) begin + readback_data_var[15:0] = field_storage.t_burst_max.value.value; + end + if(rd_mux_addr == 7'hc) begin + readback_data_var[3:0] = field_storage.t_read_write_recovery.value.value; + end + if(rd_mux_addr == 7'h10) begin + readback_data_var[4:0] = field_storage.t_rx_clk_delay.fine.value; + readback_data_var[7:5] = field_storage.t_rx_clk_delay.coarse.value; + end + if(rd_mux_addr == 7'h14) begin + readback_data_var[4:0] = field_storage.t_tx_clk_delay.fine.value; + readback_data_var[7:5] = field_storage.t_tx_clk_delay.coarse.value; + end + if(rd_mux_addr == 7'h18) begin + readback_data_var[4:0] = field_storage.address_mask_msb.value.value; + end + if(rd_mux_addr == 7'h1c) begin + readback_data_var[0] = field_storage.address_space.value.value; + end + if(rd_mux_addr == 7'h20) begin + readback_data_var[0] = field_storage.dual_phy.value.value; + end + if(rd_mux_addr == 7'h28) begin + readback_data_var[3:0] = field_storage.t_csh_cycles.value.value; + end + if(rd_mux_addr == 7'h2c) begin + readback_data_var[3:0] = field_storage.csn_to_ck_cycles.value.value; + end + if(rd_mux_addr == 7'h30) begin + readback_data_var[31:22] = field_storage.chip0_base.value.value; + end + if(rd_mux_addr == 7'h34) begin + readback_data_var[31:22] = field_storage.chip0_bound.value.value; + end + if(rd_mux_addr == 7'h38) begin + readback_data_var[31:22] = field_storage.chip1_base.value.value; + end + if(rd_mux_addr == 7'h3c) begin + readback_data_var[31:22] = field_storage.chip1_bound.value.value; + end + if(rd_mux_addr == 7'h40) begin + readback_data_var[31:22] = field_storage.chip2_base.value.value; + end + if(rd_mux_addr == 7'h44) begin + readback_data_var[31:22] = field_storage.chip2_bound.value.value; + end + if(rd_mux_addr == 7'h48) begin + readback_data_var[31:22] = field_storage.chip3_base.value.value; + end + if(rd_mux_addr == 7'h4c) begin + readback_data_var[31:22] = field_storage.chip3_bound.value.value; + end + if(rd_mux_addr == 7'h54) begin + readback_data_var[0] = field_storage.status.decode_error.value; + end + if(rd_mux_addr == 7'h58) begin + readback_data_var[31:22] = field_storage.chip4_base.value.value; + end + if(rd_mux_addr == 7'h5c) begin + readback_data_var[31:22] = field_storage.chip4_bound.value.value; + end + if(rd_mux_addr == 7'h60) begin + readback_data_var[31:22] = field_storage.chip5_base.value.value; + end + if(rd_mux_addr == 7'h64) begin + readback_data_var[31:22] = field_storage.chip5_bound.value.value; + end + if(rd_mux_addr == 7'h68) begin + readback_data_var[31:22] = field_storage.chip6_base.value.value; + end + if(rd_mux_addr == 7'h6c) begin + readback_data_var[31:22] = field_storage.chip6_bound.value.value; + end + if(rd_mux_addr == 7'h70) begin + readback_data_var[31:22] = field_storage.chip7_base.value.value; + end + if(rd_mux_addr == 7'h74) begin + readback_data_var[31:22] = field_storage.chip7_bound.value.value; + end + if(rd_mux_addr == 7'h78) begin + readback_data_var[7:0] = field_storage.phy_clock_div.value.value; + end + readback_data = readback_data_var; + readback_done = decoded_req & ~decoded_req_is_wr; + readback_err = '0; + end + + assign cpuif_rd_ack = readback_done; + assign cpuif_rd_data = readback_data; + assign cpuif_rd_err = readback_err; +endmodule diff --git a/src/regs/hyperbus_cfg_regblock_pkg.sv b/src/regs/hyperbus_cfg_regblock_pkg.sv new file mode 100644 index 0000000..1208c97 --- /dev/null +++ b/src/regs/hyperbus_cfg_regblock_pkg.sv @@ -0,0 +1,304 @@ +// Generated by PeakRDL-regblock - A free and open-source SystemVerilog generator +// https://github.com/SystemRDL/PeakRDL-regblock + +package hyperbus_cfg_regblock_pkg; + + localparam HYPERBUS_CFG_REGBLOCK_DATA_WIDTH = 32; + localparam HYPERBUS_CFG_REGBLOCK_MIN_ADDR_WIDTH = 7; + localparam HYPERBUS_CFG_REGBLOCK_SIZE = 'h7c; + + typedef struct { + logic hwset; + } hyperbus_cfg_regs__status__decode_error__in_t; + + typedef struct { + hyperbus_cfg_regs__status__decode_error__in_t decode_error; + } hyperbus_cfg_regs__status__in_t; + + typedef struct { + hyperbus_cfg_regs__status__in_t status; + } hyperbus_cfg_regs__in_t; + + typedef struct { + logic [3:0] value; + } hyperbus_cfg_regs__t_latency_access__value__out_t; + + typedef struct { + hyperbus_cfg_regs__t_latency_access__value__out_t value; + } hyperbus_cfg_regs__t_latency_access__out_t; + + typedef struct { + logic value; + } hyperbus_cfg_regs__en_latency_additional__value__out_t; + + typedef struct { + hyperbus_cfg_regs__en_latency_additional__value__out_t value; + } hyperbus_cfg_regs__en_latency_additional__out_t; + + typedef struct { + logic [15:0] value; + } hyperbus_cfg_regs__t_burst_max__value__out_t; + + typedef struct { + hyperbus_cfg_regs__t_burst_max__value__out_t value; + } hyperbus_cfg_regs__t_burst_max__out_t; + + typedef struct { + logic [3:0] value; + } hyperbus_cfg_regs__t_read_write_recovery__value__out_t; + + typedef struct { + hyperbus_cfg_regs__t_read_write_recovery__value__out_t value; + } hyperbus_cfg_regs__t_read_write_recovery__out_t; + + typedef struct { + logic [4:0] value; + } hyperbus_cfg_regs__t_rx_clk_delay__fine__out_t; + + typedef struct { + logic [2:0] value; + } hyperbus_cfg_regs__t_rx_clk_delay__coarse__out_t; + + typedef struct { + hyperbus_cfg_regs__t_rx_clk_delay__fine__out_t fine; + hyperbus_cfg_regs__t_rx_clk_delay__coarse__out_t coarse; + } hyperbus_cfg_regs__t_rx_clk_delay__out_t; + + typedef struct { + logic [4:0] value; + } hyperbus_cfg_regs__t_tx_clk_delay__fine__out_t; + + typedef struct { + logic [2:0] value; + } hyperbus_cfg_regs__t_tx_clk_delay__coarse__out_t; + + typedef struct { + hyperbus_cfg_regs__t_tx_clk_delay__fine__out_t fine; + hyperbus_cfg_regs__t_tx_clk_delay__coarse__out_t coarse; + } hyperbus_cfg_regs__t_tx_clk_delay__out_t; + + typedef struct { + logic [4:0] value; + } hyperbus_cfg_regs__address_mask_msb__value__out_t; + + typedef struct { + hyperbus_cfg_regs__address_mask_msb__value__out_t value; + } hyperbus_cfg_regs__address_mask_msb__out_t; + + typedef struct { + logic value; + } hyperbus_cfg_regs__address_space__value__out_t; + + typedef struct { + hyperbus_cfg_regs__address_space__value__out_t value; + } hyperbus_cfg_regs__address_space__out_t; + + typedef struct { + logic value; + } hyperbus_cfg_regs__dual_phy__value__out_t; + + typedef struct { + hyperbus_cfg_regs__dual_phy__value__out_t value; + } hyperbus_cfg_regs__dual_phy__out_t; + + typedef struct { + logic [3:0] value; + } hyperbus_cfg_regs__t_csh_cycles__value__out_t; + + typedef struct { + hyperbus_cfg_regs__t_csh_cycles__value__out_t value; + } hyperbus_cfg_regs__t_csh_cycles__out_t; + + typedef struct { + logic [3:0] value; + } hyperbus_cfg_regs__csn_to_ck_cycles__value__out_t; + + typedef struct { + hyperbus_cfg_regs__csn_to_ck_cycles__value__out_t value; + } hyperbus_cfg_regs__csn_to_ck_cycles__out_t; + + typedef struct { + logic [9:0] value; + } hyperbus_cfg_regs__chip0_base__value__out_t; + + typedef struct { + hyperbus_cfg_regs__chip0_base__value__out_t value; + } hyperbus_cfg_regs__chip0_base__out_t; + + typedef struct { + logic [9:0] value; + } hyperbus_cfg_regs__chip0_bound__value__out_t; + + typedef struct { + hyperbus_cfg_regs__chip0_bound__value__out_t value; + } hyperbus_cfg_regs__chip0_bound__out_t; + + typedef struct { + logic [9:0] value; + } hyperbus_cfg_regs__chip1_base__value__out_t; + + typedef struct { + hyperbus_cfg_regs__chip1_base__value__out_t value; + } hyperbus_cfg_regs__chip1_base__out_t; + + typedef struct { + logic [9:0] value; + } hyperbus_cfg_regs__chip1_bound__value__out_t; + + typedef struct { + hyperbus_cfg_regs__chip1_bound__value__out_t value; + } hyperbus_cfg_regs__chip1_bound__out_t; + + typedef struct { + logic [9:0] value; + } hyperbus_cfg_regs__chip2_base__value__out_t; + + typedef struct { + hyperbus_cfg_regs__chip2_base__value__out_t value; + } hyperbus_cfg_regs__chip2_base__out_t; + + typedef struct { + logic [9:0] value; + } hyperbus_cfg_regs__chip2_bound__value__out_t; + + typedef struct { + hyperbus_cfg_regs__chip2_bound__value__out_t value; + } hyperbus_cfg_regs__chip2_bound__out_t; + + typedef struct { + logic [9:0] value; + } hyperbus_cfg_regs__chip3_base__value__out_t; + + typedef struct { + hyperbus_cfg_regs__chip3_base__value__out_t value; + } hyperbus_cfg_regs__chip3_base__out_t; + + typedef struct { + logic [9:0] value; + } hyperbus_cfg_regs__chip3_bound__value__out_t; + + typedef struct { + hyperbus_cfg_regs__chip3_bound__value__out_t value; + } hyperbus_cfg_regs__chip3_bound__out_t; + + typedef struct { + logic value; + } hyperbus_cfg_regs__flush__request__out_t; + + typedef struct { + hyperbus_cfg_regs__flush__request__out_t request; + } hyperbus_cfg_regs__flush__out_t; + + typedef struct { + logic value; + } hyperbus_cfg_regs__status__decode_error__out_t; + + typedef struct { + hyperbus_cfg_regs__status__decode_error__out_t decode_error; + } hyperbus_cfg_regs__status__out_t; + + typedef struct { + logic [9:0] value; + } hyperbus_cfg_regs__chip4_base__value__out_t; + + typedef struct { + hyperbus_cfg_regs__chip4_base__value__out_t value; + } hyperbus_cfg_regs__chip4_base__out_t; + + typedef struct { + logic [9:0] value; + } hyperbus_cfg_regs__chip4_bound__value__out_t; + + typedef struct { + hyperbus_cfg_regs__chip4_bound__value__out_t value; + } hyperbus_cfg_regs__chip4_bound__out_t; + + typedef struct { + logic [9:0] value; + } hyperbus_cfg_regs__chip5_base__value__out_t; + + typedef struct { + hyperbus_cfg_regs__chip5_base__value__out_t value; + } hyperbus_cfg_regs__chip5_base__out_t; + + typedef struct { + logic [9:0] value; + } hyperbus_cfg_regs__chip5_bound__value__out_t; + + typedef struct { + hyperbus_cfg_regs__chip5_bound__value__out_t value; + } hyperbus_cfg_regs__chip5_bound__out_t; + + typedef struct { + logic [9:0] value; + } hyperbus_cfg_regs__chip6_base__value__out_t; + + typedef struct { + hyperbus_cfg_regs__chip6_base__value__out_t value; + } hyperbus_cfg_regs__chip6_base__out_t; + + typedef struct { + logic [9:0] value; + } hyperbus_cfg_regs__chip6_bound__value__out_t; + + typedef struct { + hyperbus_cfg_regs__chip6_bound__value__out_t value; + } hyperbus_cfg_regs__chip6_bound__out_t; + + typedef struct { + logic [9:0] value; + } hyperbus_cfg_regs__chip7_base__value__out_t; + + typedef struct { + hyperbus_cfg_regs__chip7_base__value__out_t value; + } hyperbus_cfg_regs__chip7_base__out_t; + + typedef struct { + logic [9:0] value; + } hyperbus_cfg_regs__chip7_bound__value__out_t; + + typedef struct { + hyperbus_cfg_regs__chip7_bound__value__out_t value; + } hyperbus_cfg_regs__chip7_bound__out_t; + + typedef struct { + logic [7:0] value; + } hyperbus_cfg_regs__phy_clock_div__value__out_t; + + typedef struct { + hyperbus_cfg_regs__phy_clock_div__value__out_t value; + } hyperbus_cfg_regs__phy_clock_div__out_t; + + typedef struct { + hyperbus_cfg_regs__t_latency_access__out_t t_latency_access; + hyperbus_cfg_regs__en_latency_additional__out_t en_latency_additional; + hyperbus_cfg_regs__t_burst_max__out_t t_burst_max; + hyperbus_cfg_regs__t_read_write_recovery__out_t t_read_write_recovery; + hyperbus_cfg_regs__t_rx_clk_delay__out_t t_rx_clk_delay; + hyperbus_cfg_regs__t_tx_clk_delay__out_t t_tx_clk_delay; + hyperbus_cfg_regs__address_mask_msb__out_t address_mask_msb; + hyperbus_cfg_regs__address_space__out_t address_space; + hyperbus_cfg_regs__dual_phy__out_t dual_phy; + hyperbus_cfg_regs__t_csh_cycles__out_t t_csh_cycles; + hyperbus_cfg_regs__csn_to_ck_cycles__out_t csn_to_ck_cycles; + hyperbus_cfg_regs__chip0_base__out_t chip0_base; + hyperbus_cfg_regs__chip0_bound__out_t chip0_bound; + hyperbus_cfg_regs__chip1_base__out_t chip1_base; + hyperbus_cfg_regs__chip1_bound__out_t chip1_bound; + hyperbus_cfg_regs__chip2_base__out_t chip2_base; + hyperbus_cfg_regs__chip2_bound__out_t chip2_bound; + hyperbus_cfg_regs__chip3_base__out_t chip3_base; + hyperbus_cfg_regs__chip3_bound__out_t chip3_bound; + hyperbus_cfg_regs__flush__out_t flush; + hyperbus_cfg_regs__status__out_t status; + hyperbus_cfg_regs__chip4_base__out_t chip4_base; + hyperbus_cfg_regs__chip4_bound__out_t chip4_bound; + hyperbus_cfg_regs__chip5_base__out_t chip5_base; + hyperbus_cfg_regs__chip5_bound__out_t chip5_bound; + hyperbus_cfg_regs__chip6_base__out_t chip6_base; + hyperbus_cfg_regs__chip6_bound__out_t chip6_bound; + hyperbus_cfg_regs__chip7_base__out_t chip7_base; + hyperbus_cfg_regs__chip7_bound__out_t chip7_bound; + hyperbus_cfg_regs__phy_clock_div__out_t phy_clock_div; + } hyperbus_cfg_regs__out_t; +endpackage diff --git a/src/regs/hyperbus_cfg_regs.rdl b/src/regs/hyperbus_cfg_regs.rdl new file mode 100644 index 0000000..2c2e108 --- /dev/null +++ b/src/regs/hyperbus_cfg_regs.rdl @@ -0,0 +1,223 @@ +addrmap hyperbus_cfg_regs { + desc = "HyperBus controller configuration. Chip address ranges must be 4 MiB aligned, ordered by chip-select index, and non-overlapping."; + + reg t_latency_access { + desc = "Initial latency cycles before read or write data."; + field { sw = rw; hw = r; reset = 4'h6; } value[3:0]; + }; + + reg en_latency_additional { + desc = "Enable additional latency cycles when requested by RWDS."; + field { sw = rw; hw = r; reset = 1'b0; } value; + }; + + reg t_burst_max { + desc = "Maximum continuous burst length before the PHY restarts a transfer."; + field { sw = rw; hw = r; reset = 16'd350; } value[15:0]; + }; + + reg t_read_write_recovery { + desc = "Recovery cycles inserted between read and write phases."; + field { sw = rw; hw = r; reset = 4'h6; } value[3:0]; + }; + + reg t_rx_clk_delay { + desc = "RX sampling delay with fine taps in bits 4:0 and coarse taps in bits 7:5."; + field { + desc = "Fine delay-line tap setting, nominally about 78 ps per tap."; + sw = rw; + hw = r; + reset = 5'h10; + } fine[4:0]; + field { + desc = "Coarse delay-line tap setting with an implementation-defined step size."; + sw = rw; + hw = r; + reset = 3'h0; + } coarse[7:5]; + }; + + reg t_tx_clk_delay { + desc = "TX clock delay with fine taps in bits 4:0 and coarse taps in bits 7:5."; + field { + desc = "Fine delay-line tap setting, nominally about 78 ps per tap."; + sw = rw; + hw = r; + reset = 5'h10; + } fine[4:0]; + field { + desc = "Coarse delay-line tap setting with an implementation-defined step size."; + sw = rw; + hw = r; + reset = 3'h0; + } coarse[7:5]; + }; + + reg phy_clock_div { + desc = "System-to-PHY clock division factor used by the isochronous top."; + field { + desc = "Must be at least two. Ignored by synchronous and asynchronous tops."; + sw = rw; + hw = r; + reset = 8'd8; + } value[7:0]; + }; + + reg address_mask_msb { + desc = "Most-significant address bit used by frontend address packing."; + field { sw = rw; hw = r; reset = 5'd25; } value[4:0]; + }; + + reg address_space { + desc = "Select HyperRAM or HyperFlash address-space interpretation."; + field { sw = rw; hw = r; reset = 1'b0; } value; + }; + + reg dual_phy { + desc = "Enable both PHY lanes; when clear, only PHY lane 0 is active."; + field { sw = rw; hw = r; reset = 1'b1; } value; + }; + + reg t_csh_cycles { + desc = "Chip-select high time between transfers."; + field { sw = rw; hw = r; reset = 4'h1; } value[3:0]; + }; + + reg csn_to_ck_cycles { + desc = "Delay cycles from chip-select assertion to clock start."; + field { sw = rw; hw = r; reset = 4'h0; } value[3:0]; + }; + + reg chip0_base { + desc = "Inclusive 4 MiB-aligned address for chip-select 0 decoding."; + field { sw = rw; hw = r; reset = 10'd0; } value[31:22]; + }; + + reg chip0_bound { + desc = "Exclusive 4 MiB-aligned address for chip-select 0 decoding."; + field { sw = rw; hw = r; reset = 10'd1; } value[31:22]; + }; + + reg chip1_base { + desc = "Inclusive 4 MiB-aligned address for chip-select 1 decoding."; + field { sw = rw; hw = r; reset = 10'd1; } value[31:22]; + }; + + reg chip1_bound { + desc = "Exclusive 4 MiB-aligned address for chip-select 1 decoding."; + field { sw = rw; hw = r; reset = 10'd2; } value[31:22]; + }; + + reg chip2_base { + desc = "Inclusive 4 MiB-aligned address for chip-select 2 decoding."; + field { sw = rw; hw = r; reset = 10'd2; } value[31:22]; + }; + + reg chip2_bound { + desc = "Exclusive 4 MiB-aligned address for chip-select 2 decoding."; + field { sw = rw; hw = r; reset = 10'd3; } value[31:22]; + }; + + reg chip3_base { + desc = "Inclusive 4 MiB-aligned address for chip-select 3 decoding."; + field { sw = rw; hw = r; reset = 10'd3; } value[31:22]; + }; + + reg chip3_bound { + desc = "Exclusive 4 MiB-aligned address for chip-select 3 decoding."; + field { sw = rw; hw = r; reset = 10'd4; } value[31:22]; + }; + + reg chip4_base { + desc = "Inclusive 4 MiB-aligned address for chip-select 4 decoding."; + field { sw = rw; hw = r; reset = 10'd4; } value[31:22]; + }; + + reg chip4_bound { + desc = "Exclusive 4 MiB-aligned address for chip-select 4 decoding."; + field { sw = rw; hw = r; reset = 10'd5; } value[31:22]; + }; + + reg chip5_base { + desc = "Inclusive 4 MiB-aligned address for chip-select 5 decoding."; + field { sw = rw; hw = r; reset = 10'd5; } value[31:22]; + }; + + reg chip5_bound { + desc = "Exclusive 4 MiB-aligned address for chip-select 5 decoding."; + field { sw = rw; hw = r; reset = 10'd6; } value[31:22]; + }; + + reg chip6_base { + desc = "Inclusive 4 MiB-aligned address for chip-select 6 decoding."; + field { sw = rw; hw = r; reset = 10'd6; } value[31:22]; + }; + + reg chip6_bound { + desc = "Exclusive 4 MiB-aligned address for chip-select 6 decoding."; + field { sw = rw; hw = r; reset = 10'd7; } value[31:22]; + }; + + reg chip7_base { + desc = "Inclusive 4 MiB-aligned address for chip-select 7 decoding."; + field { sw = rw; hw = r; reset = 10'd7; } value[31:22]; + }; + + reg chip7_bound { + desc = "Exclusive 4 MiB-aligned address for chip-select 7 decoding."; + field { sw = rw; hw = r; reset = 10'd8; } value[31:22]; + }; + + reg flush { + desc = "Write to stop accepting requests and drain all in-flight HyperBus operations."; + field { + desc = "Writing any value executes the ordering barrier; the value is not stored."; + sw = w; + hw = r; + } request; + }; + + reg status { + desc = "Sticky protocol and address error status."; + field { + desc = "A host request did not match any configured chip address range."; + sw = rw; + hw = r; + woclr; + hwset; + precedence = hw; + reset = 1'b0; + } decode_error; + }; + + t_latency_access t_latency_access @ 0x00; + en_latency_additional en_latency_additional @ 0x04; + t_burst_max t_burst_max @ 0x08; + t_read_write_recovery t_read_write_recovery @ 0x0c; + t_rx_clk_delay t_rx_clk_delay @ 0x10; + t_tx_clk_delay t_tx_clk_delay @ 0x14; + address_mask_msb address_mask_msb @ 0x18; + address_space address_space @ 0x1c; + dual_phy dual_phy @ 0x20; + t_csh_cycles t_csh_cycles @ 0x28; + csn_to_ck_cycles csn_to_ck_cycles @ 0x2c; + chip0_base chip0_base @ 0x30; + chip0_bound chip0_bound @ 0x34; + chip1_base chip1_base @ 0x38; + chip1_bound chip1_bound @ 0x3c; + chip2_base chip2_base @ 0x40; + chip2_bound chip2_bound @ 0x44; + chip3_base chip3_base @ 0x48; + chip3_bound chip3_bound @ 0x4c; + flush flush @ 0x50; + status status @ 0x54; + chip4_base chip4_base @ 0x58; + chip4_bound chip4_bound @ 0x5c; + chip5_base chip5_base @ 0x60; + chip5_bound chip5_bound @ 0x64; + chip6_base chip6_base @ 0x68; + chip6_bound chip6_bound @ 0x6c; + chip7_base chip7_base @ 0x70; + chip7_bound chip7_bound @ 0x74; + phy_clock_div phy_clock_div @ 0x78; +}; diff --git a/test/axi_hyper_tb.sv b/test/axi_hyper_tb.sv index fcd42cd..5bb42d2 100644 --- a/test/axi_hyper_tb.sv +++ b/test/axi_hyper_tb.sv @@ -7,7 +7,7 @@ module axi_hyper_tb import axi_pkg::*; #( - parameter int unsigned NumChips = 2, + parameter int unsigned NumConnectedChips = 2, parameter int unsigned NumPhys = 2, parameter int unsigned IsClockODelayed = 0, parameter int unsigned NB_CH = 1, @@ -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::*; ///////////////////////////// @@ -60,6 +78,23 @@ module axi_hyper_tb 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 /////////////////////// @@ -259,6 +294,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, @@ -352,6 +398,705 @@ 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_midend_buffering(input axi_ctrl_master_t axi_drv); + localparam int unsigned NumTransactions = 4; + localparam int unsigned WriteBeats = 4; + localparam axi_addr_t BufferBaseAddr = axi_addr_t'(32'h8000_2000); + axi_ctrl_master_t::ax_beat_t ax; + axi_ctrl_master_t::w_beat_t w; + axi_ctrl_master_t::b_beat_t b; + axi_ctrl_master_t::r_beat_t r; + axi_addr_t transaction_addr; + + $display("==========================="); + $display("= Midend queue buffering ="); + $display("==========================="); + + // Fill all write contexts and the 128-byte W buffer before accepting responses. + for (int unsigned transaction = 0; transaction < NumTransactions; transaction++) begin + ax = new(); + ax.ax_addr = BufferBaseAddr + axi_addr_t'(transaction * WriteBeats * + (TbAxiDataWidthFull / 8)); + ax.ax_id = TbAxiIdWidthFull'(transaction + 1); + ax.ax_len = WriteBeats - 1; + ax.ax_size = $clog2(TbAxiDataWidthFull / 8); + ax.ax_burst = axi_pkg::BURST_INCR; + axi_drv.send_aw(ax); + end + + for (int unsigned transaction = 0; transaction < NumTransactions; transaction++) begin + transaction_addr = BufferBaseAddr + axi_addr_t'(transaction * WriteBeats * + (TbAxiDataWidthFull / 8)); + for (int unsigned beat = 0; beat < WriteBeats; beat++) begin + w = new(); + w.w_data = slow_stress_data(transaction_addr, beat); + w.w_strb = '1; + w.w_last = beat == WriteBeats - 1; + axi_drv.send_w(w); + end + end + + for (int unsigned transaction = 0; transaction < NumTransactions; transaction++) begin + axi_drv.recv_b(b); + if ((b.b_resp != axi_pkg::RESP_OKAY) || + (b.b_id != TbAxiIdWidthFull'(transaction + 1))) begin + $error("[MIDEND-BUFFER] Write %0d returned id=%0d resp=%0d", + transaction, b.b_id, b.b_resp); + end + end + + // Four one-beat reads can occupy all read contexts and response entries. + for (int unsigned transaction = 0; transaction < NumTransactions; transaction++) begin + ax = new(); + ax.ax_addr = BufferBaseAddr + axi_addr_t'(transaction * WriteBeats * + (TbAxiDataWidthFull / 8)); + ax.ax_id = TbAxiIdWidthFull'(transaction + 1); + ax.ax_len = '0; + ax.ax_size = $clog2(TbAxiDataWidthFull / 8); + ax.ax_burst = axi_pkg::BURST_INCR; + axi_drv.send_ar(ax); + end + + for (int unsigned transaction = 0; transaction < NumTransactions; transaction++) begin + axi_drv.recv_r(r); + transaction_addr = BufferBaseAddr + axi_addr_t'(transaction * WriteBeats * + (TbAxiDataWidthFull / 8)); + if ((r.r_resp != axi_pkg::RESP_OKAY) || !r.r_last || + (r.r_id != TbAxiIdWidthFull'(transaction + 1)) || + (r.r_data != slow_stress_data(transaction_addr, 0))) begin + $error("[MIDEND-BUFFER] Read %0d returned id=%0d data=0x%016x last=%0b resp=%0d", + transaction, r.r_id, r.r_data, r.r_last, r.r_resp); + end + end + endtask + + task automatic run_performance_smoke(input axi_ctrl_master_t axi_drv); + localparam int unsigned NumCases = 5; + localparam axi_addr_t PerfBaseAddr = axi_addr_t'(32'h8000_8000); + int unsigned burst_beats [NumCases] = '{1, 4, 16, 64, 256}; + int unsigned write_baseline [NumCases]; + int unsigned read_baseline [NumCases]; + int unsigned segment_limit [NumCases]; + logic [63:0] write_cycles [NumCases]; + logic [63:0] read_cycles [NumCases]; + logic [63:0] cycle_snapshot; + logic [31:0] segment_snapshot; + logic [31:0] write_segments; + logic [31:0] read_segments; + axi_addr_t burst_addr; + int unsigned write_cycle_limit; + int unsigned read_cycle_limit; + real backend_cycles_per_system_cycle; + real write_bits_per_phy_cycle; + real read_bits_per_phy_cycle; + + backend_cycles_per_system_cycle = 1.0; + if (TbDutVariant == 0) begin + backend_cycles_per_system_cycle = 0.5; + end else if (TbDutVariant == 2) begin + backend_cycles_per_system_cycle = real'(TbCyclTime) / real'(TbPhyCyclTime); + end + + // Default-configuration baselines in system-clock cycles for each supported top. + write_baseline = '{44, 55, 103, 295, 1103}; + read_baseline = '{59, 71, 119, 311, 1119}; + segment_limit = '{1, 1, 1, 1, 2}; + if (TbDutVariant == 1) begin + write_baseline = '{22, 28, 52, 148, 552}; + read_baseline = '{29, 35, 59, 155, 559}; + end else if (TbDutVariant == 2) begin + write_baseline = '{36, 42, 71, 186, 671}; + read_baseline = '{43, 49, 78, 193, 678}; + end + if (NumPhys == 1) begin + write_baseline = '{24, 36, 84, 276, 1084}; + read_baseline = '{30, 42, 90, 282, 1090}; + segment_limit = '{1, 1, 1, 1, 3}; + end + + $display("==========================="); + $display("= AXI performance smoke ="); + $display("==========================="); + + for (int unsigned test_idx = 0; test_idx < NumCases; test_idx++) begin + burst_addr = PerfBaseAddr + axi_addr_t'(test_idx * 32'h1000); + + cycle_snapshot = cycle_count; + segment_snapshot = segment_start_count; + axi_write_slow(axi_drv, burst_addr, burst_beats[test_idx], 0); + write_cycles[test_idx] = cycle_count - cycle_snapshot; + write_segments = segment_start_count - segment_snapshot; + + cycle_snapshot = cycle_count; + segment_snapshot = segment_start_count; + axi_read_slow_check(axi_drv, burst_addr, burst_beats[test_idx], 0); + read_cycles[test_idx] = cycle_count - cycle_snapshot; + read_segments = segment_start_count - segment_snapshot; + + $display("[PERF] variant=%0d phys=%0d beats=%0d write_cycles=%0d read_cycles=%0d write_segments=%0d read_segments=%0d", + TbDutVariant, NumPhys, burst_beats[test_idx], write_cycles[test_idx], + read_cycles[test_idx], write_segments, read_segments); + + if (burst_beats[test_idx] == 256) begin + write_bits_per_phy_cycle = real'(burst_beats[test_idx] * TbAxiDataWidthFull) / + real'(NumPhys * write_cycles[test_idx]) / backend_cycles_per_system_cycle; + read_bits_per_phy_cycle = real'(burst_beats[test_idx] * TbAxiDataWidthFull) / + real'(NumPhys * read_cycles[test_idx]) / backend_cycles_per_system_cycle; + $display("[PERF-MAX] variant=%0d write_bits_per_phy_cycle=%0.3f read_bits_per_phy_cycle=%0.3f", + TbDutVariant, write_bits_per_phy_cycle, read_bits_per_phy_cycle); + end + + write_cycle_limit = write_baseline[test_idx] + write_baseline[test_idx] / 5 + 2; + read_cycle_limit = read_baseline[test_idx] + read_baseline[test_idx] / 5 + 2; + if (write_cycles[test_idx] > write_cycle_limit) begin + $error("[PERF] %0d-beat write took %0d cycles, limit is %0d", + burst_beats[test_idx], write_cycles[test_idx], write_cycle_limit); + end + if (read_cycles[test_idx] > read_cycle_limit) begin + $error("[PERF] %0d-beat read took %0d cycles, limit is %0d", + burst_beats[test_idx], read_cycles[test_idx], read_cycle_limit); + end + if ((write_segments != segment_limit[test_idx]) || + (read_segments != segment_limit[test_idx])) begin + $error("[PERF] %0d-beat burst used unexpected segments: write=%0d read=%0d limit=%0d", + burst_beats[test_idx], write_segments, read_segments, + segment_limit[test_idx]); + end + end + + for (int unsigned test_idx = 1; test_idx < NumCases; test_idx++) begin + if ((write_cycles[test_idx] * burst_beats[test_idx-1]) > + (write_cycles[test_idx-1] * burst_beats[test_idx])) begin + $error("[PERF] Write cycles per beat did not improve from %0d to %0d beats", + burst_beats[test_idx-1], burst_beats[test_idx]); + end + if ((read_cycles[test_idx] * burst_beats[test_idx-1]) > + (read_cycles[test_idx-1] * burst_beats[test_idx])) begin + $error("[PERF] Read cycles per beat did not improve from %0d to %0d beats", + burst_beats[test_idx-1], burst_beats[test_idx]); + end + end + endtask + + task automatic run_slow_backpressure_test( + input axi_ctrl_master_t axi_drv, + input reg_bus_master_t reg_drv + ); + localparam axi_addr_t SlowBaseAddr = axi_addr_t'(32'h8000_4000); + logic [RegBusDW-1:0] saved_t_burst_max; + logic [31:0] write_segment_starts; + logic [31:0] read_segment_starts; + logic [31:0] segment_start_snapshot; + logic reg_error; + + $display("==========================="); + $display("= Slow AXI backpressure ="); + $display("==========================="); + + reg_drv.send_read(32'h2 << 2, saved_t_burst_max, reg_error); + if (reg_error != 1'b0) $error("unexpected error"); + + reg_drv.send_write(32'h2 << 2, TbSlowBurstMax, '1, reg_error); + if (reg_error != 1'b0) $error("unexpected error"); + + segment_start_snapshot = segment_start_count; + axi_write_slow(axi_drv, SlowBaseAddr, TbSlowNumBeats, TbSlowGapCycles); + write_segment_starts = segment_start_count - segment_start_snapshot; + if (write_segment_starts <= 1) begin + $error("[AXI-SLOW] Write observed %0d HyperBus segment start(s), expected at least one restart", + write_segment_starts); + end else begin + $display("[AXI-SLOW] Write observed %0d HyperBus segment starts (%0d restarts)", + write_segment_starts, write_segment_starts - 1); + end + + segment_start_snapshot = segment_start_count; + axi_read_slow_check(axi_drv, SlowBaseAddr, TbSlowNumBeats, TbSlowGapCycles); + read_segment_starts = segment_start_count - segment_start_snapshot; + if (read_segment_starts <= 1) begin + $error("[AXI-SLOW] Read observed %0d HyperBus segment start(s), expected at least one restart", + read_segment_starts); + end else begin + $display("[AXI-SLOW] Read observed %0d HyperBus segment starts (%0d restarts)", + read_segment_starts, read_segment_starts - 1); + end + + reg_drv.send_write(32'h2 << 2, saved_t_burst_max, '1, reg_error); + if (reg_error != 1'b0) $error("unexpected error"); + 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 flush_done_time; + + write_done_time = 0; + flush_done_time = 0; + $display("==========================="); + $display("= Config flush barrier ="); + $display("==========================="); + + fork + begin + axi_write_slow(axi_drv, BarrierAddr, 16, 16); + write_done_time = $time; + end + begin + repeat (32) @(posedge clk); + reg_drv.send_write(32'h50, '0, '1, reg_error); + flush_done_time = $time; + if (reg_error != 1'b0) begin + $error("[CFG-BARRIER] Flush register write returned an error"); + end + end + join + + if ((write_done_time == 0) || (flush_done_time < write_done_time)) begin + $error("[CFG-BARRIER] Flush completed at %0t before AXI write completed at %0t", + flush_done_time, write_done_time); + 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'h54, status, reg_error); + if ((reg_error != 1'b0) || !status[0]) begin + $error("[DECODE] Sticky decode-error status was not set"); + end + reg_drv.send_write(32'h54, 32'h1, '1, reg_error); + if (reg_error != 1'b0) $error("unexpected error"); + reg_drv.send_read(32'h54, status, reg_error); + if ((reg_error != 1'b0) || status[0]) begin + $error("[DECODE] Sticky decode-error status did not clear"); + end + 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 (NumConnectedChips < 2) begin + return; + end + + $display("==========================="); + $display("= Cross-chip burst ="); + $display("==========================="); + + reg_drv.send_write(32'h34, Boundary, '1, reg_error); + if (reg_error != 1'b0) $error("unexpected error"); + reg_drv.send_write(32'h38, Boundary, '1, reg_error); + if (reg_error != 1'b0) $error("unexpected error"); + + segment_start_snapshot = segment_start_count; + 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'h38, 32'h8100_0000, '1, reg_error); + if (reg_error != 1'b0) $error("unexpected error"); + reg_drv.send_write(32'h34, 32'h8100_0000, '1, reg_error); + 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 (NumConnectedChips < 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'h3c, '0, '1, reg_error); + if (reg_error != 1'b0) $error("unexpected error"); + axi_write_subword(axi_drv, ValidAddr, TestData, 3); + axi_check_subword(axi_drv, ValidAddr, TestData, 3); + + // 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'h3c, 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, + input reg_bus_master_t reg_drv + ); + localparam axi_addr_t AtomicAddr = axi_addr_t'(32'h8000_0200); + localparam logic [31:0] InitialValue = 32'h1234_5678; + localparam logic [31:0] Addend = 32'h0102_0304; + 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("= 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 + reg_drv.send_read(32'h54, status, reg_error); + if ((reg_error != 1'b0) || !status[0]) begin + $error("[ATOMIC] Unsupported operation did not set sticky error status"); + end + reg_drv.send_write(32'h54, 32'h1, '1, reg_error); + if (reg_error != 1'b0) $error("unexpected error"); + axi_check_subword(axi_drv, AtomicAddr, InitialValue + Addend, 2); + + // A malformed multi-beat atomic must drain all W beats before returning an error. + 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 + reg_drv.send_read(32'h54, status, reg_error); + if ((reg_error != 1'b0) || !status[0]) begin + $error("[ATOMIC] Malformed operation did not set sticky error status"); + end + reg_drv.send_write(32'h54, 32'h1, '1, reg_error); + if (reg_error != 1'b0) $error("unexpected error"); + axi_write_subword(axi_drv, AtomicAddr, 32'h89ab_cdef, 2); + axi_check_subword(axi_drv, AtomicAddr, 32'h89ab_cdef, 2); + endtask + + 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 (NumConnectedChips < 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'h3c, '0, '1, reg_error); + if (reg_error != 1'b0) $error("unexpected error"); + axi_write_subword(axi_drv, ZeroEndAddr, ZeroEndInitial, 2); + ax.ax_addr = ZeroEndAddr; + 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'h3c, 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 ); @@ -359,6 +1104,11 @@ 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; + automatic logic [31:0] iso_saved_t_burst_max; + automatic int unsigned iso_test_dividers[4] = '{2, 4, 8, 16}; // Reset the AXI drivers and scoreboards end_of_sim = 1'b0; @@ -375,19 +1125,108 @@ module axi_hyper_tb @(posedge rst_n); mst_scoreboard.monitor(); - #600350ns; + // Map each chip to a distinct 16 MiB host-address window. + if (NumConnectedChips > 1) begin + reg_master.send_write(32'h3c, 32'h8200_0000, '1, s_reg_error); + if (s_reg_error != 1'b0) $error("unexpected error"); + reg_master.send_write(32'h38, 32'h8100_0000, '1, s_reg_error); + if (s_reg_error != 1'b0) $error("unexpected error"); + end + reg_master.send_write(32'h34, 32'h8100_0000, '1, s_reg_error); + if (s_reg_error != 1'b0) $error("unexpected error"); + reg_master.send_write(32'h30, 32'h8000_0000, '1, s_reg_error); + if (s_reg_error != 1'b0) $error("unexpected error"); - // switch memory address space to register space - reg_master.send_write(32'h7<<2, 1'b1, '1, s_reg_error); + reg_master.send_write(32'h4 << 2, TbRxDelayLineTaps, '1, s_reg_error); + if (s_reg_error != 1'b0) $error("unexpected error"); + reg_master.send_write(32'h5 << 2, 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'h78, reg_read, s_reg_error); + if ((s_reg_error != 1'b0) || (reg_read != 8)) $error("unexpected divider reset value"); + reg_master.send_write(32'h78, 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(32'h7<<2, 1'b0, '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'h78, 8'd4, '1, s_reg_error); + if (s_reg_error != 1'b0) $error("unexpected error"); + reg_master.send_read(32'h78, reg_read, s_reg_error); + if ((s_reg_error != 1'b0) || (reg_read != 4)) $error("divider update failed"); + + divider_cycle_snapshot = cycle_count; + axi_write_slow(axi_ctrl_mst, 32'h8000_7000, 4, 0); + div4_write_cycles = cycle_count - divider_cycle_snapshot; + + reg_master.send_write(32'h78, 8'd2, '1, s_reg_error); + if (s_reg_error != 1'b0) $error("unexpected error"); + reg_master.send_read(32'h78, reg_read, s_reg_error); + if ((s_reg_error != 1'b0) || (reg_read != 2)) $error("divider restore failed"); + + divider_cycle_snapshot = cycle_count; + 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_midend_buffering(axi_ctrl_mst); + run_performance_smoke(axi_ctrl_mst); + run_slow_backpressure_test(axi_ctrl_mst, reg_master); + check_config_barrier(axi_ctrl_mst, reg_master); + 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, reg_master); + check_atomic_range_errors(axi_ctrl_mst, reg_master); + + if (TbDutVariant == 0) begin + $display("==========================="); + $display("= Isochronous backpressure ="); + $display("==========================="); + reg_master.send_read(32'h8, iso_saved_t_burst_max, s_reg_error); + if (s_reg_error != 1'b0) $error("unexpected t_burst_max read error"); + foreach (iso_test_dividers[i]) begin + // Keep each data phase near 2 us, leaving margin for CA and access latency. + reg_master.send_write(32'h8, 400 / iso_test_dividers[i], '1, s_reg_error); + if (s_reg_error != 1'b0) $error("unexpected t_burst_max update error"); + reg_master.send_write(32'h78, iso_test_dividers[i], '1, s_reg_error); + if (s_reg_error != 1'b0) $error("unexpected divider update error"); + axi_write_slow(axi_ctrl_mst, 32'h8001_0000 + i * 32'h100, 16, 0); + axi_read_slow_check(axi_ctrl_mst, 32'h8001_0000 + i * 32'h100, 16, 128); + end + + reg_master.send_write(32'h78, 8'd2, '1, s_reg_error); + if (s_reg_error != 1'b0) $error("unexpected divider restore error"); + reg_master.send_write(32'h8, iso_saved_t_burst_max, '1, s_reg_error); + if (s_reg_error != 1'b0) $error("unexpected t_burst_max restore error"); + end + + 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'h7<<2, 1'b1, '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'h7<<2, 1'b0, '1, s_reg_error); + if (s_reg_error != 1'b0) $error("unexpected error"); + end check_consecutive_reads(axi_ctrl_mst); @@ -412,30 +1251,6 @@ module axi_hyper_tb $display("==========================="); reg_master.send_write(32'h20,1'b0,'1,s_reg_error); - reg_master.send_write(32'h24,1'b0,'1,s_reg_error); - if (s_reg_error != 1'b0) $error("unexpected error"); - - axi_rand_mst.reset(); - axi_ctrl_mst.reset_master(); - check_odd_subword_accesses(axi_ctrl_mst); - - $display("==========================="); - $display("= Random AXI transactions ="); - $display("==========================="); - - axi_rand_mst.run(TbNumReads, TbNumWrites); - - $display("==========================="); - $display("= Test finished ="); - $display("==========================="); - - mst_scoreboard.clear_range(32'h8000_0000, 32'h8000_0000 + ( TbDramDataWidth * TbDramLenWidth )); - - $display("==========================="); - $display("= Use only phy 1 ="); - $display("==========================="); - - reg_master.send_write(32'h24,1'b1,'1,s_reg_error); if (s_reg_error != 1'b0) $error("unexpected error"); axi_rand_mst.reset(); @@ -471,17 +1286,63 @@ module axi_hyper_tb .RegAw ( RegBusAW ), .RegDw ( RegBusDW ), - .NumChips ( NumChips ), + .NumConnectedChips ( NumConnectedChips ), .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 232b541..d7485e8 100644 --- a/test/dut_if.sv +++ b/test/dut_if.sv @@ -22,14 +22,18 @@ module dut_if parameter int RegAw = -1, parameter int RegDw = -1, - parameter int NumChips = -1, + parameter int NumConnectedChips = 2, parameter int NumPhys = -1, + parameter bit AnnotateSdf = 1'b1, parameter int IsClockODelayed = -1, + 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 @@ -70,7 +74,7 @@ module dut_if `REG_BUS_ASSIGN_FROM_RSP(reg_slv_if,reg_resp) - logic [NumPhys-1:0][NumChips-1:0] hyper_cs_n_wire; + logic [NumPhys-1:0][hyperbus_pkg::HyperNumChips-1:0] hyper_cs_n_wire; logic [NumPhys-1:0] hyper_ck_wire; logic [NumPhys-1:0] hyper_ck_n_wire; logic [NumPhys-1:0] hyper_rwds_o; @@ -80,8 +84,11 @@ 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][hyperbus_pkg::HyperNumChips-1:0] hyper_cs_n_q; + logic segment_start; - wire [NumPhys-1:0][NumChips-1:0] pad_hyper_csn; + wire [NumPhys-1:0][NumConnectedChips-1:0] pad_hyper_csn; wire [NumPhys-1:0] pad_hyper_ck; wire [NumPhys-1:0] pad_hyper_ckn; wire [NumPhys-1:0] pad_hyper_rwds; @@ -122,9 +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 #( - .NumChips ( NumChips ), + hyperbus_test_dut #( + .DutVariant ( DutVariant ), .NumPhys ( NumPhys ), .AxiAddrWidth ( AxiAddrWidth ), .AxiDataWidth ( AxiDataWidth ), @@ -132,22 +161,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 ), - .RegAddrWidth ( RegAw ), .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 ), @@ -170,7 +195,7 @@ module dut_if generate for (genvar i=0; i