//////////////////////////////////////////////////////////////////////////////// // // Filename: axil2apb.v // {{{ // Project: WB2AXIPSP: bus bridges and other odds and ends // // Purpose: High throughput AXI-lite bridge to APB. With both skid // buffers enabled, it can handle 50% throughput--the maximum // that APB can handle. // // Creator: Dan Gisselquist, Ph.D. // Gisselquist Technology, LLC // //////////////////////////////////////////////////////////////////////////////// // }}} // Copyright (C) 2020-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 axil2apb #( // {{{ parameter C_AXI_ADDR_WIDTH = 32, parameter C_AXI_DATA_WIDTH = 32, // OPT_OUTGOING_SKIDBUFFER: required for 50% throughput parameter [0:0] OPT_OUTGOING_SKIDBUFFER = 1'b0 // }}} ) ( // {{{ input wire S_AXI_ACLK, input wire S_AXI_ARESETN, // // The AXI-lite interface // {{{ input wire S_AXI_AWVALID, output wire S_AXI_AWREADY, input wire [C_AXI_ADDR_WIDTH-1:0] S_AXI_AWADDR, input wire [2:0] S_AXI_AWPROT, // input wire S_AXI_WVALID, output wire S_AXI_WREADY, input wire [C_AXI_DATA_WIDTH-1 : 0] S_AXI_WDATA, input wire [(C_AXI_DATA_WIDTH/8)-1:0] S_AXI_WSTRB, // output reg S_AXI_BVALID, input wire S_AXI_BREADY, output reg [1:0] S_AXI_BRESP, // input wire S_AXI_ARVALID, output wire S_AXI_ARREADY, input wire [C_AXI_ADDR_WIDTH-1:0] S_AXI_ARADDR, input wire [2:0] S_AXI_ARPROT, // output reg S_AXI_RVALID, input wire S_AXI_RREADY, output reg [C_AXI_DATA_WIDTH-1:0] S_AXI_RDATA, output reg [1:0] S_AXI_RRESP, // }}} // // The APB interface // {{{ output reg M_APB_PSEL, output reg M_APB_PENABLE, input wire M_APB_PREADY, output reg [C_AXI_ADDR_WIDTH-1:0] M_APB_PADDR, output reg M_APB_PWRITE, output reg [C_AXI_DATA_WIDTH-1:0] M_APB_PWDATA, output reg [C_AXI_DATA_WIDTH/8-1:0] M_APB_PWSTRB, output reg [2:0] M_APB_PPROT, input wire [C_AXI_DATA_WIDTH-1:0] M_APB_PRDATA, input wire M_APB_PSLVERR // }}} // }}} ); // Register declarations // {{{ localparam AW = C_AXI_ADDR_WIDTH; localparam DW = C_AXI_DATA_WIDTH; localparam AXILLSB = $clog2(C_AXI_DATA_WIDTH)-3; wire awskd_valid, wskd_valid, arskd_valid; reg axil_write_ready, axil_read_ready, write_grant, apb_idle; wire [AW-AXILLSB-1:0] awskd_addr, arskd_addr; wire [DW-1:0] wskd_data; wire [DW/8-1:0] wskd_strb; wire [2:0] awskd_prot, arskd_prot; reg apb_bvalid, apb_rvalid, apb_error, out_skid_full; reg [DW-1:0] apb_data; // }}} //////////////////////////////////////////////////////////////////////// // // Incoming AXI-lite write interface // {{{ //////////////////////////////////////////////////////////////////////// // // // awskd - write address skid buffer // {{{ skidbuffer #(.DW(C_AXI_ADDR_WIDTH-AXILLSB + 3), .OPT_OUTREG(0) ) awskd (.i_clk(S_AXI_ACLK), .i_reset(!S_AXI_ARESETN), .i_valid(S_AXI_AWVALID), .o_ready(S_AXI_AWREADY), .i_data({ S_AXI_AWADDR[AW-1:AXILLSB], S_AXI_AWPROT }), .o_valid(awskd_valid), .i_ready(axil_write_ready), .o_data({ awskd_addr, awskd_prot })); // }}} // wskd - write data skid buffer // {{{ skidbuffer #(.DW(DW+(DW/8)), .OPT_OUTREG(0) ) wskd (.i_clk(S_AXI_ACLK), .i_reset(!S_AXI_ARESETN), .i_valid(S_AXI_WVALID), .o_ready(S_AXI_WREADY), .i_data({ S_AXI_WDATA, S_AXI_WSTRB }), .o_valid(wskd_valid), .i_ready(axil_write_ready), .o_data({ wskd_data, wskd_strb })); // }}} // apb_idle // {{{ always @(*) begin apb_idle = !M_APB_PSEL;// || (M_APB_PENABLE && M_APB_PREADY); if (OPT_OUTGOING_SKIDBUFFER && (M_APB_PENABLE && M_APB_PREADY)) apb_idle = 1'b1; end // }}} // axil_write_ready // {{{ always @(*) begin axil_write_ready = apb_idle; if (S_AXI_BVALID && !S_AXI_BREADY) axil_write_ready = 1'b0; if (!awskd_valid || !wskd_valid) axil_write_ready = 1'b0; if (!write_grant && arskd_valid) axil_write_ready = 1'b0; end // }}} // }}} //////////////////////////////////////////////////////////////////////// // // Incoming AXI-lite read interface // {{{ //////////////////////////////////////////////////////////////////////// // // // arskd buffer // {{{ skidbuffer #(.DW(C_AXI_ADDR_WIDTH-AXILLSB+3), .OPT_OUTREG(0) ) arskd (.i_clk(S_AXI_ACLK), .i_reset(!S_AXI_ARESETN), .i_valid(S_AXI_ARVALID), .o_ready(S_AXI_ARREADY), .i_data({ S_AXI_ARADDR[AW-1:AXILLSB], S_AXI_ARPROT }), .o_valid(arskd_valid), .i_ready(axil_read_ready), .o_data({ arskd_addr, arskd_prot })); // }}} // axil_read_ready // {{{ always @(*) begin axil_read_ready = apb_idle; if (S_AXI_RVALID && !S_AXI_RREADY) axil_read_ready = 1'b0; if (write_grant && awskd_valid && wskd_valid) axil_read_ready = 1'b0; if (!arskd_valid) axil_read_ready = 1'b0; end // }}} // }}} //////////////////////////////////////////////////////////////////////// // // Arbitrate among reads and writes --- alternating arbitration // {{{ //////////////////////////////////////////////////////////////////////// // // // write_grant -- alternates // {{{ always @(posedge S_AXI_ACLK) if (apb_idle) begin if (axil_write_ready) write_grant <= 1'b0; else if (axil_read_ready) write_grant <= 1'b1; end // }}} // }}} //////////////////////////////////////////////////////////////////////// // // Drive the APB bus // {{{ //////////////////////////////////////////////////////////////////////// // // // APB bus // {{{ initial M_APB_PSEL = 1'b0; initial M_APB_PENABLE = 1'b0; always @(posedge S_AXI_ACLK) begin if (apb_idle) begin M_APB_PSEL <= 1'b0; if (axil_read_ready) begin M_APB_PSEL <= 1'b1; M_APB_PADDR <= { arskd_addr, {(AXILLSB){1'b0}} }; M_APB_PWRITE <= 1'b0; M_APB_PPROT <= arskd_prot; end else if (axil_write_ready) begin M_APB_PSEL <= 1'b1; M_APB_PADDR <= { awskd_addr, {(AXILLSB){1'b0}} }; M_APB_PWRITE <= 1'b1; M_APB_PPROT <= awskd_prot; end if (wskd_valid) begin M_APB_PWDATA <= wskd_data; M_APB_PWSTRB <= wskd_strb; end M_APB_PENABLE <= 1'b0; end else if (!M_APB_PENABLE) M_APB_PENABLE <= 1'b1; else if (M_APB_PREADY) begin // if (M_APB_PSEL && M_APB_ENABLE) M_APB_PENABLE <= 1'b0; M_APB_PSEL <= 1'b0; end if (!S_AXI_ARESETN) begin M_APB_PSEL <= 1'b0; M_APB_PENABLE <= 1'b0; end end // }}} reg r_apb_bvalid, r_apb_rvalid, r_apb_error; reg [DW-1:0] r_apb_data; generate if (OPT_OUTGOING_SKIDBUFFER) begin : GEN_OSKID // {{{ // r_apb_bvalid, r_apb_rvalid, r_apb_error, r_apb_data // {{{ initial r_apb_bvalid = 1'b0; initial r_apb_rvalid = 1'b0; always @(posedge S_AXI_ACLK) begin if (M_APB_PSEL && M_APB_PENABLE && M_APB_PREADY) begin r_apb_bvalid <= (S_AXI_BVALID && !S_AXI_BREADY) && M_APB_PWRITE; r_apb_rvalid <= (S_AXI_RVALID && !S_AXI_RREADY) && !M_APB_PWRITE; if (!M_APB_PWRITE) r_apb_data <= M_APB_PRDATA; r_apb_error <= M_APB_PSLVERR; end else begin if (S_AXI_BREADY) r_apb_bvalid <= 1'b0; if (S_AXI_RREADY) r_apb_rvalid <= 1'b0; end if (!S_AXI_ARESETN) begin r_apb_bvalid <= 1'b0; r_apb_rvalid <= 1'b0; end end // }}} // apb_bvalid // {{{ always @(*) apb_bvalid = (M_APB_PSEL && M_APB_PENABLE && M_APB_PREADY && M_APB_PWRITE)|| r_apb_bvalid; // }}} // apb_rvalid // {{{ always @(*) apb_rvalid = (M_APB_PSEL && M_APB_PENABLE && M_APB_PREADY && !M_APB_PWRITE)||r_apb_rvalid; // }}} // apb_data // {{{ always @(*) if (out_skid_full) apb_data = r_apb_data; else apb_data = M_APB_PRDATA; // }}} // apb_error // {{{ always @(*) if (out_skid_full) apb_error = r_apb_error; else apb_error = M_APB_PSLVERR; // }}} always @(*) out_skid_full = r_apb_bvalid || r_apb_rvalid; // }}} end else begin : NO_OSKID // {{{ initial r_apb_bvalid = 1'b0; initial r_apb_rvalid = 1'b0; initial r_apb_error = 1'b0; initial r_apb_data = 0; always @(*) begin r_apb_bvalid = 1'b0; r_apb_rvalid = 1'b0; r_apb_error = 1'b0; r_apb_data = 0; apb_bvalid = M_APB_PSEL && M_APB_PENABLE && M_APB_PREADY && M_APB_PWRITE; apb_rvalid = M_APB_PSEL && M_APB_PENABLE && M_APB_PREADY && !M_APB_PWRITE; apb_data = M_APB_PRDATA; apb_error = M_APB_PSLVERR; out_skid_full = 1'b0; end // Verilator lint_off UNUSED wire skd_unused; assign skd_unused = &{ 1'b0, r_apb_bvalid, r_apb_rvalid, r_apb_data, r_apb_error, out_skid_full }; // Verilator lint_on UNUSED // }}} end endgenerate // }}} //////////////////////////////////////////////////////////////////////// // // AXI-lite write return signaling // {{{ //////////////////////////////////////////////////////////////////////// // // // BVALID // {{{ initial S_AXI_BVALID = 1'b0; always @(posedge S_AXI_ACLK) if (!S_AXI_ARESETN) S_AXI_BVALID <= 1'b0; else if (!S_AXI_BVALID || S_AXI_BREADY) S_AXI_BVALID <= apb_bvalid; // }}} // BRESP // {{{ initial S_AXI_BRESP = 2'b00; always @(posedge S_AXI_ACLK) if (!S_AXI_ARESETN) S_AXI_BRESP <= 2'b00; else if (!S_AXI_BVALID || S_AXI_BREADY) S_AXI_BRESP <= { apb_error, 1'b0 }; // }}} // }}} //////////////////////////////////////////////////////////////////////// // // AXI-lite read return signaling // {{{ //////////////////////////////////////////////////////////////////////// // // // RVALID // {{{ initial S_AXI_RVALID = 1'b0; always @(posedge S_AXI_ACLK) if (!S_AXI_ARESETN) S_AXI_RVALID <= 1'b0; else if (!S_AXI_RVALID || S_AXI_RREADY) S_AXI_RVALID <= apb_rvalid; // }}} // RRESP // {{{ initial S_AXI_RRESP = 2'b00; always @(posedge S_AXI_ACLK) if (!S_AXI_ARESETN) S_AXI_RRESP <= 2'b00; else if (!S_AXI_RVALID || S_AXI_RREADY) S_AXI_RRESP <= { apb_error, 1'b0 }; // }}} // RDATA // {{{ always @(posedge S_AXI_ACLK) if ((!S_AXI_RVALID || S_AXI_RREADY) && apb_rvalid) S_AXI_RDATA <= apb_data; // }}} // }}} // Make Verilator happy // {{{ // Verilator lint_off UNUSED wire unused; assign unused = &{ 1'b0, S_AXI_AWPROT, S_AXI_ARPROT, S_AXI_AWADDR[AXILLSB-1:0], S_AXI_ARADDR[AXILLSB-1:0] }; // Verilator lint_on UNUSED // }}} //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// // // Formal properties // {{{ //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// `ifdef FORMAL localparam F_LGDEPTH = 3; wire [F_LGDEPTH-1:0] faxi_rd_outstanding, faxi_wr_outstanding, faxi_awr_outstanding; reg f_past_valid; initial f_past_valid = 1'b0; always @(posedge S_AXI_ACLK) f_past_valid <= 1'b1; //////////////////////////////////////////////////////////////////////// // // AXI-lite interface properties // {{{ //////////////////////////////////////////////////////////////////////// // // faxil_slave #( // {{{ .C_AXI_ADDR_WIDTH(C_AXI_ADDR_WIDTH), .C_AXI_DATA_WIDTH (C_AXI_DATA_WIDTH), .F_OPT_COVER_BURST(4), .F_AXI_MAXWAIT(17), .F_AXI_MAXDELAY(17), .F_AXI_MAXRSTALL(3), .F_LGDEPTH(F_LGDEPTH) // }}} ) faxil ( // {{{ .i_clk(S_AXI_ACLK), // System clock .i_axi_reset_n(S_AXI_ARESETN), // .i_axi_awvalid(S_AXI_AWVALID), .i_axi_awready(S_AXI_AWREADY), .i_axi_awaddr(S_AXI_AWADDR), .i_axi_awprot(S_AXI_AWPROT), // .i_axi_wready(S_AXI_WREADY), .i_axi_wdata(S_AXI_WDATA), .i_axi_wstrb(S_AXI_WSTRB), .i_axi_wvalid(S_AXI_WVALID), // .i_axi_bresp(S_AXI_BRESP), .i_axi_bvalid(S_AXI_BVALID), .i_axi_bready(S_AXI_BREADY), // .i_axi_arvalid(S_AXI_ARVALID), .i_axi_arready(S_AXI_ARREADY), .i_axi_araddr(S_AXI_ARADDR), .i_axi_arprot(S_AXI_ARPROT), // .i_axi_rvalid(S_AXI_RVALID), .i_axi_rready(S_AXI_RREADY), .i_axi_rresp(S_AXI_RRESP), .i_axi_rdata(S_AXI_RDATA), // .f_axi_rd_outstanding(faxi_rd_outstanding), .f_axi_wr_outstanding(faxi_wr_outstanding), .f_axi_awr_outstanding(faxi_awr_outstanding) // }}} ); // Correlate outstanding counters against our state // {{{ always @(*) if (S_AXI_ARESETN) begin assert(faxi_awr_outstanding == (S_AXI_AWREADY ? 0:1) + (r_apb_bvalid ? 1:0) + (S_AXI_BVALID ? 1:0) + ((M_APB_PSEL && M_APB_PWRITE) ? 1:0)); assert(faxi_wr_outstanding == (S_AXI_WREADY ? 0:1) + (r_apb_bvalid ? 1:0) + (S_AXI_BVALID ? 1:0) + ((M_APB_PSEL && M_APB_PWRITE) ? 1:0)); assert(faxi_rd_outstanding == (S_AXI_ARREADY ? 0:1) + (r_apb_rvalid ? 1:0) + (S_AXI_RVALID ? 1:0) + ((M_APB_PSEL && !M_APB_PWRITE) ? 1:0)); if (r_apb_bvalid) assert(S_AXI_BVALID); if (r_apb_rvalid) assert(S_AXI_RVALID); end // }}} // }}} //////////////////////////////////////////////////////////////////////// // // APB interface properties // {{{ //////////////////////////////////////////////////////////////////////// // // fapb_master #( // {{{ .AW(C_AXI_ADDR_WIDTH), .DW(C_AXI_DATA_WIDTH), .F_OPT_MAXSTALL(3) // }}} ) fapb ( // {{{ .PCLK(S_AXI_ACLK), .PRESETn(S_AXI_ARESETN), .PSEL( M_APB_PSEL), .PENABLE(M_APB_PENABLE), .PREADY( M_APB_PREADY), .PADDR( M_APB_PADDR), .PWRITE( M_APB_PWRITE), .PWDATA( M_APB_PWDATA), .PWSTRB( M_APB_PWSTRB), .PPROT( M_APB_PPROT), .PRDATA( M_APB_PRDATA), .PSLVERR(M_APB_PSLVERR) // }}} ); always @(*) if (!M_APB_PSEL) assert(!M_APB_PENABLE); // }}} //////////////////////////////////////////////////////////////////////// // // Induction invariants // {{{ //////////////////////////////////////////////////////////////////////// // // always @(*) assert(!axil_write_ready || !axil_read_ready); // }}} //////////////////////////////////////////////////////////////////////// // // Contract properties // {{{ //////////////////////////////////////////////////////////////////////// // // (* anyconst *) reg f_never_test; (* anyconst *) reg [AW-1:0] f_never_addr; (* anyconst *) reg [DW-1:0] f_never_data; (* anyconst *) reg [DW/8-1:0] f_never_strb; (* anyconst *) reg [2:0] f_never_prot; // Assume the never values are never received // {{{ always @(*) if (f_never_test) begin assume(f_never_addr[AXILLSB-1:0] == 0); if (S_AXI_AWVALID) begin assume(S_AXI_AWADDR[AW-1:AXILLSB] != f_never_addr[AW-1:AXILLSB]); assume(S_AXI_AWPROT != f_never_prot); end if (S_AXI_WVALID) begin assume(S_AXI_WDATA != f_never_data); assume(S_AXI_WSTRB != f_never_strb); end if (S_AXI_ARVALID) begin assume(S_AXI_ARADDR[AW-1:AXILLSB] != f_never_addr[AW-1:AXILLSB]); assume(S_AXI_ARPROT != f_never_prot); end if (M_APB_PSEL && M_APB_PENABLE && M_APB_PREADY&& !M_APB_PWRITE) assume(M_APB_PRDATA != f_never_data); end // }}} // Assert the never values are never in the incoming skid buffers // {{{ always @(*) if (f_never_test) begin if (awskd_valid) begin assert(awskd_addr != f_never_addr[AW-1:AXILLSB]); assert(awskd_prot != f_never_prot); end if (wskd_valid) begin assert(wskd_data != f_never_data); assert(wskd_strb != f_never_strb); end if (arskd_valid) begin assert(arskd_addr != f_never_addr[AW-1:AXILLSB]); assert(arskd_prot != f_never_prot); end if (r_apb_rvalid) assert(r_apb_data != f_never_data); end // }}} // Assert the never values are never output // {{{ always @(*) if (f_never_test) begin if (M_APB_PSEL) begin assert(M_APB_PADDR != f_never_addr); assert(M_APB_PPROT != f_never_prot); if (M_APB_PWRITE) begin assert(M_APB_PWDATA != f_never_data); assert(M_APB_PWSTRB != f_never_strb); end end if (S_AXI_RVALID) assert(S_AXI_RDATA != f_never_data); end // }}} // }}} //////////////////////////////////////////////////////////////////////// // // Cover checks // {{{ //////////////////////////////////////////////////////////////////////// // // // None (yet) // }}} //////////////////////////////////////////////////////////////////////// // // Careless assumptions // {{{ //////////////////////////////////////////////////////////////////////// // // // }}} `endif // }}} endmodule