diff --git a/src/axi/config/compile.yml b/src/axi/config/compile.yml new file mode 100644 index 000000000..57774c8d0 --- /dev/null +++ b/src/axi/config/compile.yml @@ -0,0 +1,22 @@ +--- +provides: [axi_sub] +schema_version: 2.4.0 +requires: + - libs +targets: + tb: + directories: [$COMPILE_ROOT/rtl] + files: + - $COMPILE_ROOT/rtl/axi_if.sv + - $COMPILE_ROOT/rtl/axi_pkg.sv + - $COMPILE_ROOT/rtl/axi_addr.v + - $COMPILE_ROOT/rtl/skidbuffer.v + - $COMPILE_ROOT/rtl/axi_sub_rd.sv + rtl: + directories: [$COMPILE_ROOT/rtl] + files: + - $COMPILE_ROOT/rtl/axi_if.sv + - $COMPILE_ROOT/rtl/axi_pkg.sv + - $COMPILE_ROOT/rtl/axi_addr.v + - $COMPILE_ROOT/rtl/skidbuffer.v + - $COMPILE_ROOT/rtl/axi_sub_rd.sv diff --git a/src/axi/rtl/axi_addr.v b/src/axi/rtl/axi_addr.v new file mode 100644 index 000000000..8b38874f7 --- /dev/null +++ b/src/axi/rtl/axi_addr.v @@ -0,0 +1,235 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Filename: axi_addr.v +// {{{ +// Project: WB2AXIPSP: bus bridges and other odds and ends +// +// Purpose: The AXI (full) standard has some rather complicated addressing +// modes, where the address can either be FIXED, INCRementing, or +// even where it can WRAP around some boundary. When in either INCR or +// WRAP modes, the next address must always be aligned. In WRAP mode, +// the next address calculation needs to wrap around a given value, and +// that value is dependent upon the burst size (i.e. bytes per beat) and +// length (total numbers of beats). Since this calculation can be +// non-trivial, and since it needs to be done multiple times, the logic +// below captures it for every time it might be needed. +// +// 20200918 - modified to accommodate (potential) AXI3 burst lengths +// +// Creator: Dan Gisselquist, Ph.D. +// Gisselquist Technology, LLC +// +//////////////////////////////////////////////////////////////////////////////// +// }}} +// Copyright (C) 2019-2024, Gisselquist Technology, LLC +// {{{ +// This file is part of the WB2AXIP project. +// +// The WB2AXIP project contains free software and gateware, licensed under the +// Apache License, Version 2.0 (the "License"). You may not use this project, +// or this file, except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. +// +//////////////////////////////////////////////////////////////////////////////// +// +// +`default_nettype none +// }}} +module axi_addr #( + // {{{ + parameter AW = 32, + DW = 32, + // parameter [0:0] OPT_AXI3 = 1'b0, + localparam LENB = 8 + // }}} + ) ( + // {{{ + input wire [AW-1:0] i_last_addr, + input wire [2:0] i_size, // 1b, 2b, 4b, 8b, etc + input wire [1:0] i_burst, // fixed, incr, wrap, reserved + input wire [LENB-1:0] i_len, + output wire [AW-1:0] o_next_addr + // }}} + ); + + // Parameter/register declarations + // {{{ + localparam DSZ = $clog2(DW)-3; + localparam [1:0] FIXED = 2'b00; + // localparam [1:0] INCREMENT = 2'b01; + // localparam [1:0] WRAP = 2'b10; + localparam IN_AW = (AW >= 12) ? 12 : AW; + localparam [IN_AW-1:0] ONE = 1; + + reg [IN_AW-1:0] wrap_mask, increment; + reg [IN_AW-1:0] crossblk_addr, aligned_addr, unaligned_addr; + // }}} + + // Address increment + // {{{ + always @(*) + if (DSZ == 0) + increment = 1; + else if (DSZ == 1) + increment = (i_size[0]) ? 2 : 1; + else if (DSZ == 2) + increment = (i_size[1]) ? 4 : ((i_size[0]) ? 2 : 1); + else if (DSZ == 3) + case(i_size[1:0]) + 2'b00: increment = 1; + 2'b01: increment = 2; + 2'b10: increment = 4; + 2'b11: increment = 8; + endcase + else + increment = (1<1) ? 1 : (AW-1):0]= 0; + 2'b11: aligned_addr[(AW-1>2) ? 2 : (AW-1):0]= 0; + endcase + // }}} + end else begin + // {{{ + // Align any subsequent address + case(i_size) + 3'b001: aligned_addr[ 0] = 0; + 3'b010: aligned_addr[(AW-1>1) ? 1 : (AW-1):0]=0; + 3'b011: aligned_addr[(AW-1>2) ? 2 : (AW-1):0]=0; + 3'b100: aligned_addr[(AW-1>3) ? 3 : (AW-1):0]=0; + 3'b101: aligned_addr[(AW-1>4) ? 4 : (AW-1):0]=0; + 3'b110: aligned_addr[(AW-1>5) ? 5 : (AW-1):0]=0; + 3'b111: aligned_addr[(AW-1>6) ? 6 : (AW-1):0]=0; + default: aligned_addr = unaligned_addr; + endcase + // }}} + end + // }}} + end else + aligned_addr = i_last_addr[IN_AW-1:0]; + // }}} + + // crossblk_addr from aligned_addr, for WRAP addressing + // {{{ + always @(*) + if (i_burst[1]) + begin + // WRAP! + crossblk_addr[IN_AW-1:0] = (i_last_addr[IN_AW-1:0] & ~wrap_mask) + | (aligned_addr & wrap_mask); + end else + crossblk_addr[IN_AW-1:0] = aligned_addr; + // }}} + + // o_next_addr: Guarantee only the bottom 12 bits change + // {{{ + // This is really a logic simplification. AXI bursts aren't allowed + // to cross 4kB boundaries. Given that's the case, we don't have to + // suffer from the propagation across all AW bits, and can limit any + // address propagation to just the lower 12 bits + generate if (AW > 12) + begin : WIDE_ADDRESS + assign o_next_addr = { i_last_addr[AW-1:12], + crossblk_addr[11:0] }; + end else begin : NARROW_ADDRESS + assign o_next_addr = crossblk_addr[AW-1:0]; + end endgenerate + // }}} + + // Make Verilator happy + // {{{ + // Verilator lint_off UNUSED + wire unused; + assign unused = (LENB <= 4) ? &{1'b0, i_len[0] } + : &{ 1'b0, i_len[LENB-1:4], i_len[0] }; + // Verilator lint_on UNUSED + // }}} +endmodule diff --git a/src/axi/rtl/axi_if.sv b/src/axi/rtl/axi_if.sv new file mode 100644 index 000000000..207589863 --- /dev/null +++ b/src/axi/rtl/axi_if.sv @@ -0,0 +1,158 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Description: +// Signals for a standard AXI4 compliant interface +// + +interface axi_if #(parameter integer AW = 32, parameter integer DW = 32, parameter integer IW = 3, parameter integer UW = 32); + + import axi_pkg::*; + + // AXI AR + logic [AW-1:0] araddr; + logic [1:0] arburst; + logic [2:0] arsize; + logic [7:0] arlen; + logic [UW-1:0] aruser; + logic [IW-1:0] arid; + logic arlock; + logic arvalid; + logic arready; + + // AXI R + logic [DW-1:0] rdata; + logic [1:0] rresp; + logic [IW-1:0] rid; + logic rlast; + logic rvalid; + logic rready; + + // AXI AW + logic [AW-1:0] awaddr; + logic [1:0] awburst; + logic [2:0] awsize; + logic [7:0] awlen; + logic [UW-1:0] awuser; + logic [IW-1:0] awid; + logic awlock; + logic awvalid; + logic awready; + + // AXI W + logic [DW-1:0] wdata; + logic [DW/8-1:0] wstrb; + logic wvalid; + logic wready; + logic wlast; + + // AXI B + logic [1:0] bresp; + logic [IW-1:0] bid; + logic bvalid; + logic bready; + + // Modport for read manager + modport r_mgr ( + // AR + output araddr, + output arburst, + output arsize, + output arlen, + output aruser, + output arid, + output arlock, + output arvalid, + input arready, + // R + input rdata, + input rresp, + input rid, + input rlast, + input rvalid, + output rready + ); + + // Modport for write manager + modport w_mgr ( + // AW + output awaddr, + output awburst, + output awsize, + output awlen, + output awuser, + output awid, + output awlock, + output awvalid, + input awready, + // W + output wdata, + output wstrb, + output wvalid, + input wready, + output wlast, + // B + input bresp, + input bid, + input bvalid, + output bready + ); + + // Modport for read subordinate + modport r_sub ( + // AR + input araddr, + input arburst, + input arsize, + input arlen, + input aruser, + input arid, + input arlock, + input arvalid, + output arready, + // R + output rdata, + output rresp, + output rid, + output rlast, + output rvalid, + input rready + ); + + // Modport for write subordinate + modport w_sub ( + // AW + input awaddr, + input awburst, + input awsize, + input awlen, + input awuser, + input awid, + input awlock, + input awvalid, + output awready, + // W + input wdata, + input wstrb, + input wvalid, + output wready, + input wlast, + // B + output bresp, + output bid, + output bvalid, + input bready + ); + +endinterface diff --git a/src/axi/rtl/axi_pkg.sv b/src/axi/rtl/axi_pkg.sv new file mode 100644 index 000000000..43022e75d --- /dev/null +++ b/src/axi/rtl/axi_pkg.sv @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package axi_pkg; + + // AXI Burst Enum + typedef enum logic [1:0] { + AXI_BURST_FIXED = 2'b00, + AXI_BURST_INCR = 2'b01, + AXI_BURST_WRAP = 2'b10, + AXI_BURST_RESERVED = 2'b11 + } axi_burst_e; + + // AXI Resp Enum + typedef enum logic [1:0] { + AXI_RESP_OKAY = 2'b00, + AXI_RESP_EXOKAY = 2'b01, + AXI_RESP_SLVERR = 2'b10, + AXI_RESP_DECERR = 2'b11 + } axi_resp_e; + + // Transaction context + typedef struct packed { + logic [AW-1:0] addr; + logic [1:0] burst; + logic [2:0] size; + logic [7:0] len; + logic [UW-1:0] user; + logic [IW-1:0] id; + logic lock; + } axi_ctx_t; + + typedef struct packed { + logic [IW-1:0] id; + logic [UW-1:0] user; + axi_resp_e resp; + logic last; + } xfer_ctx_t; + + typedef struct packed { + logic [AW-1:0] addr; + logic [AW-1:0] addr_mask; + } axi_ex_ctx_t; + +endpackage diff --git a/src/axi/rtl/axi_sub.sv b/src/axi/rtl/axi_sub.sv new file mode 100644 index 000000000..cfdbfd984 --- /dev/null +++ b/src/axi/rtl/axi_sub.sv @@ -0,0 +1,206 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// ------------------------------------------------------------- +// AXI Subordinate +// ------------------------------------------------------------- +// Description: +// Subordinate to convert AXI protocol transactions into internal component accesses +// Includes an arbiter to squash duplex AXI transactions into simplex component operations +// May optionally include an Exclusive Access monitor (AxLOCK signal) +// +// Limitations: +// - When multiple ID tracking is enabled, write responses are returned in the +// same order they are received, regardless of ID. +// +// ------------------------------------------------------------- + +module axi_sub import axi_pkg::*; #( + parameter AW = 32, // Address Width + parameter DW = 32, // Data Width + BC = DW/8, // Byte Count + BW = $clog2(BC), // Byte count Width + parameter UW = 32, // User Width + parameter IW = 1, // ID Width + ID_NUM = 1 << IW, // Don't override + + parameter EX_EN = 0, // Enable exclusive access tracking w/ AxLOCK + parameter C_LAT = 0 // Component latency in clock cycles from (dv&&!hld) -> rdata + // Must be const per component + // For registers, typically 0 + // For SRAM, 1 or more +) ( + input clk, + input rst_n, + + // AXI INF + axi_if.w_sub s_axi_w_if, + axi_if.r_sub s_axi_r_if, + + //COMPONENT INF + output logic dv, + output logic [AW-1:0] addr, // Byte address + output logic write, + output logic [UW-1:0] user, + output logic [IW-1:0] id, + output logic [DW-1:0] wdata, // Requires: Component dwidth == AXI dwidth + output logic [BC-1:0] wstrb, // Requires: Component dwidth == AXI dwidth + input logic [DW-1:0] rdata, // Requires: Component dwidth == AXI dwidth + output logic last, // Asserted with final 'dv' of a burst + input logic hld, + input logic err + +); + + // Exclusive Access Signals + logic [ID_NUM-1:0] ex_clr; + logic [ID_NUM-1:0] ex_active; + axi_ex_ctx_t [ID_NUM-1:0] ex_ctx; + + //Read Subordinate INF + logic r_dv; + logic [AW-1:0] r_addr; // Byte address + logic [UW-1:0] r_user; + logic [IW-1:0] r_id; + logic r_last; // Asserted with final 'dv' of a burst + logic r_hld; + logic r_err; + + logic [DW-1:0] r_rdata; // Requires: Component dwidth == AXI dwidth + + //Write Subordinate INF + logic w_dv; + logic [AW-1:0] w_addr; // Byte address + logic [UW-1:0] w_user; + logic [IW-1:0] w_id; + logic [DW-1:0] w_wdata; // Requires: Component dwidth == AXI dwidth + logic [BC-1:0] w_wstrb; // Requires: Component dwidth == AXI dwidth + logic w_last; // Asserted with final 'dv' of a burst + logic w_hld; + logic w_err; + + + axi_sub_wr #( + .AW (AW ), + .DW (DW ), + .UW (UW ), + .IW (IW ), + + .EX_EN(EX_EN) + ) i_axi_sub_wr ( + .clk (clk ), + .rst_n(rst_n), + + // AXI INF + .s_axi_if(s_axi_w_if), + + // Exclusive Access Signals + .ex_clr (ex_clr ), + .ex_active(ex_active), + .ex_ctx (ex_ctx ), + + //COMPONENT INF + .dv (w_dv ), + .addr (w_addr ), + .user (w_user ), + .id (w_id ), + .wdata(w_wdata), + .wstrb(w_wstrb), + .last (w_last ), + .hld (w_hld ), + .err (w_err ) + + ); + + axi_sub_rd #( + .AW(AW), + .DW(DW), + .UW(UW), + .IW(IW), + + .EX_EN(EX_EN), + .C_LAT(C_LAT) + ) i_axi_sub_rd ( + .clk (clk ), + .rst_n(rst_n), + + // AXI INF + .s_axi_if(s_axi_r_if), + + // Exclusive Access Signals + .ex_clr (ex_clr ), + .ex_active(ex_active), + .ex_ctx (ex_ctx ), + + //COMPONENT INF + .dv (r_dv ), + .addr (r_addr ), + .user (r_user ), + .id (r_id ), + .last (r_last ), + .hld (r_hld ), + .err (r_err ), + + .rdata(r_rdata) + ); + + axi_sub_arb #( + .AW(AW), + .DW(DW), + .UW(UW), + .IW(IW), + + .EX_EN(EX_EN), + .C_LAT(C_LAT) + ) i_axi_sub_arb ( + .clk (clk ), + .rst_n (rst_n ), + + //Read Subordinate INF + .r_dv (r_dv ), + .r_addr (r_addr ), + .r_user (r_user ), + .r_id (r_id ), + .r_last (r_last ), + .r_hld (r_hld ), + .r_err (r_err ), + .r_rdata(r_rdata), + + //Write Subordinate INF + .w_dv (w_dv ), + .w_addr (w_addr ), + .w_user (w_user ), + .w_id (w_id ), + .w_wdata(w_wdata), + .w_wstrb(w_wstrb), + .w_last (w_last ), + .w_hld (w_hld ), + .w_err (w_err ), + + //COMPONENT INF + .dv (dv ), + .addr (addr ), + .write (write ), + .user (user ), + .id (id ), + .wdata (wdata ), + .wstrb (wstrb ), + .last (last ), + .hld (hld ), + .err (err ), + .rdata (rdata ) + ); + +endmodule diff --git a/src/axi/rtl/axi_sub_arb.sv b/src/axi/rtl/axi_sub_arb.sv new file mode 100644 index 000000000..aa770372c --- /dev/null +++ b/src/axi/rtl/axi_sub_arb.sv @@ -0,0 +1,141 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// ------------------------------------------------------------- +// AXI Subordinate Arbiter +// ------------------------------------------------------------- +// Description: +// Arbitrate between Reads and Writes coming from AXI subordinate modules. +// Always give precedence to Writes. +// +// ------------------------------------------------------------- + +module axi_sub_arb import axi_pkg::*; #( + parameter AW = 32, // Address Width + parameter DW = 32, // Data Width + BC = DW/8, // Byte Count + BW = $clog2(BC), // Byte count Width + parameter UW = 32, // User Width + parameter IW = 1, // ID Width + ID_NUM = 1 << IW, // Don't override + + parameter EX_EN = 0, // Enable exclusive access tracking w/ AxLOCK + parameter C_LAT = 0 // Component latency in clock cycles from (dv&&!hld) -> rdata + // Must be const per component + // For registers, typically 0 + // For SRAM, 1 or more +) ( + input clk, + input rst_n, + + //Read Subordinate INF + input logic r_dv, + input logic [AW-1:0] r_addr, // Byte address + input logic [UW-1:0] r_user, + input logic [IW-1:0] r_id, + input logic r_last, // Asserted with final 'dv' of a burst + output logic r_hld, + output logic r_err, + + output logic [DW-1:0] r_rdata, // Requires: Component dwidth == AXI dwidth + + //Write Subordinate INF + input logic w_dv, + input logic [AW-1:0] w_addr, // Byte address + input logic [UW-1:0] w_user, + input logic [IW-1:0] w_id, + input logic [DW-1:0] w_wdata, // Requires: Component dwidth == AXI dwidth + input logic [BC-1:0] w_wstrb, // Requires: Component dwidth == AXI dwidth + input logic w_last, // Asserted with final 'dv' of a burst + output logic w_hld, + output logic w_err, + + //COMPONENT INF + output logic dv, + output logic [AW-1:0] addr, // Byte address + output logic write, + output logic [UW-1:0] user, + output logic [IW-1:0] id, + output logic [DW-1:0] wdata, // Requires: Component dwidth == AXI dwidth + output logic [BC-1:0] wstrb, // Requires: Component dwidth == AXI dwidth + output logic last, // Asserted with final 'dv' of a burst + input logic hld, + input logic err, // FIXME Does this assert with dv or with rdata for reads (when C_LAT > 0)? + + input logic [DW-1:0] rdata // Requires: Component dwidth == AXI dwidth +); + + logic r_pri; // Priority to reads + logic r_win; + + // Switch priority to current arb winner so that priority persists + // in case + // a) it was granted during a hold + // b) it was granted at start of multi-beat burst + // Otherwise, always give priority to other channel at end of a burst + // to arbitrate fairly + always_ff@(posedge clk or negedge rst_n) begin + if (!rst_n) + r_pri <= 1'b0; + // Toggle priority at end of burst + else if (w_dv && !w_hld && w_last) + r_pri <= 1'b1; + else if (r_dv && !r_hld && r_last) + r_pri <= 1'b0; + // Keep priority when burst starts + else if (w_dv && !r_win && !w_last) + r_pri <= 1'b0; + else if (r_dv && r_win && !r_last) + r_pri <= 1'b1; + end + + always_comb begin +// case ({r_pri,r_dv,w_dv}) inside +// 3'b000: r_win = 0; +// 3'b001: r_win = 0; +// 3'b010: r_win = 1; +// 3'b011: r_win = 0; +// 3'b100: r_win = 1; +// 3'b101: r_win = 0; +// 3'b110: r_win = 1; +// 3'b111: r_win = 1; +// endcase + if (r_pri) r_win = r_dv || !w_dv; + else r_win = w_dv || !r_dv; + end + + always_comb begin + dv = r_dv || w_dv; + addr = r_win ? r_addr : w_addr; + write = r_win ? 0 : 1; + user = r_win ? r_user : w_user; + id = r_win ? r_id : w_id ; + last = r_win ? r_last : w_last; + r_hld = hld || !r_win; + w_hld = hld || r_win; + r_err = err; + w_err = err; + wdata = w_wdata; + wstrb = w_wstrb; + r_rdata = rdata; + end + + `CALIPTRA_ASSERT_NEVER(AXI_SUB_ARB_CONFLICT, r_dv && !r_hld && w_dv && !w_hld, clk, !rst_n) + // This arbiter can't deal with an err signal that asserts at + // a delay from the dv signal (as in the case of the read channel). + `CALIPTRA_ASSERT(AXI_SUB_ARB_LAT_ERR, C_LAT == 0, clk, !rst_n) + + +endmodule diff --git a/src/axi/rtl/axi_sub_rd.sv b/src/axi/rtl/axi_sub_rd.sv new file mode 100644 index 000000000..b7baf53a9 --- /dev/null +++ b/src/axi/rtl/axi_sub_rd.sv @@ -0,0 +1,393 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// ------------------------------------------------------------- +// AXI Read Subordinate +// ------------------------------------------------------------- +// Description: +// Subordinate to convert AXI protocol reads into internal component accesses +// +// Limitations: +// - When multiple ID tracking is enabled, read responses are returned in the +// same order they are received, regardless of ID. +// +// ------------------------------------------------------------- + +module axi_sub_rd import axi_pkg::*; #( + parameter AW = 32, // Address Width + parameter DW = 32, // Data Width + BC = DW/8, // Byte Count + BW = $clog2(BC), // Byte count Width + parameter UW = 32, // User Width + parameter IW = 1, // ID Width + ID_NUM = 1 << IW, // Don't override + + parameter EX_EN = 0, // Enable exclusive access tracking w/ AxLOCK + parameter C_LAT = 0 // Component latency in clock cycles from (dv&&!hld) -> rdata + // Must be const per component + // For registers, typically 0 + // For SRAM, 1 or more +) ( + input clk, + input rst_n, + + // AXI INF + axi_if.r_sub s_axi_if, + + // Exclusive Access Signals + input logic [ID_NUM-1:0] ex_clr, + output logic [ID_NUM-1:0] ex_active, + output var axi_ex_ctx_t [ID_NUM-1:0] ex_ctx, + + //COMPONENT INF + output logic dv, + output logic [AW-1:0] addr, // Byte address + output logic [UW-1:0] user, + output logic [IW-1:0] id, + output logic last, // Asserted with final 'dv' of a burst + input logic hld, + input logic err, + + input logic [DW-1:0] rdata // Requires: Component dwidth == AXI dwidth +); + + // --------------------------------------- // + // Localparams/Typedefs // + // --------------------------------------- // + + + // --------------------------------------- // + // Signals // + // --------------------------------------- // + + genvar cp; // Context pipeline + genvar dp; // Data pipeline + genvar ex; // Exclusive contexts + + // Active transaction signals + // track requests as they are sent to component + axi_ctx_t txn_ctx; + logic [AW-1:0] txn_addr_nxt; + logic [ 7:0] txn_cnt; // Internal down-counter to track txn progress + logic txn_active; + logic [C_LAT:0] txn_rvalid; + xfer_ctx_t [C_LAT:0] txn_xfer_ctx; + logic txn_final_beat; + + // Data pipeline signals (skid buffer) + // track data after it is received from component + logic [C_LAT+1:0] [DW-1:0] dp_rdata; + xfer_ctx_t [C_LAT+1:0] dp_xfer_ctx; + logic [C_LAT+1:0] dp_rvalid; + logic [C_LAT+1:0] dp_rready; + + + // --------------------------------------- // + // Address Request I/F // + // --------------------------------------- // + + assign s_axi_if.arready = !txn_active || txn_final_beat; + + // Indicates there are still reqs to be issued towards component. + // This active signal deasserts after final dv to component, meaning data is + // still in flight from component->AXI for C_LAT clocks after deassertion + always_ff@(posedge clk or negedge rst_n) begin + if (!rst_n) begin + txn_active <= 1'b0; + end + else if (s_axi_if.arvalid) begin + txn_active <= 1'b1; + end + else if (txn_final_beat) begin + txn_active <= 1'b0; + end + else begin + txn_active <= txn_active; + end + end + + // TODO reset? + always_ff@(posedge clk/* or negedge rst_n*/) begin +// if (!rst_n) begin +// txn_ctx <= '{default:0}; +// end + if (s_axi_if.arvalid && s_axi_if.arready) begin + txn_ctx.addr <= s_axi_if.araddr; + txn_ctx.burst <= s_axi_if.arburst; + txn_ctx.size <= s_axi_if.arsize; + txn_ctx.len <= s_axi_if.arlen ; + txn_ctx.user <= s_axi_if.aruser; + txn_ctx.id <= s_axi_if.arid ; + txn_ctx.lock <= s_axi_if.arlock; + txn_cnt <= s_axi_if.arlen ; + end + else if (txn_rvalid[0]) begin + txn_ctx.addr <= txn_addr_nxt; + txn_ctx.burst <= txn_ctx.burst; + txn_ctx.size <= txn_ctx.size; + txn_ctx.len <= txn_ctx.len ; + txn_ctx.user <= txn_ctx.user; + txn_ctx.id <= txn_ctx.id ; + txn_ctx.lock <= txn_ctx.lock; + txn_cnt <= |txn_cnt ? txn_cnt - 1 : txn_cnt; // Prevent underflow to 255 at end to reduce switching power. Extra logic cost worth it? + end + else begin + txn_ctx <= txn_ctx; + end + end + + // Only make the request to component if we have space in the pipeline to + // store the result (under worst-case AXI backpressure) + // To check this, look at the 'ready' output from the FINAL stage of the + // skidbuffer pipeline + always_comb dv = txn_active && dp_rready[C_LAT]; + always_comb txn_rvalid[0] = dv && !hld; + + // Asserts on the final beat of the COMPONENT INF which means it lags the + // final AXI beat by at least C_LAT clocks (or more depending on backpressure) + always_comb txn_final_beat = txn_rvalid[0] && txn_xfer_ctx[0].last; + + + // --------------------------------------- // + // Address Calculations // + // --------------------------------------- // + // Force aligned address to component + always_comb addr = {txn_ctx.addr[AW-1:BW],BW'(0)}; + always_comb user = txn_ctx.user; + always_comb id = txn_ctx.id; + + // Use full address to calculate next address (in case of arsize < data width) + axi_addr #( + .AW (AW), + .DW (DW), + .LENB(8 ) + ) i_axi_addr ( + .i_last_addr(txn_ctx.addr ), + .i_size (txn_ctx.size ), // 1b, 2b, 4b, 8b, etc + .i_burst (txn_ctx.burst), // fixed, incr, wrap, reserved + .i_len (txn_ctx.len ), + .o_next_addr(txn_addr_nxt ) + ); + + + // --------------------------------------- // + // Request Context Pipeline // + // --------------------------------------- // + always_comb begin + txn_xfer_ctx[0].id = txn_ctx.id; + txn_xfer_ctx[0].user = txn_ctx.user; + txn_xfer_ctx[0].last = txn_cnt == 0; + txn_xfer_ctx[0].resp = (EX_EN && txn_ctx.lock) ? AXI_RESP_EXOKAY : AXI_RESP_OKAY; + end + + // Shift Register to track requests made to component + generate + if (C_LAT > 0) begin: TXN_SR + always_ff@(posedge clk or negedge rst_n) begin + if (!rst_n) begin + txn_rvalid[C_LAT:1] <= C_LAT'(0); + end + else begin + txn_rvalid[C_LAT:1] <= txn_rvalid[C_LAT-1:0]; + end + end + + // Context is maintained alongside request while waiting for + // component response to arrive + if (C_LAT > 1) begin + for (cp = 1; cp <= C_LAT; cp++) begin: CTX_PIPELINE + // No reset needed on data path -- txn_rvalid (control path) is reset + always_ff@(posedge clk) begin + txn_xfer_ctx[cp] <= txn_xfer_ctx[cp-1]; + end + end: CTX_PIPELINE + end + + end: TXN_SR + endgenerate + + + // --------------------------------------- // + // Exclusive Access Tracking // + // --------------------------------------- // + generate + if (EX_EN) begin: EX_AXS_TRACKER + // Exclusive access requires transaction LENGTH to be a power of 2, + // so an address mask may be prepared by assuming the AxLEN value has + // all consecutive LSB set to 1, then all 0's in higher order bits. After + // shifting by the width of the transaction (AxSIZE), this mask can be applied + // to AxADDR to give the aligned address relative to an exclusive access. + // + logic [AW-1:0] addr_ex_algn_mask; + always_comb begin + case (BC) inside + 1: addr_ex_algn_mask = ~(AW'(txn_ctx.len)); + 2: addr_ex_algn_mask = (~(AW'(txn_ctx.len))) << txn_ctx.size[0]; + 4: addr_ex_algn_mask = (~(AW'(txn_ctx.len))) << txn_ctx.size[1:0]; + 8: addr_ex_algn_mask = (~(AW'(txn_ctx.len))) << txn_ctx.size[1:0]; + default: addr_ex_algn_mask = (~(AW'(txn_ctx.len))) << txn_ctx.size; + endcase + end + + always_ff@(posedge clk or negedge rst_n) begin + if (!rst_n) begin + ex_active <= '0; + end + // give 'set' precedence over 'clr' in case of same ID + else if ((txn_rvalid[0] && txn_ctx.lock) && |ex_clr) begin + ex_active <= (ex_active & ~ex_clr) | (1 << txn_ctx.id); + end + else if (txn_rvalid[0] && txn_ctx.lock) begin + ex_active <= ex_active | (1 << txn_ctx.id); + end + else if (|ex_clr) begin + ex_active <= ex_active & ~ex_clr; + end + else begin + ex_active <= ex_active; + end + end + + for (ex = 0; ex < ID_NUM; ex++) begin: EX_CTX_TRACKER + // TODO: reset? + always_ff@(posedge clk) begin + if (txn_rvalid[0] && txn_ctx.lock && (txn_ctx.id == ex)) begin + ex_ctx[ex].addr <= txn_ctx.addr; + ex_ctx[ex].addr_mask <= addr_ex_algn_mask; + end + // Ignore the clear case, as ex_active is the ctrl path + //else if (ex_clr[ex]) begin + //end + else begin + ex_ctx[ex] <= ex_ctx[ex]; + end + end + end + end: EX_AXS_TRACKER + else begin: EX_UNSUPPORTED + for (ex = 0; ex < ID_NUM; ex++) begin: EX_SIG_0 + always_comb begin + ex_active[ex] = 1'b0; + ex_ctx[ex] = '{default:0}; + end + end: EX_SIG_0 + end: EX_UNSUPPORTED + endgenerate + + + // --------------------------------------- // + // Data/Response // + // --------------------------------------- // + always_comb dp_rvalid[0] = txn_rvalid[C_LAT]; + always_comb dp_rdata[0] = rdata; + always_comb begin + dp_xfer_ctx[0].id = txn_xfer_ctx[C_LAT].id; + dp_xfer_ctx[0].user = txn_xfer_ctx[C_LAT].user; // NOTE: Unused after it enters data pipeline + dp_xfer_ctx[0].resp = err ? AXI_RESP_SLVERR : + EX_EN ? txn_xfer_ctx[C_LAT].resp : + AXI_RESP_OKAY; + dp_xfer_ctx[0].last = txn_xfer_ctx[C_LAT].last; + end + + generate + for (dp = 0; dp <= C_LAT; dp++) begin: DATA_PIPELINE + // skidbuffer instance to pipeline data payload + context to AXI, + // after response is received from component. + // This is necessary when there is latency from dv->rdata, + // because AXI R channel can be stalled by dropping rready, + // and we can't drop the data (which was requested N cycles ago) + skidbuffer #( + .OPT_LOWPOWER (0 ), + .OPT_OUTREG (0 ), + // + .OPT_PASSTHROUGH(0 ), + .DW (DW + $bits(xfer_ctx_t)), + .OPT_INITIAL (1'b1) + ) i_dp_skd ( + .i_clk (clk ), + .i_reset(!rst_n ), + .i_valid(dp_rvalid[dp] ), + .o_ready(dp_rready[dp] ), + .i_data ({dp_rdata[dp], + dp_xfer_ctx[dp]} ), + .o_valid(dp_rvalid[dp+1] ), + .i_ready(dp_rready[dp+1] ), + .o_data ({dp_rdata[dp+1], + dp_xfer_ctx[dp+1]}) + ); + + end: DATA_PIPELINE + endgenerate + + always_comb dp_rready[C_LAT+1] = s_axi_if.rready; + always_comb begin + s_axi_if.rvalid = dp_rvalid[C_LAT+1]; + s_axi_if.rlast = dp_xfer_ctx[C_LAT+1].last; + s_axi_if.rdata = dp_rdata[C_LAT+1]; + s_axi_if.rid = dp_xfer_ctx[C_LAT+1].id; + s_axi_if.rresp = dp_xfer_ctx[C_LAT+1].resp; + end + + + // --------------------------------------- // + // Formal Properties // + // --------------------------------------- // + `CALIPTRA_ASSERT_KNOWN(AXI_SUB_X_ARVALID, s_axi_if.arvalid, clk, !rst_n) + `CALIPTRA_ASSERT_KNOWN(AXI_SUB_X_ARREADY, s_axi_if.arready, clk, !rst_n) + `CALIPTRA_ASSERT_KNOWN(AXI_SUB_X_ARADDR , (s_axi_if.arvalid ? s_axi_if.araddr : '0), clk, !rst_n) + `CALIPTRA_ASSERT_KNOWN(AXI_SUB_X_ARBURST, (s_axi_if.arvalid ? s_axi_if.arburst : '0), clk, !rst_n) + `CALIPTRA_ASSERT_KNOWN(AXI_SUB_X_ARSIZE , (s_axi_if.arvalid ? s_axi_if.arsize : '0), clk, !rst_n) + `CALIPTRA_ASSERT_KNOWN(AXI_SUB_X_ARLEN , (s_axi_if.arvalid ? s_axi_if.arlen : '0), clk, !rst_n) + `CALIPTRA_ASSERT_KNOWN(AXI_SUB_X_ARUSER , (s_axi_if.arvalid ? s_axi_if.aruser : '0), clk, !rst_n) + `CALIPTRA_ASSERT_KNOWN(AXI_SUB_X_ARID , (s_axi_if.arvalid ? s_axi_if.arid : '0), clk, !rst_n) + `CALIPTRA_ASSERT_KNOWN(AXI_SUB_X_ARLOCK , (s_axi_if.arvalid ? s_axi_if.arlock : '0), clk, !rst_n) + `CALIPTRA_ASSERT_KNOWN(AXI_SUB_X_RVALID , s_axi_if.rvalid , clk, !rst_n) + `CALIPTRA_ASSERT_KNOWN(AXI_SUB_X_RREADY , s_axi_if.rready , clk, !rst_n) + `CALIPTRA_ASSERT_KNOWN(AXI_SUB_X_RDATA , (s_axi_if.rvalid ? s_axi_if.rdata : '0), clk, !rst_n) + `CALIPTRA_ASSERT_KNOWN(AXI_SUB_X_RRESP , (s_axi_if.rvalid ? s_axi_if.rresp : '0), clk, !rst_n) + `CALIPTRA_ASSERT_KNOWN(AXI_SUB_X_RID , (s_axi_if.rvalid ? s_axi_if.rid : '0), clk, !rst_n) + `CALIPTRA_ASSERT_KNOWN(AXI_SUB_X_RLAST , (s_axi_if.rvalid ? s_axi_if.rlast : '0), clk, !rst_n) + + // Handshake rules + `CALIPTRA_ASSERT (AXI_SUB_AR_HSHAKE_ERR, s_axi_if.arvalid && !s_axi_if.arready => s_axi_if.arvalid, clk, !rst_n) + `CALIPTRA_ASSERT (AXI_SUB_R_HSHAKE_ERR, s_axi_if.rvalid && !s_axi_if.rready => s_axi_if.rvalid, clk, !rst_n) + + `CALIPTRA_ASSERT_NEVER(ERR_AXI_RD_DROP , dp_rvalid[0] && !dp_rready[0], clk, !rst_n) + `CALIPTRA_ASSERT_NEVER(ERR_AXI_RD_X , dp_rvalid[0] && $isunknown({dp_rdata[0],dp_xfer_ctx[0]}), clk, !rst_n) + // Exclusive access rules: + // - Must have an address that is aligned to burst byte count + // - Byte count must be power of 2 inside 1:128 + // - Max burst length = 16 + `CALIPTRA_ASSERT (ERR_AXI_EX_UNALGN , (s_axi_if.arvalid && s_axi_if.arlock) -> ~|s_axi_if.araddr[$clog2((1< ((1< (s_axi_if.arlen < 16), clk, !rst_n) + + genvar sva_ii; + generate + if (C_LAT > 0) begin + for (sva_ii = 0; sva_ii < C_LAT; sva_ii++) begin + // Last stage should be first to fill and last to go empty + `CALIPTRA_ASSERT_NEVER(ERR_RD_SKD_BUF, dp_rready[sva_ii+1] && !dp_rready[sva_ii], clk, !rst_n) + end + end + endgenerate + + + // --------------------------------------- // + // Coverage // + // --------------------------------------- // + + +endmodule diff --git a/src/axi/rtl/axi_sub_wr.sv b/src/axi/rtl/axi_sub_wr.sv new file mode 100644 index 000000000..cfd3998e2 --- /dev/null +++ b/src/axi/rtl/axi_sub_wr.sv @@ -0,0 +1,356 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// ------------------------------------------------------------- +// AXI Write Subordinate +// ------------------------------------------------------------- +// Description: +// Subordinate to convert AXI protocol writes into internal component accesses +// +// Limitations: +// - When multiple ID tracking is enabled, write responses are returned in the +// same order they are received, regardless of ID. +// +// ------------------------------------------------------------- + +module axi_sub_wr import axi_pkg::*; #( + parameter AW = 32, // Address Width + parameter DW = 32, // Data Width + BC = DW/8, // Byte Count + BW = $clog2(BC), // Byte count Width + parameter UW = 32, // User Width + parameter IW = 1, // ID Width + ID_NUM = 1 << IW, // Don't override + + parameter EX_EN = 0 // Enable exclusive access tracking w/ AxLOCK +) ( + input clk, + input rst_n, + + // AXI INF + axi_if.w_sub s_axi_if, + + // Exclusive Access Signals + output logic [ID_NUM-1:0] ex_clr, + input logic [ID_NUM-1:0] ex_active, + input var axi_ex_ctx_t [ID_NUM-1:0] ex_ctx, + + //COMPONENT INF + output logic dv, + output logic [AW-1:0] addr, // Byte address + output logic [UW-1:0] user, + output logic [IW-1:0] id, + output logic [DW-1:0] wdata, // Requires: Component dwidth == AXI dwidth + output logic [BC-1:0] wstrb, // Requires: Component dwidth == AXI dwidth + output logic last, // Asserted with final 'dv' of a burst + input logic hld, + input logic err + +); + + // --------------------------------------- // + // Localparams/Typedefs // + // --------------------------------------- // + + + // --------------------------------------- // + // Signals // + // --------------------------------------- // + + genvar ex; // Exclusive contexts + + logic dv_pre; + + // Active transaction signals + // track requests as they are sent to component + axi_ctx_t s_axi_if_ctx; + axi_ctx_t req_ctx; + logic req_valid; + logic req_ready; + logic req_matches_ex; + axi_ctx_t txn_ctx; + logic [AW-1:0] txn_addr_nxt; + logic txn_active; + logic txn_wvalid; + logic txn_wready; + logic txn_allow; // If an exclusive-write with no match to tracked context, don't complete write to component + logic txn_err; + logic txn_final_beat; + logic [ID_NUM-1:0] txn_ex_match; // Current access matches the flagged exclusive context + // Possible for multiple bits to be set -- match of multiple contexts + + // Response Pipeline signals + logic rp_valid; + logic rp_ready; + axi_resp_e rp_resp; + logic [IW-1:0] rp_id; + + + // --------------------------------------- // + // Address Request I/F // + // --------------------------------------- // + + always_comb begin + s_axi_if_ctx.addr = s_axi_if.awaddr ; + s_axi_if_ctx.burst = s_axi_if.awburst; + s_axi_if_ctx.size = s_axi_if.awsize ; + s_axi_if_ctx.len = s_axi_if.awlen ; + s_axi_if_ctx.user = s_axi_if.awuser ; + s_axi_if_ctx.id = s_axi_if.awid ; + s_axi_if_ctx.lock = s_axi_if.awlock && EX_EN; + end + + // skidbuffer instance to pipeline request context from AXI. + skidbuffer #( + .OPT_LOWPOWER (0 ), + .OPT_OUTREG (0 ), + // + .OPT_PASSTHROUGH(0 ), + .DW ($bits(axi_ctx_t)), + .OPT_INITIAL (1'b1) + ) i_req_skd ( + .i_clk (clk ), + .i_reset(!rst_n ), + .i_valid(s_axi_if.awvalid), + .o_ready(s_axi_if.awready), + .i_data (s_axi_if_ctx ), + .o_valid(req_valid ), + .i_ready(req_ready ), + .o_data (req_ctx ) + ); + + // Only accept request when we have a guaranteed slot in the response buffer + // to put the response + assign req_ready = (!txn_active || (txn_final_beat && !s_axi_if.bvalid)) && rp_ready[0]; + + // Indicates there are still reqs to be issued towards component. + // This active signal deasserts after final dv to component + always_ff@(posedge clk or negedge rst_n) begin + if (!rst_n) begin + txn_active <= 1'b0; + end + else if (req_valid && req_ready) begin + txn_active <= 1'b1; + end + else if (txn_final_beat) begin + txn_active <= 1'b0; + end + else begin + txn_active <= txn_active; + end + end + + always_comb req_matches_ex = (req_ctx.addr & ex_ctx[req_ctx.id].addr_mask) == ex_ctx[req_ctx.id].addr; + + // TODO reset? + always_ff@(posedge clk/* or negedge rst_n*/) begin +// if (!rst_n) begin +// txn_ctx <= '{default:0}; +// end + if (req_valid && req_ready) begin + txn_ctx.addr <= req_ctx.addr; + txn_ctx.burst <= req_ctx.burst; + txn_ctx.size <= req_ctx.size; + txn_ctx.len <= req_ctx.len ; + txn_ctx.user <= req_ctx.user; + txn_ctx.id <= req_ctx.id ; + txn_ctx.lock <= req_ctx.lock; + txn_allow <= !EX_EN || !req_ctx.lock || (ex_active[req_ctx.id] && req_matches_ex); + txn_err <= 1'b0; + end + else if (dv && !hld) begin + txn_ctx.addr <= txn_addr_nxt; + txn_ctx.burst <= txn_ctx.burst; + txn_ctx.size <= txn_ctx.size; + txn_ctx.len <= txn_ctx.len ; + txn_ctx.user <= txn_ctx.user; + txn_ctx.id <= txn_ctx.id ; + txn_ctx.lock <= txn_ctx.lock; + txn_allow <= txn_allow; + txn_err <= txn_err || err; + end + else begin + txn_ctx <= txn_ctx; + txn_allow <= txn_allow; + txn_err <= txn_err; + end + end + + // Asserts on the final COMPONENT INF beat, which means data does not + // arrive at endpoint until after C_LAT clocks + always_comb txn_final_beat = dv_pre && (!txn_allow || !hld) && last; + + + // --------------------------------------- // + // Address Calculations // + // --------------------------------------- // + // Force aligned address to component + always_comb addr = {txn_ctx.addr[AW-1:BW],BW'(0)}; + always_comb user = txn_ctx.user; + always_comb id = txn_ctx.id; + + // Use full address to calculate next address (in case of AxSIZE < data width) + axi_addr #( + .AW (AW), + .DW (DW), + .LENB(8 ) + ) i_axi_addr ( + .i_last_addr(txn_ctx.addr ), + .i_size (txn_ctx.size ), // 1b, 2b, 4b, 8b, etc + .i_burst (txn_ctx.burst), // fixed, incr, wrap, reserved + .i_len (txn_ctx.len ), + .o_next_addr(txn_addr_nxt ) + ); + + + // --------------------------------------- // + // Exclusive Access Tracking // + // --------------------------------------- // + + generate + for (ex=0; ex < ID_NUM; ex++) begin: EX_AXS_TRACKER + logic [AW-1:0] addr_ex_algn; + + // Component address aligned to exclusive tracking context + // Don't use aligned 'addr' signal, because exclusive access alignment may + // be smaller than component inf (since single-byte exclusive access is legal) + always_comb addr_ex_algn = txn_ctx.addr & ex_ctx[ex].addr_mask; + + // Match on each beat in case a burst transaction only overlaps + // the exclusive context partially + always_comb txn_ex_match[ex] <= (addr_ex_algn == ex_ctx[ex].addr); + + end: EX_AXS_TRACKER + endgenerate + + // Only clear the context when a write goes through to dest - meaining dv, not dv_pre + always_ff@(posedge clk or negedge rst_n) begin + if (!rst_n) + ex_clr <= ID_NUM'(0); + else if (EX_EN && dv && !hld) + ex_clr <= ex_active & txn_ex_match; // Could have multiple set bits + else + ex_clr <= ID_NUM'(0); + end + + + // --------------------------------------- // + // Data/Response // + // --------------------------------------- // + + always_comb txn_wvalid = s_axi_if.wvalid && txn_active; + always_comb s_axi_if.wready = txn_wready && txn_active; + + // skidbuffer instance to pipeline data payload from AXI. + skidbuffer #( + .OPT_LOWPOWER (0 ), + .OPT_OUTREG (0 ), + // + .OPT_PASSTHROUGH(0 ), + .DW (DW + BC + 1), + .OPT_INITIAL (1'b1) + ) i_dp_skd ( + .i_clk (clk ), + .i_reset(!rst_n ), + .i_valid(txn_wvalid ), + .o_ready(txn_wready ), + .i_data ({s_axi_if.wdata, + s_axi_if.wstrb, + s_axi_if.wlast}), + .o_valid(dv_pre ), + .i_ready(!hld ), + .o_data ({wdata, + wstrb, + last } ) + ); + + always_comb dv = dv_pre && txn_allow; + + // Registered skidbuffer to pipeline response signals. + // Skid buffer captures any response transfer if the + // register output is busy with a previous response and is + // stalled. + // There is guaranteed to be space in the skid buffer because new + // requests are stalled (AWREADY=0) until this buffer is ready. + always_comb begin + rp_valid[0] = txn_final_beat; + rp_resp[0] = txn_allow && (txn_err || err) ? AXI_RESP_SLVERR : + txn_allow && txn_ctx.lock ? AXI_RESP_EXOKAY : + AXI_RESP_OKAY; + rp_id[0] = txn_ctx.id; + end + + skidbuffer #( + .OPT_LOWPOWER (0 ), + .OPT_OUTREG (1 ), + // + .OPT_PASSTHROUGH(0 ), + .DW (IW + $bits(axi_resp_e)), + .OPT_INITIAL (1'b1) + ) i_rsp_skd ( + .i_clk (clk ), + .i_reset(!rst_n ), + .i_valid(rp_valid[0] ), + .o_ready(rp_ready[0] ), + .i_data ({rp_resp[0], + rp_id[0]} ), + .o_valid(s_axi_if.bvalid ), + .i_ready(s_axi_if.bready ), + .o_data ({s_axi_if.bresp, + s_axi_if.bid} ) + ); + + + // --------------------------------------- // + // Formal Properties // + // --------------------------------------- // + `CALIPTRA_ASSERT_KNOWN(AXI_SUB_X_AWVALID, s_axi_if.awvalid, clk, !rst_n) + `CALIPTRA_ASSERT_KNOWN(AXI_SUB_X_AWREADY, s_axi_if.awready, clk, !rst_n) + `CALIPTRA_ASSERT_KNOWN(AXI_SUB_X_AWADDR , (s_axi_if.awvalid ? s_axi_if.awaddr : '0), clk, !rst_n) + `CALIPTRA_ASSERT_KNOWN(AXI_SUB_X_AWBURST, (s_axi_if.awvalid ? s_axi_if.awburst : '0), clk, !rst_n) + `CALIPTRA_ASSERT_KNOWN(AXI_SUB_X_AWSIZE , (s_axi_if.awvalid ? s_axi_if.awsize : '0), clk, !rst_n) + `CALIPTRA_ASSERT_KNOWN(AXI_SUB_X_AWLEN , (s_axi_if.awvalid ? s_axi_if.awlen : '0), clk, !rst_n) + `CALIPTRA_ASSERT_KNOWN(AXI_SUB_X_AWUSER , (s_axi_if.awvalid ? s_axi_if.awuser : '0), clk, !rst_n) + `CALIPTRA_ASSERT_KNOWN(AXI_SUB_X_AWID , (s_axi_if.awvalid ? s_axi_if.awid : '0), clk, !rst_n) + `CALIPTRA_ASSERT_KNOWN(AXI_SUB_X_AWLOCK , (s_axi_if.awvalid ? s_axi_if.awlock : '0), clk, !rst_n) + `CALIPTRA_ASSERT_KNOWN(AXI_SUB_X_WVALID , s_axi_if.wvalid , clk, !rst_n) + `CALIPTRA_ASSERT_KNOWN(AXI_SUB_X_WREADY , s_axi_if.wready , clk, !rst_n) + `CALIPTRA_ASSERT_KNOWN(AXI_SUB_X_WDATA , (s_axi_if.wvalid ? s_axi_if.wdata : '0), clk, !rst_n) + `CALIPTRA_ASSERT_KNOWN(AXI_SUB_X_WSTRB , (s_axi_if.wvalid ? s_axi_if.wstrb : '0), clk, !rst_n) + `CALIPTRA_ASSERT_KNOWN(AXI_SUB_X_WLAST , (s_axi_if.wvalid ? s_axi_if.wlast : '0), clk, !rst_n) + `CALIPTRA_ASSERT_KNOWN(AXI_SUB_X_BVALID , s_axi_if.bvalid , clk, !rst_n) + `CALIPTRA_ASSERT_KNOWN(AXI_SUB_X_BREADY , s_axi_if.bready , clk, !rst_n) + `CALIPTRA_ASSERT_KNOWN(AXI_SUB_X_BRESP , (s_axi_if.bvalid ? s_axi_if.bresp : '0), clk, !rst_n) + `CALIPTRA_ASSERT_KNOWN(AXI_SUB_X_BID , (s_axi_if.bvalid ? s_axi_if.bid : '0), clk, !rst_n) + + // Handshake rules + `CALIPTRA_ASSERT (AXI_SUB_AW_HSHAKE_ERR, s_axi_if.awvalid && !s_axi_if.awready => s_axi_if.awvalid, clk, !rst_n) + `CALIPTRA_ASSERT (AXI_SUB_W_HSHAKE_ERR, s_axi_if.wvalid && !s_axi_if.wready => s_axi_if.wvalid, clk, !rst_n) + `CALIPTRA_ASSERT (AXI_SUB_B_HSHAKE_ERR, s_axi_if.bvalid && !s_axi_if.bready => s_axi_if.bvalid, clk, !rst_n) + + // Exclusive access rules: + // - Must have an address that is aligned to burst byte count + // - Byte count must be power of 2 inside 1:128 + // - Max burst length = 16 + `CALIPTRA_ASSERT (ERR_AXI_EX_UNALGN , (s_axi_if.awvalid && s_axi_if.awlock) -> ~|s_axi_if.awaddr[$clog2((1< ((1< (s_axi_if.awlen < 16), clk, !rst_n) + + + // --------------------------------------- // + // Coverage // + // --------------------------------------- // + +endmodule diff --git a/src/axi/rtl/skidbuffer.v b/src/axi/rtl/skidbuffer.v new file mode 100644 index 000000000..e6b1a62a2 --- /dev/null +++ b/src/axi/rtl/skidbuffer.v @@ -0,0 +1,495 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Filename: skidbuffer.v +// {{{ +// Project: WB2AXIPSP: bus bridges and other odds and ends +// +// Purpose: A basic SKID buffer. +// {{{ +// Skid buffers are required for high throughput AXI code, since the AXI +// specification requires that all outputs be registered. This means +// that, if there are any stall conditions calculated, it will take a clock +// cycle before the stall can be propagated up stream. This means that +// the data will need to be buffered for a cycle until the stall signal +// can make it to the output. +// +// Handling that buffer is the purpose of this core. +// +// On one end of this core, you have the i_valid and i_data inputs to +// connect to your bus interface. There's also a registered o_ready +// signal to signal stalls for the bus interface. +// +// The other end of the core has the same basic interface, but it isn't +// registered. This allows you to interact with the bus interfaces +// as though they were combinatorial logic, by interacting with this half +// of the core. +// +// If at any time the incoming !stall signal, i_ready, signals a stall, +// the incoming data is placed into a buffer. Internally, that buffer +// is held in r_data with the r_valid flag used to indicate that valid +// data is within it. +// }}} +// Parameters: +// {{{ +// DW or data width +// In order to make this core generic, the width of the data in the +// skid buffer is parameterized +// +// OPT_LOWPOWER +// Forces both o_data and r_data to zero if the respective *VALID +// signal is also low. While this costs extra logic, it can also +// be used to guarantee that any unused values aren't toggling and +// therefore unnecessarily using power. +// +// This excess toggling can be particularly problematic if the +// bus signals have a high fanout rate, or a long signal path +// across an FPGA. +// +// OPT_OUTREG +// Causes the outputs to be registered +// +// OPT_PASSTHROUGH +// Turns the skid buffer into a passthrough. Used for formal +// verification only. +// }}} +// Creator: Dan Gisselquist, Ph.D. +// Gisselquist Technology, LLC +// +//////////////////////////////////////////////////////////////////////////////// +// }}} +// Copyright (C) 2019-2024, Gisselquist Technology, LLC +// {{{ +// This file is part of the WB2AXIP project. +// +// The WB2AXIP project contains free software and gateware, licensed under the +// Apache License, Version 2.0 (the "License"). You may not use this project, +// or this file, except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. +// +//////////////////////////////////////////////////////////////////////////////// +// +// +`default_nettype none +// }}} +module skidbuffer #( + // {{{ + parameter [0:0] OPT_LOWPOWER = 0, + parameter [0:0] OPT_OUTREG = 1, + // + parameter [0:0] OPT_PASSTHROUGH = 0, + parameter DW = 8, + parameter [0:0] OPT_INITIAL = 1'b1 + // }}} + ) ( + // {{{ + input wire i_clk, i_reset, + input wire i_valid, + output wire o_ready, + input wire [DW-1:0] i_data, + output wire o_valid, + input wire i_ready, + output reg [DW-1:0] o_data + // }}} + ); + + wire [DW-1:0] w_data; + + generate if (OPT_PASSTHROUGH) + begin : PASSTHROUGH + // {{{ + assign { o_valid, o_ready } = { i_valid, i_ready }; + + always @(*) + if (!i_valid && OPT_LOWPOWER) + o_data = 0; + else + o_data = i_data; + + assign w_data = 0; + + // Keep Verilator happy + // Verilator lint_off UNUSED + // {{{ + wire unused_passthrough; + assign unused_passthrough = &{ 1'b0, i_clk, i_reset }; + // }}} + // Verilator lint_on UNUSED + // }}} + end else begin : LOGIC + // We'll start with skid buffer itself + // {{{ + reg r_valid; + reg [DW-1:0] r_data; + + // r_valid + // {{{ + initial if (OPT_INITIAL) r_valid = 0; + always @(posedge i_clk) + if (i_reset) + r_valid <= 0; + else if ((i_valid && o_ready) && (o_valid && !i_ready)) + // We have incoming data, but the output is stalled + r_valid <= 1; + else if (i_ready) + r_valid <= 0; + // }}} + + // r_data + // {{{ + initial if (OPT_INITIAL) r_data = 0; + always @(posedge i_clk) + if (OPT_LOWPOWER && i_reset) + r_data <= 0; + else if (OPT_LOWPOWER && (!o_valid || i_ready)) + r_data <= 0; + else if ((!OPT_LOWPOWER || !OPT_OUTREG || i_valid) && o_ready) + r_data <= i_data; + + assign w_data = r_data; + // }}} + + // o_ready + // {{{ + assign o_ready = !r_valid; + // }}} + + // + // And then move on to the output port + // + if (!OPT_OUTREG) + begin : NET_OUTPUT + // Outputs are combinatorially determined from inputs + // {{{ + // o_valid + // {{{ + assign o_valid = !i_reset && (i_valid || r_valid); + // }}} + + // o_data + // {{{ + always @(*) + if (r_valid) + o_data = r_data; + else if (!OPT_LOWPOWER || i_valid) + o_data = i_data; + else + o_data = 0; + // }}} + // }}} + end else begin : REG_OUTPUT + // Register our outputs + // {{{ + // o_valid + // {{{ + reg ro_valid; + + initial if (OPT_INITIAL) ro_valid = 0; + always @(posedge i_clk) + if (i_reset) + ro_valid <= 0; + else if (!o_valid || i_ready) + ro_valid <= (i_valid || r_valid); + + assign o_valid = ro_valid; + // }}} + + // o_data + // {{{ + initial if (OPT_INITIAL) o_data = 0; + always @(posedge i_clk) + if (OPT_LOWPOWER && i_reset) + o_data <= 0; + else if (!o_valid || i_ready) + begin + + if (r_valid) + o_data <= r_data; + else if (!OPT_LOWPOWER || i_valid) + o_data <= i_data; + else + o_data <= 0; + end + // }}} + + // }}} + end + // }}} + end endgenerate + + // Keep Verilator happy + // {{{ + // Verilator lint_off UNUSED + wire unused; + assign unused = &{ 1'b0, w_data }; + // Verilator lint_on UNUSED + // }}} +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// +// Formal properties +// {{{ +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +`ifdef FORMAL +`ifdef SKIDBUFFER +`define ASSUME assume +`else +`define ASSUME assert +`endif + + reg f_past_valid; + + initial f_past_valid = 0; + always @(posedge i_clk) + f_past_valid <= 1; + + always @(*) + if (!f_past_valid) + assume(i_reset); + + //////////////////////////////////////////////////////////////////////// + // + // Incoming stream properties / assumptions + // {{{ + //////////////////////////////////////////////////////////////////////// + // + always @(posedge i_clk) + if (!f_past_valid) + begin + `ASSUME(!i_valid || !OPT_INITIAL); + end else if ($past(i_valid && !o_ready && !i_reset) && !i_reset) + `ASSUME(i_valid && $stable(i_data)); + +`ifdef VERIFIC +`define FORMAL_VERIFIC + // Reset properties + property RESET_CLEARS_IVALID; + @(posedge i_clk) i_reset |=> !i_valid; + endproperty + + property IDATA_HELD_WHEN_NOT_READY; + @(posedge i_clk) disable iff (i_reset) + i_valid && !o_ready |=> i_valid && $stable(i_data); + endproperty + +`ifdef SKIDBUFFER + assume property (IDATA_HELD_WHEN_NOT_READY); +`else + assert property (IDATA_HELD_WHEN_NOT_READY); +`endif +`endif + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Outgoing stream properties / assumptions + // {{{ + //////////////////////////////////////////////////////////////////////// + // + + generate if (!OPT_PASSTHROUGH) + begin + + always @(posedge i_clk) + if (!f_past_valid) // || $past(i_reset)) + begin + // Following any reset, valid must be deasserted + assert(!o_valid || !OPT_INITIAL); + end else if ($past(o_valid && !i_ready && !i_reset) && !i_reset) + // Following any stall, valid must remain high and + // data must be preserved + assert(o_valid && $stable(o_data)); + + end endgenerate + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Other properties + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + generate if (!OPT_PASSTHROUGH) + begin + // Rule #1: + // If registered, then following any reset we should be + // ready for a new request + // {{{ + always @(posedge i_clk) + if (f_past_valid && $past(OPT_OUTREG && i_reset)) + assert(o_ready); + // }}} + + // Rule #2: + // All incoming data must either go directly to the + // output port, or into the skid buffer + // {{{ +`ifndef VERIFIC + always @(posedge i_clk) + if (f_past_valid && !$past(i_reset) && $past(i_valid && o_ready + && (!OPT_OUTREG || o_valid) && !i_ready)) + assert(!o_ready && w_data == $past(i_data)); +`else + assert property (@(posedge i_clk) + disable iff (i_reset) + (i_valid && o_ready + && (!OPT_OUTREG || o_valid) && !i_ready) + |=> (!o_ready && w_data == $past(i_data))); +`endif + // }}} + + // Rule #3: + // After the last transaction, o_valid should become idle + // {{{ + if (!OPT_OUTREG) + begin + // {{{ + always @(posedge i_clk) + if (f_past_valid && !$past(i_reset) && !i_reset + && $past(i_ready)) + begin + assert(o_valid == i_valid); + assert(!i_valid || (o_data == i_data)); + end + // }}} + end else begin + // {{{ + always @(posedge i_clk) + if (f_past_valid && !$past(i_reset)) + begin + if ($past(i_valid && o_ready)) + assert(o_valid); + + if ($past(!i_valid && o_ready && i_ready)) + assert(!o_valid); + end + // }}} + end + // }}} + + // Rule #4 + // Same thing, but this time for o_ready + // {{{ + always @(posedge i_clk) + if (f_past_valid && $past(!o_ready && i_ready)) + assert(o_ready); + // }}} + + // If OPT_LOWPOWER is set, o_data and w_data both need to be + // zero any time !o_valid or !r_valid respectively + // {{{ + if (OPT_LOWPOWER) + begin + always @(*) + if ((OPT_OUTREG || !i_reset) && !o_valid) + assert(o_data == 0); + + always @(*) + if (o_ready) + assert(w_data == 0); + + end + // }}} + end endgenerate + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Cover checks + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // +`ifdef SKIDBUFFER + generate if (!OPT_PASSTHROUGH) + begin + reg f_changed_data; + + initial f_changed_data = 0; + always @(posedge i_clk) + if (i_reset) + f_changed_data <= 1; + else if (i_valid && $past(!i_valid || o_ready)) + begin + if (i_data != $past(i_data + 1)) + f_changed_data <= 0; + end else if (!i_valid && i_data != 0) + f_changed_data <= 0; + + +`ifndef VERIFIC + reg [3:0] cvr_steps, cvr_hold; + + always @(posedge i_clk) + if (i_reset) + begin + cvr_steps <= 0; + cvr_hold <= 0; + end else begin + cvr_steps <= cvr_steps + 1; + cvr_hold <= cvr_hold + 1; + case(cvr_steps) + 0: if (o_valid || i_valid) + cvr_steps <= 0; + 1: if (!i_valid || !i_ready) + cvr_steps <= 0; + 2: if (!i_valid || !i_ready) + cvr_steps <= 0; + 3: if (!i_valid || !i_ready) + cvr_steps <= 0; + 4: if (!i_valid || i_ready) + cvr_steps <= 0; + 5: if (!i_valid || !i_ready) + cvr_steps <= 0; + 6: if (!i_valid || !i_ready) + cvr_steps <= 0; + 7: if (!i_valid || i_ready) + cvr_steps <= 0; + 8: if (!i_valid || i_ready) + cvr_steps <= 0; + 9: if (!i_valid || !i_ready) + cvr_steps <= 0; + 10: if (!i_valid || !i_ready) + cvr_steps <= 0; + 11: if (!i_valid || !i_ready) + cvr_steps <= 0; + 12: begin + cvr_steps <= cvr_steps; + cover(!o_valid && !i_valid && f_changed_data); + if (!o_valid || !i_ready) + cvr_steps <= 0; + else + cvr_hold <= cvr_hold + 1; + end + default: assert(0); + endcase + end + +`else + // Cover test + cover property (@(posedge i_clk) + disable iff (i_reset) + (!o_valid && !i_valid) + ##1 i_valid && i_ready [*3] + ##1 i_valid && !i_ready + ##1 i_valid && i_ready [*2] + ##1 i_valid && !i_ready [*2] + ##1 i_valid && i_ready [*3] + // Wait for the design to clear + ##1 o_valid && i_ready [*0:5] + ##1 (!o_valid && !i_valid && f_changed_data)); +`endif + end endgenerate +`endif // SKIDBUFFER + // }}} +`endif +// }}} +endmodule