//////////////////////////////////////////////////////////////////////////////// // // Filename: axidouble.v // {{{ // Project: WB2AXIPSP: bus bridges and other odds and ends // // Purpose: Create a special AXI slave which can be used to reduce crossbar // logic for multiple simplified AXI slaves. This is a companion // core to the similar axisingle core, but allowing the slave to // decode the clock between multiple possible addresses. // // To use this, the slave must follow specific (simplified AXI) rules: // // Write interface // -------------------- // 1. The controller will guarantee that AWVALID == WVALID // (You can connect AWVALID to WVALID when connecting to your core) // 2. The controller will guarantee that AWID == 0 for the slave // All ID logic will be handled internally // 3. The controller will guarantee that AWLEN == 0 and WLAST == 1 // Instead, the controller will handle all burst addressing // internally // 4. This makes AWBURST irrelevant // 5. Other wires are simplified as well: AWLOCK=0, AWCACHE=3, AWQOS=0 // 6. If OPT_EXCLUSIVE_ACCESS is set, the controller will handle lock // logic internally // 7. The slave must guarantee that AWREADY == WREADY = 1 // (This core doesn't have AWREADY or WREADY inputs) // 8. The slave must also guarantee that BVALID == $past(AWVALID) // (This core internally generates BVALID, and so the slave's // BVALID return is actually ignored.) // 9. The controller will also guarantee that BREADY = 1 // (This core doesn't have a BVALID input) // // The controller will maintain AWPROT in case the slave wants to // disallow particular writes, and AWSIZE so the slave can know how many // bytes are being accessed. // // Read interface // -------------------- // 1. The controller will guarantee that RREADY = 1 // (This core doesn't have an RREADY output) // 2. The controller will guarantee that ARID = 0 // All IDs are handled internally // 3. The controller will guarantee that ARLEN == 0 // All burst logic is handled internally // 4. As before, this makes ARBURST irrelevant // 5. Other wires are simplified: ARLOCK=0, ARCACHE = 3, ARQOS=0, etc // 6. The slave must guarantee that RVALID == $past(ARVALID) // The controller actually ignores RVALID--but to be a valid slave, // this must be assumed. // 7. The slave must also guarantee that RLAST == 1 anytime RVALID // // As with the write side, the controller will fill in ARSIZE and ARPROT. // They may be used or ignored by the slave. // // Why? This simplifies slave logic. Slaves may interact with the bus // using only the logic below: // // always @(posedge S_AXI_ACLK) // if (AWVALID) case(AWADDR) // R1: slvreg_1 <= WDATA; // R2: slvreg_2 <= WDATA; // R3: slvreg_3 <= WDATA; // R4: slvreg_4 <= WDATA; // endcase // // always @(*) // BRESP = 2'b00; // OKAY // // always @(posedge S_AXI_ACLK) // if (ARVALID) // case(ARADDR) // R1: RDATA <= slvreg_1; // R2: RDATA <= slvreg_2; // R3: RDATA <= slvreg_3; // R4: RDATA <= slvreg_4; // endcase // // always @(*) // RRESP = 2'b00; // OKAY // // This core will then keep track of the more complex bus logic, locking, // burst length, burst ID's, etc, simplifying both slaves and connection // logic. Slaves with the more complicated (and proper/accurate) logic, // that follow the rules above, should have no problems with this // additional logic. // // Performance: // // Throughput: The slave can sustain one read/write per clock as long as // the upstream master keeps S_AXI_[BR]READY high. If S_AXI_[BR]READY // ever drops, there's some flexibility provided by the return FIFO, so // the master might not notice a drop in throughput until the FIFO fills. // // Latency: This core will create a four clock latency on all requests. // // Logic: Actual logic depends upon how this is set up and built. As // parameterized below, this core can fit within 639 Xilinx 6-LUTs and // 39 M-LUTs. // // Narrow bursts: This core supports narrow bursts by nature. Whether the // subcores pay attention to WSTRB, AWSIZE, and ARSIZE is up to the // subcore itself. // // 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 axidouble #( // {{{ parameter integer C_AXI_DATA_WIDTH = 32, parameter integer C_AXI_ADDR_WIDTH = 32, parameter integer C_AXI_ID_WIDTH = 1, // // NS is the number of slave interfaces. If you are interested // in a single slave interface, checkout the demofull.v core // in this same repository. parameter NS = 8, // // OPT_LOWPOWER is generated by the interconnect, so we need // to define it here. parameter [0:0] OPT_LOWPOWER = 1'b0, // // Shorthand for address width, data width, and id width // AW, and DW, are short-hand abbreviations used locally. localparam AW = C_AXI_ADDR_WIDTH, // // Each of the slave interfaces has an address range. The // base address for each slave is given by AW bits of SLAVE_ADDR // below. parameter [NS*AW-1:0] SLAVE_ADDR = { { 3'b111, {(AW-3){1'b0}} }, { 3'b110, {(AW-3){1'b0}} }, { 3'b101, {(AW-3){1'b0}} }, { 3'b100, {(AW-3){1'b0}} }, { 3'b011, {(AW-3){1'b0}} }, { 3'b010, {(AW-3){1'b0}} }, { 4'b0001,{(AW-4){1'b0}} }, { 4'b0000,{(AW-4){1'b0}} } }, // // // The relevant bits of the slave address are given in // SLAVE_MASK below, at AW bits per slave. To be valid, // SLAVE_ADDR & ~SLAVE_MASK must be zero. Only the masked // bits will be used in any compare. // // Also, while not technically required, it is strongly // recommended that the bottom 12-bits of each AW bits of // the SLAVE_MASK bust be zero. parameter [NS*AW-1:0] SLAVE_MASK = (NS <= 1) ? 0 : { {(NS-2){ 3'b111, {(AW-3){1'b0}} }}, {(2){ 4'b1111, {(AW-4){1'b0}} }} }, // // LGFLEN specifies the log (based two) of the number of // transactions that may need to be held outstanding internally. // If you really want high throughput, and if you expect any // back pressure at all, then increase LGFLEN. Otherwise the // default value of 3 (FIFO size = 8) should be sufficient // to maintain full loading parameter LGFLEN=3, // // This core will handle exclusive access if // OPT_EXCLUSIVE_ACCESS is set to one. If set to 1, all // subcores will have exclusive access applied. There is no // core-by-core means of enabling exclusive access at this time. parameter [0:0] OPT_EXCLUSIVE_ACCESS = 1'b1 // }}} ) ( // {{{ input wire S_AXI_ACLK, input wire S_AXI_ARESETN, // // Write address channel coming from upstream input wire S_AXI_AWVALID, output wire S_AXI_AWREADY, input wire [C_AXI_ID_WIDTH-1:0] S_AXI_AWID, input wire [C_AXI_ADDR_WIDTH-1:0] S_AXI_AWADDR, input wire [8-1:0] S_AXI_AWLEN, input wire [3-1:0] S_AXI_AWSIZE, input wire [2-1:0] S_AXI_AWBURST, input wire S_AXI_AWLOCK, input wire [4-1:0] S_AXI_AWCACHE, input wire [3-1:0] S_AXI_AWPROT, input wire [4-1:0] S_AXI_AWQOS, // // Write data channel coming from upstream 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, input wire S_AXI_WLAST, // // Write responses sent back output wire S_AXI_BVALID, input wire S_AXI_BREADY, output wire [C_AXI_ID_WIDTH-1:0] S_AXI_BID, output wire [2-1:0] S_AXI_BRESP, // // Read address request channel from upstream input wire S_AXI_ARVALID, output wire S_AXI_ARREADY, input wire [C_AXI_ID_WIDTH-1:0] S_AXI_ARID, input wire [C_AXI_ADDR_WIDTH-1:0] S_AXI_ARADDR, input wire [8-1:0] S_AXI_ARLEN, input wire [3-1:0] S_AXI_ARSIZE, input wire [2-1:0] S_AXI_ARBURST, input wire S_AXI_ARLOCK, input wire [4-1:0] S_AXI_ARCACHE, input wire [3-1:0] S_AXI_ARPROT, input wire [4-1:0] S_AXI_ARQOS, // // Read data return channel back upstream output wire S_AXI_RVALID, input wire S_AXI_RREADY, output wire [C_AXI_ID_WIDTH-1:0] S_AXI_RID, output wire [C_AXI_DATA_WIDTH-1:0] S_AXI_RDATA, output wire S_AXI_RLAST, output wire [2-1:0] S_AXI_RRESP, // // // Now for the simplified downstream interface to a series // of downstream slaves. All outgoing wires are shared between // the slaves save the AWVALID and ARVALID signals. Slave // returns are not shared. // // // Simplified Write address channel. output wire [NS-1:0] M_AXI_AWVALID, // input wire M_AXI_AWREADY is assumed to be 1 output wire [0:0] M_AXI_AWID,// = 0 output wire [C_AXI_ADDR_WIDTH-1:0] M_AXI_AWADDR, output wire [8-1:0] M_AXI_AWLEN,// = 0 output wire [3-1:0] M_AXI_AWSIZE, output wire [2-1:0] M_AXI_AWBURST,//=INC output wire M_AXI_AWLOCK,// = 0 output wire [4-1:0] M_AXI_AWCACHE,// = 0 output wire [3-1:0] M_AXI_AWPROT,// = 0 output wire [4-1:0] M_AXI_AWQOS,// = 0 // // Simplified write data channel output wire [NS-1:0] M_AXI_WVALID,//=AWVALID // input wire M_AXI_WVALID is *assumed* to be 1 output wire [C_AXI_DATA_WIDTH-1:0] M_AXI_WDATA, output wire [C_AXI_DATA_WIDTH/8-1:0] M_AXI_WSTRB, output wire M_AXI_WLAST,// = 1 // // Simplified write response channel // input wire M_AXI_BVALID is *assumed* to be // $past(M_AXI_AWVALID), and so ignored output wire M_AXI_BREADY,// = 1 input wire [NS*2-1:0] M_AXI_BRESP, // The controller handles BID, so this can be ignored as well // // Simplified read address channel output wire [NS-1:0] M_AXI_ARVALID, // input wire M_AXI_ARREADY is assumed to be 1 output wire [0:0] M_AXI_ARID,// = 0 output wire [C_AXI_ADDR_WIDTH-1:0] M_AXI_ARADDR, output wire [8-1:0] M_AXI_ARLEN,// = 0 output wire [3-1:0] M_AXI_ARSIZE, output wire [2-1:0] M_AXI_ARBURST,//=INC output wire M_AXI_ARLOCK,// = 0 output wire [4-1:0] M_AXI_ARCACHE,// = 0 output wire [3-1:0] M_AXI_ARPROT,// = 0 output wire [4-1:0] M_AXI_ARQOS,// = 0 // // Simplified read data return channel // input wire M_AXI_RVALID is assumed to be $past(ARVALID,1) output wire M_AXI_RREADY,// = 1 input wire [NS*C_AXI_DATA_WIDTH-1:0] M_AXI_RDATA, input wire [NS*2-1:0] M_AXI_RRESP // input wire M_AXI_RLAST is assumed to be 1 // }}} ); // Signal declarations // {{{ localparam DW = C_AXI_DATA_WIDTH; localparam IW = C_AXI_ID_WIDTH; // LGNS is the number of bits required in a slave index localparam LGNS = (NS <= 1) ? 1 : $clog2(NS); // localparam [1:0] OKAY = 2'b00, EXOKAY = 2'b01, SLVERR = 2'b10, INTERCONNECT_ERROR = 2'b11; // localparam ADDR_LSBS = $clog2(DW)-3; // reg locked_burst, locked_write, lock_valid; // Write signals // {{{ wire awskd_stall; wire awskid_valid, bffull, bempty, write_awskidready, dcd_awvalid; reg write_bvalid, write_response; reg bfull, write_no_index; wire [NS:0] raw_wdecode; reg [NS:0] last_wdecode, wdecode; wire [AW-1:0] m_awaddr; wire [LGNS-1:0] write_windex; reg [LGNS-1:0] write_bindex; wire [3-1:0] awskid_prot, m_axi_awprot; wire [LGFLEN:0] bfill; reg [LGFLEN:0] write_count; reg [1:0] write_resp; // reg [C_AXI_ID_WIDTH-1:0] write_id, write_bid, write_retid; reg [C_AXI_ADDR_WIDTH-1:0] write_addr; wire [C_AXI_ID_WIDTH-1:0] awskid_awid; wire [C_AXI_ADDR_WIDTH-1:0] awskid_awaddr, next_waddr; reg write_request, write_topofburst, write_beat_bvalid; reg [3-1:0] write_size; reg [2-1:0] write_burst; reg [8-1:0] write_len, write_awlen; wire [8-1:0] awskid_awlen; wire [2-1:0] awskid_awburst; wire [3-1:0] awskid_awsize; wire awskid_awlock; // reg write_top_beat; // }}} // Read signals // {{{ wire rempty, rdfull; wire [LGFLEN:0] rfill; wire [LGNS-1:0] read_index; reg [LGNS-1:0] last_read_index; reg [1:0] read_resp; reg [DW-1:0] read_rdata; wire read_rwait, arskd_stall; reg read_rvalid, read_result, read_no_index; wire [AW-1:0] m_araddr; reg [AW-1:0] araddr; reg [3-1:0] arprot; wire [NS:0] raw_rdecode; reg [C_AXI_ID_WIDTH-1:0] arid, read_rvid, read_retid; reg [3-1:0] arsize; reg [2-1:0] arburst; reg arlock, read_rvlock; reg read_rvlast, read_retlast; reg [8-1:0] arlen, rlen; wire [C_AXI_ADDR_WIDTH-1:0] next_araddr; wire issue_read; reg read_full; reg [LGFLEN:0] read_count; reg arvalid; reg [NS:0] last_rdecode, rdecode; wire [0:0] unused_pin; // }}} // }}} //////////////////////////////////////////////////////////////////////// // // Unused wire assignments // {{{ //////////////////////////////////////////////////////////////////////// // // assign M_AXI_AWID = 0; assign M_AXI_AWLEN = 0; assign M_AXI_AWBURST = 2'b00; assign M_AXI_AWLOCK = 1'b0; assign M_AXI_AWCACHE = 4'h3; // assign M_AXI_AWPROT = 3'h0; assign M_AXI_AWQOS = 4'h0; // assign M_AXI_WVALID = M_AXI_AWVALID; assign M_AXI_WLAST = 1'b1; // assign M_AXI_BREADY = 1'b1; // assign M_AXI_ARID = 1'b0; assign M_AXI_ARLEN = 8'h0; // Burst of one beat assign M_AXI_ARBURST = 2'b00; // INC assign M_AXI_ARLOCK = 1'b0; assign M_AXI_ARCACHE = 4'h3; // assign M_AXI_ARPROT = 3'h0; assign M_AXI_ARQOS = 4'h0; // assign M_AXI_RREADY = -1; // }}} //////////////////////////////////////////////////////////////////////// // // Write logic: // {{{ //////////////////////////////////////////////////////////////////////// // // // // Incoming write address requests must go through a skidbuffer. By // keeping OPT_OUTREG == 0, this shouldn't cost us any time, but should // instead buy us the ability to keep AWREADY high even if for reasons // we can't act on AWVALID & AWREADY on the same cycle skidbuffer #( // {{{ .OPT_OUTREG(0), .DW(C_AXI_ID_WIDTH+AW+8+3+2+1+3) // }}} ) awskid( // {{{ .i_clk(S_AXI_ACLK), .i_reset(!S_AXI_ARESETN), .i_valid(S_AXI_AWVALID), .o_ready(S_AXI_AWREADY), .i_data({ S_AXI_AWID, S_AXI_AWADDR, S_AXI_AWLEN, S_AXI_AWSIZE, S_AXI_AWBURST, S_AXI_AWLOCK, S_AXI_AWPROT }), .o_valid(awskid_valid), .i_ready(write_awskidready), .o_data({ awskid_awid, awskid_awaddr, awskid_awlen, awskid_awsize, awskid_awburst, awskid_awlock, awskid_prot }) // }}} ); // write_addr and other write_* // {{{ // On any write address request (post-skidbuffer), copy down the details // of that request. Once these details are valid (i.e. on the next // clock), S_AXI_WREADY will be true. always @(posedge S_AXI_ACLK) if (awskid_valid && write_awskidready) begin write_id <= awskid_awid; write_addr <= awskid_awaddr; write_size <= awskid_awsize; write_awlen <= awskid_awlen; write_burst <= awskid_awburst; // write_lock <= awskid_awlock; if (OPT_LOWPOWER && !awskid_valid) begin write_id <= {(C_AXI_ID_WIDTH){1'b0}}; write_addr <= {(C_AXI_ADDR_WIDTH){1'b0}}; write_size <= 3'h0; write_awlen <= 8'h0; write_burst <= 2'b00; end end else if (S_AXI_WVALID && S_AXI_WREADY) // Following each write beat, we need to update our address write_addr <= next_waddr; // }}} // next_waddr from get_next_write_address // {{{ // Given the details of the address request, get the next address to // write to. axi_addr #( // {{{ .AW(C_AXI_ADDR_WIDTH), .DW(C_AXI_DATA_WIDTH) // }}} ) get_next_write_address( // {{{ write_addr, write_size, write_burst, write_awlen, next_waddr // }}} ); // }}} // write_request, write_topofburst, write_len // {{{ // Count through the beats of the burst in write_len. write_topofburst // indicates the first beat in any new burst, but will be zero for all // subsequent burst beats. write_request is true anytime we are trying // to write. initial write_request = 1'b0; initial write_topofburst = 1'b1; initial write_len = 0; always @(posedge S_AXI_ACLK) if (!S_AXI_ARESETN) begin // {{{ write_request <= 1'b0; write_topofburst <= 1'b1; write_len <= 0; // }}} end else if (write_awskidready) begin // {{{ write_request <= awskid_valid; write_topofburst <= awskid_valid; write_len <= (awskid_valid) ? awskid_awlen : 8'h00; // }}} end else if (S_AXI_WVALID && S_AXI_WREADY) begin // {{{ write_topofburst <= 1'b0; if (S_AXI_WLAST) write_request <= 1'b0; if (write_len > 0) write_len <= write_len - 1; // }}} end // }}} // Slave address decoding // {{{ // Decode our incoming address in order to determine the next // slave the address addresses addrdecode #( // {{{ .AW(AW), .DW(3), .NS(NS), .SLAVE_ADDR(SLAVE_ADDR), .SLAVE_MASK(SLAVE_MASK), .OPT_REGISTERED(1'b1) // }}} ) wraddr( // {{{ .i_clk(S_AXI_ACLK), .i_reset(!S_AXI_ARESETN), .i_valid(awskid_valid && write_awskidready), .o_stall(awskd_stall), // .i_addr(awskid_valid && write_awskidready // ? awskid_awaddr : next_waddr), .i_addr(awskid_awaddr), .i_data(awskid_prot), .o_valid(dcd_awvalid), .i_stall(!S_AXI_WVALID), .o_decode(raw_wdecode), .o_addr(m_awaddr), .o_data(m_axi_awprot) // }}} ); // }}} // last_wdecode // {{{ // We only do our decode on the address request. We need the decoded // values long after the top of the burst. Therefore, let's use // dcd_awvalid to know we have a valid output from the decoder and // then we'll latch that (register it really) for the rest of the burst always @(posedge S_AXI_ACLK) if (dcd_awvalid) last_wdecode <= raw_wdecode; // }}} // wdecode // {{{ always @(*) begin if (dcd_awvalid) wdecode = raw_wdecode; else wdecode = last_wdecode; end // }}} // Downstream slave (write) signals // {{{ // It's now time to create our write request for the slave. Slave // writes take place on the clock after address valid is true as long // as S_AXI_WVALID is true. This places combinatorial logic onto the // outgoing AWVALID. The sign that we are in the middle of a burst // will specifically be that WREADY is true. // // // If there were any part of this algorithm I disliked it would be the // AWVALID logic here. It shouldn't nearly be this loaded. assign S_AXI_WREADY = write_request; assign M_AXI_AWVALID = (S_AXI_WVALID && write_request && (!locked_burst || locked_write)) ? wdecode[NS-1:0] : 0; assign M_AXI_AWADDR = write_addr; assign M_AXI_AWPROT = m_axi_awprot; assign M_AXI_AWSIZE = write_size; assign M_AXI_WDATA = S_AXI_WDATA; assign M_AXI_WSTRB = S_AXI_WSTRB; // }}} // write_awskidready // {{{ // We can accept a new value from the skid buffer as soon as the last // write value comes in, or equivalently if we are not in the middle // of a write. This is all subject, of course, to our backpressure // FIFO not being full. assign write_awskidready = ((S_AXI_WVALID&&S_AXI_WLAST) || !S_AXI_WREADY) && !bfull; // }}} // write_windex // {{{ // Back out an index from our decoded slave value generate if (NS <= 1) begin : WR_ONE_SLAVE assign write_windex = 0; end else begin : WR_INDEX reg [LGNS-1:0] r_write_windex; integer k; always @(*) begin r_write_windex = 0; for(k=0; k 0)) rlen <= rlen - 1; // }}} // arvalid // {{{ // Should the slave M_AXI_ARVALID be true in general? Based upon // rlen above, but still needs to be gated across all slaves. initial arvalid = 1'b0; always @(posedge S_AXI_ACLK) if (!S_AXI_ARESETN) arvalid <= 1'b0; else if (S_AXI_ARVALID && S_AXI_ARREADY) arvalid <= 1'b1; else if (issue_read && (rlen == 0)) arvalid <= 1'b0; // }}} // next_araddr -- Get the next AXI address // {{{ axi_addr #( // {{{ .AW(C_AXI_ADDR_WIDTH), .DW(C_AXI_DATA_WIDTH) // }}} ) get_next_read_address( // {{{ araddr, arsize, arburst, arlen, next_araddr // }}} ); // }}} // raw_rdecode-- Decode which slave is being addressed by this read. // {{{ addrdecode #( // {{{ .AW(AW), .DW(1), .NS(NS), .SLAVE_ADDR(SLAVE_ADDR), .SLAVE_MASK(SLAVE_MASK), .OPT_REGISTERED(1'b1) // }}} ) rdaddr( // {{{ .i_clk(S_AXI_ACLK), .i_reset(!S_AXI_ARESETN), .i_valid(S_AXI_ARVALID && S_AXI_ARREADY || rlen>0), // Warning: there's no skid on this stall .o_stall(arskd_stall), .i_addr((S_AXI_ARVALID & S_AXI_ARREADY) ? S_AXI_ARADDR : next_araddr), .i_data(1'b0), .o_valid(read_rwait), .i_stall(!issue_read), .o_decode(raw_rdecode), .o_addr(m_araddr), .o_data(unused_pin[0]) // }}} ); // }}} // last_rdecode // {{{ // We want the value from the decoder on the first clock cycle. It // may not be valid after that, so we'll hold on to it in last_rdecode initial last_rdecode = 0; always @(posedge S_AXI_ACLK) if (read_rwait) last_rdecode <= raw_rdecode; // }}} // rdecode // {{{ always @(*) if (read_rwait) rdecode = raw_rdecode; else rdecode = last_rdecode; // }}} // Finally, issue our read request any time the FIFO isn't full // {{{ assign issue_read = !read_full; assign M_AXI_ARVALID = issue_read ? rdecode[NS-1:0] : 0; assign M_AXI_ARADDR = m_araddr; assign M_AXI_ARPROT = arprot; assign M_AXI_ARSIZE = arsize; // }}} // read_rvalid, read_result // {{{ // read_rvalid would be the RVALID response from the slave that would // be returned if we checked it. read_result is the same thing--one // clock later. initial { read_result, read_rvalid } = 0; always @(posedge S_AXI_ACLK) if (!S_AXI_ARESETN) { read_result, read_rvalid } <= 2'b00; else { read_result, read_rvalid } <= { read_rvalid, (arvalid&issue_read) }; // }}} // read_rvid, read_rvlast, read_rvlock // {{{ // On the same clock when rvalid is true, we'll also want to know // if RLAST should be true (decoded here, not in the slave), and // whether or not the transaction is locked. These values are valid // any time read_rvalid is true. always @(posedge S_AXI_ACLK) begin if (arvalid && issue_read) begin read_rvid <= arid; read_rvlast <= (rlen == 0); read_rvlock <= (read_rvlock && !read_rvlast) || (OPT_EXCLUSIVE_ACCESS && arlock && lock_valid); end else if (read_rvlast) read_rvlock <= 1'b0; if (!S_AXI_ARESETN) begin read_rvlock <= 1'b0; read_rvlast <= 1'b1; end end // }}} // read_retid, read_retlast // {{{ // read_result is true one clock after read_rvalid is true. Copy // the ID and LAST values into this pipeline clock cycle always @(posedge S_AXI_ACLK) begin read_retid <= read_rvid; read_retlast <= read_rvlast; end // }}} // // Decode the read value. // // read_index - First step is to calculate the index of the slave // {{{ generate if (NS <= 1) begin : RD_ONE_SLAVE assign read_index = 0; end else begin : RD_INDEX reg [LGNS-1:0] r_read_index = 0; integer k; always @(*) begin r_read_index = 0; for(k=0; k= lock_addr) &&(S_AXI_AWADDR <= lock_last)) r_lock_valid <= 0; end if (S_AXI_ARVALID && S_AXI_ARREADY && S_AXI_ARLOCK && S_AXI_ARBURST == 2'b01) begin r_lock_valid <= !locked_write; lock_addr <= S_AXI_ARADDR; lock_id <= S_AXI_ARID; lock_size <= S_AXI_ARSIZE; lock_len <= S_AXI_ARLEN[3:0]; lock_last <= S_AXI_ARADDR + ({ {(AW-4){1'b0}}, lock_len } << S_AXI_ARSIZE); end if (awskid_valid && write_awskidready) begin r_locked_burst <= 1'b0; locked_write <= awskid_awlock; if (awskid_awlock) begin r_locked_burst <= r_lock_valid; if (lock_addr != awskid_awaddr) r_locked_burst <= 1'b0; if (lock_id != awskid_awid) r_locked_burst <= 1'b0; if (lock_size != awskid_awsize) r_locked_burst <= 1'b0; if (lock_len != awskid_awlen[3:0]) r_locked_burst <= 1'b0; if (2'b01 != awskid_awburst) r_locked_burst <= 1'b0; end // Write if !locked_write || write_burst // EXOKAY on locked_write && write_burst // OKAY on all other writes where the slave // does not assert an error end else if (S_AXI_WVALID && S_AXI_WREADY && S_AXI_WLAST) r_locked_burst <= 1'b0; if (!S_AXI_ARESETN) begin r_lock_valid <= 1'b0; r_locked_burst <= 1'b0; end end assign locked_burst = r_locked_burst; assign lock_valid = r_lock_valid; // }}} end else begin : NO_EXCLUSIVE_ACCESS // {{{ // Keep track of whether or not the current burst requests // exclusive access or not. locked_write is an important // signal used to make certain that we do not write to our // slave on any locked write requests. (Shouldn't happen, // since we aren't returning any EXOKAY's from reads ...) always @(posedge S_AXI_ACLK) if (awskid_valid && write_awskidready) locked_write <= awskid_awlock; else if (S_AXI_WVALID && S_AXI_WREADY && S_AXI_WLAST) locked_write <= 1'b0; assign locked_burst = 0; assign lock_valid = 0; // }}} end endgenerate // }}} // Make Verilator happy // {{{ // verilator lint_off UNUSED wire unused; assign unused = &{ 1'b0, S_AXI_AWCACHE, S_AXI_ARCACHE, S_AXI_AWQOS, S_AXI_ARQOS, dcd_awvalid, m_awaddr, unused_pin, bffull, rdfull, bfill, rfill, awskd_stall, arskd_stall }; // verilator lint_on UNUSED // }}} //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// // // Formal verification properties // {{{ //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// `ifdef FORMAL localparam F_LGDEPTH = LGFLEN+9; // // ... // faxi_slave #( .C_AXI_DATA_WIDTH(C_AXI_DATA_WIDTH), .C_AXI_ADDR_WIDTH(C_AXI_ADDR_WIDTH), .C_AXI_ID_WIDTH(C_AXI_ID_WIDTH), .F_AXI_MAXDELAY(5), .F_LGDEPTH(F_LGDEPTH)) properties ( .i_clk(S_AXI_ACLK), .i_axi_reset_n(S_AXI_ARESETN), // .i_axi_awvalid(S_AXI_AWVALID), .i_axi_awready(S_AXI_AWREADY), .i_axi_awid( S_AXI_AWID), .i_axi_awaddr( S_AXI_AWADDR), .i_axi_awlen( S_AXI_AWLEN), .i_axi_awsize( S_AXI_AWSIZE), .i_axi_awburst(S_AXI_AWBURST), .i_axi_awlock( S_AXI_AWLOCK), .i_axi_awcache(S_AXI_AWCACHE), .i_axi_awprot( S_AXI_AWPROT), .i_axi_awqos( S_AXI_AWQOS), // .i_axi_wvalid(S_AXI_WVALID), .i_axi_wready(S_AXI_WREADY), .i_axi_wdata( S_AXI_WDATA), .i_axi_wstrb( S_AXI_WSTRB), .i_axi_wlast( S_AXI_WLAST), // .i_axi_bvalid(S_AXI_BVALID), .i_axi_bready(S_AXI_BREADY), .i_axi_bid( S_AXI_BID), .i_axi_bresp( S_AXI_BRESP), // .i_axi_arvalid(S_AXI_ARVALID), .i_axi_arready(S_AXI_ARREADY), .i_axi_arid( S_AXI_ARID), .i_axi_araddr( S_AXI_ARADDR), .i_axi_arlen( S_AXI_ARLEN), .i_axi_arsize( S_AXI_ARSIZE), .i_axi_arburst(S_AXI_ARBURST), .i_axi_arlock( S_AXI_ARLOCK), .i_axi_arcache(S_AXI_ARCACHE), .i_axi_arprot( S_AXI_ARPROT), .i_axi_arqos( S_AXI_ARQOS), // .i_axi_rvalid(S_AXI_RVALID), .i_axi_rready(S_AXI_RREADY), .i_axi_rid( S_AXI_RID), .i_axi_rdata( S_AXI_RDATA), .i_axi_rlast( S_AXI_RLAST), .i_axi_rresp( S_AXI_RRESP) // // ... // ); // // ... // always @(*) if (!OPT_EXCLUSIVE_ACCESS) begin assert(!S_AXI_BVALID || S_AXI_BRESP != EXOKAY); assert(!S_AXI_RVALID || S_AXI_RRESP != EXOKAY); end //////////////////////////////////////////////////////////////////////// // // Properties necessary to pass induction // //////////////////////////////////////////////////////////////////////// // // always @(*) assert($onehot0(M_AXI_AWVALID)); always @(*) assert($onehot0(M_AXI_ARVALID)); // // // Write properties // // always @(*) if (S_AXI_WVALID && S_AXI_WREADY) begin if (locked_burst && !locked_write) assert(M_AXI_AWVALID == 0); else if (wdecode[NS]) assert(M_AXI_AWVALID == 0); else begin assert($onehot(M_AXI_AWVALID)); assert(M_AXI_AWVALID == wdecode[NS-1:0]); end end else assert(M_AXI_AWVALID == 0); // // ... // // // // Read properties // // // // ... // //////////////////////////////////////////////////////////////////////// // // Simplifying (careless) assumptions // // Caution: these might void your proof // //////////////////////////////////////////////////////////////////////// // // localparam [0:0] F_CHECK_WRITES = 1'b1; localparam [0:0] F_CHECK_READS = 1'b1; generate if (!F_CHECK_WRITES) begin always @(*) assume(!S_AXI_AWVALID); always @(*) assert(!S_AXI_BVALID); always @(*) assert(!M_AXI_AWVALID); // ... end endgenerate generate if (!F_CHECK_READS) begin always @(*) assume(!S_AXI_ARVALID); always @(*) assert(!S_AXI_RVALID); always @(*) assert(M_AXI_ARVALID == 0); always @(*) assert(rdecode == 0); // ... end endgenerate // // ... // //////////////////////////////////////////////////////////////////////// // // Cover properties // //////////////////////////////////////////////////////////////////////// // // reg [3:0] cvr_arvalids, cvr_awvalids, cvr_reads, cvr_writes; (* anyconst *) reg cvr_burst; always @(*) if (cvr_burst && S_AXI_AWVALID) assume(S_AXI_AWLEN > 2); always @(*) if (cvr_burst && S_AXI_ARVALID) assume(S_AXI_ARLEN > 2); initial cvr_awvalids = 0; always @(posedge S_AXI_ACLK) if (!cvr_burst || !S_AXI_ARESETN) cvr_awvalids <= 0; else if (S_AXI_AWVALID && S_AXI_AWREADY && !(&cvr_awvalids)) cvr_awvalids <= cvr_awvalids + 1; initial cvr_arvalids = 0; always @(posedge S_AXI_ACLK) if (!cvr_burst || !S_AXI_ARESETN) cvr_arvalids <= 0; else if (S_AXI_ARVALID && S_AXI_ARREADY && !(&cvr_arvalids)) cvr_arvalids <= cvr_arvalids + 1; initial cvr_writes = 0; always @(posedge S_AXI_ACLK) if (!cvr_burst || !S_AXI_ARESETN) cvr_writes <= 0; else if (S_AXI_BVALID && S_AXI_BREADY && !(&cvr_writes)) cvr_writes <= cvr_writes + 1; initial cvr_reads = 0; always @(posedge S_AXI_ACLK) if (!S_AXI_ARESETN) cvr_reads <= 0; else if (S_AXI_RVALID && S_AXI_RREADY && S_AXI_RLAST && !(&cvr_arvalids)) cvr_reads <= cvr_reads + 1; generate if (F_CHECK_WRITES) begin : COVER_WRITES always @(*) cover(cvr_awvalids > 2); always @(*) cover(cvr_writes > 2); always @(*) cover(cvr_writes > 4); end endgenerate generate if (F_CHECK_READS) begin : COVER_READS always @(*) cover(cvr_arvalids > 2); always @(*) cover(cvr_reads > 2); always @(*) cover(cvr_reads > 4); end endgenerate always @(*) cover((cvr_writes > 2) && (cvr_reads > 2)); generate if (OPT_EXCLUSIVE_ACCESS) begin : COVER_EXCLUSIVE_ACCESS always @(*) cover(S_AXI_BVALID && S_AXI_BRESP == EXOKAY); end endgenerate `endif endmodule