| //////////////////////////////////////////////////////////////////////////////// |
| // |
| // 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-2020, 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, |
| // |
| // Shorthand for address width, data width, and id width |
| // AW, and DW, are short-hand abbreviations used locally. |
| localparam AW = C_AXI_ADDR_WIDTH, |
| localparam DW = C_AXI_DATA_WIDTH, |
| localparam IW = C_AXI_ID_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 |
| ); |
| // |
| // |
| // LGNS is the number of bits required in a slave index |
| localparam LGNS = $clog2(NS); |
| // |
| localparam [1:0] OKAY = 2'b00, |
| EXOKAY = 2'b01, |
| SLVERR = 2'b10, |
| INTERCONNECT_ERROR = 2'b11; |
| localparam ADDR_LSBS = $clog2(DW)-3; |
| // |
| |
| //////////////////////////////////////////////////////////////////////// |
| // |
| // 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; |
| |
| //////////////////////////////////////////////////////////////////////// |
| |
| reg locked_burst, locked_write, lock_valid; |
| integer k; |
| |
| //////////////////////////////////////////////////////////////////////// |
| // |
| // Write logic: |
| // |
| //////////////////////////////////////////////////////////////////////// |
| // |
| // |
| 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; |
| reg [LGNS-1:0] write_windex, 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; |
| |
| |
| // |
| // 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 })); |
| |
| // |
| // 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; |
| end else if (S_AXI_WVALID && S_AXI_WREADY) |
| // Following each write beat, we need to update our address |
| write_addr <= next_waddr; |
| |
| // |
| // 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); |
| |
| |
| // |
| // 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 (awskid_valid && write_awskidready) |
| begin |
| write_request <= 1'b1; |
| write_topofburst <= 1'b1; |
| write_len <= awskid_awlen; |
| 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 |
| |
| // 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)); |
| |
| // 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; |
| |
| always @(*) |
| begin |
| if (dcd_awvalid) |
| wdecode = raw_wdecode; |
| else |
| wdecode = last_wdecode; |
| end |
| |
| // |
| // 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; |
| |
| // 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; |
| |
| // Back out an index from our decoded slave value |
| always @(*) |
| begin |
| write_windex = 0; |
| for(k=0; k<NS; k=k+1) |
| if (wdecode[k]) |
| write_windex = write_windex | k[LGNS-1:0]; |
| end |
| |
| always @(posedge S_AXI_ACLK) |
| begin |
| write_bindex <= write_windex; |
| write_no_index <= wdecode[NS]; |
| end |
| |
| always @(posedge S_AXI_ACLK) |
| // if (write_top_of_burst) // -- not necessary |
| write_bid <= write_id; |
| |
| // write_bvalid will be true one clock after the last write is accepted. |
| // This is the internal signal that would've come from a subordinate |
| // slave's BVALID, save that we are generating it internally. |
| initial { write_response, write_bvalid } = 0; |
| always @(posedge S_AXI_ACLK) |
| if (!S_AXI_ARESETN) |
| { write_response, write_bvalid } <= 0; |
| else |
| { write_response, write_bvalid } <= { write_bvalid, |
| (S_AXI_WVALID && S_AXI_WREADY&& S_AXI_WLAST) }; |
| |
| // |
| // Examine write beats, not just write bursts |
| always @(posedge S_AXI_ACLK) |
| begin |
| write_top_beat <= write_topofburst; |
| write_beat_bvalid <= S_AXI_WVALID && S_AXI_WREADY; |
| end |
| |
| // The response from any burst should be an DECERR (interconnect |
| // error) if ever the addressed slave doesn't exist in our address map. |
| // This is sticky: any attempt to generate a request to a non-existent |
| // slave will generate an interconnect error. Likewise, if the slave |
| // ever returns a slave error, we'll propagate it back in the burst |
| // return. Finally, on an exclusive access burst, we'll return EXOKAY |
| // if we could write the values. |
| always @(posedge S_AXI_ACLK) |
| if (write_beat_bvalid) |
| begin |
| if (write_no_index) |
| write_resp <= INTERCONNECT_ERROR; |
| else if (M_AXI_BRESP[2*write_bindex]) |
| write_resp <= { 1'b1, (write_top_beat) |
| ? 1'b0 : write_resp[0] }; |
| else if (write_top_beat || !write_resp[1]) |
| write_resp <= { 1'b0, (write_top_beat && locked_burst && locked_write) }; |
| end |
| |
| always @(posedge S_AXI_ACLK) |
| write_retid <= write_bid; |
| |
| // |
| // The pseudo-FIFO for the write side. This counter will let us know |
| // if any write response will ever overflow our write response FIFO, |
| // allowing us to be able to confidently deal with any backpressure. |
| initial write_count = 0; |
| initial bfull = 0; |
| always @(posedge S_AXI_ACLK) |
| if (!S_AXI_ARESETN) |
| begin |
| write_count <= 0; |
| bfull <= 0; |
| end else case({ (awskid_valid && write_awskidready), |
| (S_AXI_BVALID & S_AXI_BREADY) }) |
| 2'b01: begin |
| write_count <= write_count - 1; |
| bfull <= 1'b0; |
| end |
| 2'b10: begin |
| write_count <= write_count + 1; |
| bfull <= (&write_count[LGFLEN-1:0]); |
| end |
| default: begin end |
| endcase |
| |
| // |
| // Backpressure FIFO on write response returns |
| sfifo #(.BW(C_AXI_ID_WIDTH+2), .OPT_ASYNC_READ(0), .LGFLEN(LGFLEN)) |
| bfifo ( .i_clk(S_AXI_ACLK), .i_reset(!S_AXI_ARESETN), |
| .i_wr(write_response), .i_data({ write_retid, write_resp }), |
| .o_full(bffull), .o_fill(bfill), |
| .i_rd(S_AXI_BVALID && S_AXI_BREADY), |
| .o_data({ S_AXI_BID, S_AXI_BRESP }), |
| .o_empty(bempty)); |
| |
| assign S_AXI_BVALID = !bempty; |
| |
| //////////////////////////////////////////////////////////////////////// |
| // |
| // Read logic |
| // |
| //////////////////////////////////////////////////////////////////////// |
| // |
| // |
| wire rempty, rdfull; |
| wire [LGFLEN:0] rfill; |
| reg [LGNS-1:0] read_index, 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; |
| |
| |
| // |
| // Copy the burst information, for use in determining the next address |
| always @(posedge S_AXI_ACLK) |
| if (S_AXI_ARVALID && S_AXI_ARREADY) |
| begin |
| araddr <= S_AXI_ARADDR; |
| arid <= S_AXI_ARID; |
| arlen <= S_AXI_ARLEN; |
| arsize <= S_AXI_ARSIZE; |
| arburst <= S_AXI_ARBURST; |
| arlock <= S_AXI_ARLOCK; |
| arprot <= S_AXI_ARPROT; |
| end else if (issue_read) |
| araddr <= next_araddr; |
| |
| // Count the number of remaining items in a burst. Note that rlen |
| // counts from N-1 to 0, not from N to 1. |
| initial rlen = 0; |
| always @(posedge S_AXI_ACLK) |
| if (!S_AXI_ARESETN) |
| rlen <= 0; |
| else if (S_AXI_ARVALID && S_AXI_ARREADY) |
| rlen <= S_AXI_ARLEN; |
| else if (issue_read && (rlen > 0)) |
| rlen <= rlen - 1; |
| |
| // 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; |
| |
| // 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); |
| |
| // |
| // Decode which slave is being addressed by this read. |
| wire [0:0] unused_pin; |
| 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])); |
| |
| // |
| // 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; |
| |
| 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 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) }; |
| |
| // 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) |
| if (arvalid && issue_read) |
| begin |
| read_rvid <= arid; |
| read_rvlast <= (rlen == 0); |
| read_rvlock <= OPT_EXCLUSIVE_ACCESS && arlock && lock_valid; |
| end |
| |
| // 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. |
| // |
| |
| // |
| // First step is to calculate the index of the slave |
| always @(*) |
| begin |
| read_index = 0; |
| |
| for(k=0; k<NS; k=k+1) |
| if (rdecode[k]) |
| read_index = read_index | k[LGNS-1:0]; |
| end |
| |
| // |
| // Keep this index into the RVALID cycle |
| always @(posedge S_AXI_ACLK) |
| last_read_index <= read_index; |
| |
| // read_no_index is a flag to indicate that no slave was indexed. |
| always @(posedge S_AXI_ACLK) |
| read_no_index <= rdecode[NS]; |
| |
| // Now we can use last_read_index to determine the return data. |
| // read_rdata will be valid on the same clock $past(RVALID) or |
| // read_return cycle |
| always @(posedge S_AXI_ACLK) |
| read_rdata <= M_AXI_RDATA[DW*last_read_index +: DW]; |
| |
| // |
| // As with read_rdata, read_resp is the response from the slave |
| always @(posedge S_AXI_ACLK) |
| if (read_no_index) |
| read_resp <= INTERCONNECT_ERROR; |
| else if (M_AXI_RRESP[2*last_read_index + 1]) |
| read_resp <= SLVERR; // SLVERR |
| else if (OPT_EXCLUSIVE_ACCESS && read_rvlock) |
| read_resp <= EXOKAY; // Exclusive access Okay |
| else |
| read_resp <= OKAY; // OKAY |
| |
| // |
| // Since we can't allow the incoming requests to overflow in the |
| // presence of any back pressure, let's create a phantom FIFO here |
| // counting the number of values either in the final pipeline or in |
| // final read FIFO. If read_full is true, the FIFO is full and we |
| // cannot move any more data forward. |
| initial { read_count, read_full } = 0; |
| always @(posedge S_AXI_ACLK) |
| if (!S_AXI_ARESETN) |
| { read_count, read_full } <= 0; |
| else case({ read_rwait && issue_read, S_AXI_RVALID & S_AXI_RREADY}) |
| 2'b10: begin |
| read_count <= read_count + 1; |
| read_full <= &read_count[LGFLEN-1:0]; |
| end |
| 2'b01: begin |
| read_count <= read_count - 1; |
| read_full <= 1'b0; |
| end |
| default: begin end |
| endcase |
| |
| assign S_AXI_ARREADY = (rlen == 0) && !read_full; |
| |
| // |
| // Send the return results through a synchronous FIFO to handle |
| // back-pressure. Doing this costs us one clock of latency. |
| sfifo #(.BW(C_AXI_ID_WIDTH+DW+1+2), .OPT_ASYNC_READ(0), .LGFLEN(LGFLEN)) |
| rfifo ( .i_clk(S_AXI_ACLK), .i_reset(!S_AXI_ARESETN), |
| .i_wr(read_result), .i_data({ read_retid, read_rdata, |
| read_retlast, read_resp }), |
| .o_full(rdfull), .o_fill(rfill), |
| .i_rd(S_AXI_RVALID && S_AXI_RREADY), |
| .o_data({ S_AXI_RID, S_AXI_RDATA, S_AXI_RLAST, S_AXI_RRESP }),.o_empty(rempty)); |
| |
| assign S_AXI_RVALID = !rempty; |
| |
| //////////////////////////////////////////////////////////////////////// |
| // |
| // Exclusive access / Bus locking logic |
| // |
| //////////////////////////////////////////////////////////////////////// |
| // |
| // |
| generate if (OPT_EXCLUSIVE_ACCESS) |
| begin : EXCLUSIVE_ACCESS |
| |
| reg [AW-1:0] lock_addr, lock_last; |
| reg [4-1:0] lock_len; |
| reg [3-1:0] lock_size; |
| |
| initial lock_valid = 1'b0; |
| initial locked_burst = 1'b0; |
| initial locked_write = 1'b0; |
| always @(posedge S_AXI_ACLK) |
| begin |
| |
| // |
| // Step one: Set the lock_valid signal. This means |
| // that a read request has been successful requesting |
| // the lock for this address. |
| // |
| if (awskid_valid && write_awskidready) |
| begin |
| // On any write to the value inside our lock |
| // range, disable the lock_valid signal |
| if ((awskid_awaddr |
| + ({ {(AW-4){1'b0}},awskid_awlen[3:0]} << S_AXI_AWSIZE) |
| >= lock_addr) |
| &&(S_AXI_AWADDR <= lock_last)) |
| lock_valid <= 0; |
| end |
| |
| if (S_AXI_ARVALID && S_AXI_ARREADY && S_AXI_ARLOCK) |
| begin |
| lock_valid <= !locked_write; |
| lock_addr <= S_AXI_ARADDR; |
| 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 |
| locked_burst <= 1'b0; |
| locked_write <= awskid_awlock; |
| |
| if (awskid_awlock) |
| begin |
| locked_burst <= lock_valid; |
| if (lock_addr != awskid_awaddr) |
| locked_burst <= 1'b0; |
| if (lock_size != awskid_awsize) |
| locked_burst <= 1'b0; |
| if ({ 4'h0, lock_len } != awskid_awlen) |
| 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) |
| locked_burst <= 1'b0; |
| |
| if (!S_AXI_ARESETN) |
| begin |
| lock_valid <= 1'b0; |
| locked_burst <= 1'b0; |
| end |
| end |
| 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; |
| |
| always @(*) |
| locked_burst <= 1'b0; |
| |
| always @(*) |
| lock_valid = 1'b0; |
| 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 |
| // |
| // The following are a subset of the formal properties used to verify this |
| // module. |
| // |
| //////////////////////////////////////////////////////////////////////////////// |
| //////////////////////////////////////////////////////////////////////////////// |
| `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 |