359 lines
14 KiB
Verilog
359 lines
14 KiB
Verilog
// Copyright 2018 ETH Zurich and University of Bologna.
|
|
// Copyright and related rights are licensed under the Solderpad Hardware
|
|
// License, Version 0.51 (the "License"); you may not use this file except in
|
|
// compliance with the License. You may obtain a copy of the License at
|
|
// http://solderpad.org/licenses/SHL-0.51. Unless required by applicable law
|
|
// or agreed to in writing, software, hardware and materials distributed under
|
|
// this 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 to SRAM Adapter
|
|
// ----------------------------
|
|
// Author: Florian Zaruba (zarubaf@iis.ee.ethz.ch)
|
|
//
|
|
// Description: Manages AXI transactions
|
|
// Supports all burst accesses but only on aligned addresses and with full data width.
|
|
// Assertions should guide you if there is something unsupported happening.
|
|
//
|
|
module axi2sram_sp_external #(
|
|
parameter AXI_ID_WIDTH = 5,
|
|
parameter AXI_ADDR_WIDTH = 32,
|
|
parameter AXI_DATA_WIDTH = 32,
|
|
parameter FAST_READ_BURST = 1
|
|
)(
|
|
input clk,
|
|
input resetn,
|
|
|
|
input [AXI_ADDR_WIDTH-1:0] s_araddr ,
|
|
input [1 :0] s_arburst,
|
|
input [3 :0] s_arcache,
|
|
input [AXI_ID_WIDTH-1 :0] s_arid ,
|
|
input [7 :0] s_arlen ,
|
|
input s_arlock ,
|
|
input [2 :0] s_arprot ,
|
|
output reg s_arready,
|
|
input [2 :0] s_arsize ,
|
|
input s_arvalid,
|
|
input [AXI_ADDR_WIDTH-1:0] s_awaddr ,
|
|
input [1 :0] s_awburst,
|
|
input [3 :0] s_awcache,
|
|
input [AXI_ID_WIDTH-1 :0] s_awid ,
|
|
input [7 :0] s_awlen ,
|
|
input s_awlock ,
|
|
input [2 :0] s_awprot ,
|
|
output reg s_awready,
|
|
input [2 :0] s_awsize ,
|
|
input s_awvalid,
|
|
output reg [AXI_ID_WIDTH-1 :0] s_bid ,
|
|
input s_bready ,
|
|
output reg [1 :0] s_bresp ,
|
|
output reg s_bvalid ,
|
|
output reg [AXI_DATA_WIDTH-1:0] s_rdata ,
|
|
output reg [AXI_ID_WIDTH-1 :0] s_rid ,
|
|
output reg s_rlast ,
|
|
input s_rready ,
|
|
output reg [1 :0] s_rresp ,
|
|
output reg s_rvalid ,
|
|
input [AXI_DATA_WIDTH-1:0] s_wdata ,
|
|
input s_wlast ,
|
|
output reg s_wready ,
|
|
input [AXI_DATA_WIDTH/8-1:0] s_wstrb ,
|
|
input s_wvalid ,
|
|
|
|
output reg req_o,
|
|
output reg we_o,
|
|
output reg [AXI_ADDR_WIDTH-1:0] addr_o,
|
|
output reg bank_o,
|
|
output reg [AXI_DATA_WIDTH/8-1:0] be_o,
|
|
output reg [AXI_DATA_WIDTH-1:0] data_o,
|
|
input [AXI_DATA_WIDTH-1:0] data_i
|
|
);
|
|
// AXI has the following rules governing the use of bursts:
|
|
// - for wrapping bursts, the burst length must be 2, 4, 8, or 16
|
|
// - a burst must not cross a 4KB address boundary
|
|
// - early termination of bursts is not supported.
|
|
|
|
localparam LOG_NR_BYTES = $clog2(AXI_DATA_WIDTH/8);
|
|
|
|
reg [AXI_ID_WIDTH-1:0] ax_req_d_id;
|
|
reg [AXI_ADDR_WIDTH-1:0] ax_req_d_addr;
|
|
reg [7:0] ax_req_d_len;
|
|
reg [2:0] ax_req_d_size;
|
|
reg [1:0] ax_req_d_burst;
|
|
|
|
reg [AXI_ID_WIDTH-1:0] ax_req_q_id;
|
|
reg [AXI_ADDR_WIDTH-1:0] ax_req_q_addr;
|
|
reg [7:0] ax_req_q_len;
|
|
reg [2:0] ax_req_q_size;
|
|
reg [1:0] ax_req_q_burst;
|
|
|
|
reg [2:0] state_d;
|
|
reg [2:0] state_q;
|
|
|
|
localparam IDLE = 3'h0;
|
|
localparam READ = 3'h1;
|
|
localparam WRITE = 3'h2;
|
|
localparam SEND_B = 3'h3;
|
|
localparam WAIT_WVALID = 3'h4;
|
|
localparam READ_ADDR = 3'h6;
|
|
|
|
localparam FIXED = 2'b00;
|
|
localparam INCR = 2'b01;
|
|
localparam WRAP = 2'b10;
|
|
localparam [AXI_ADDR_WIDTH-1:0] ADDR_INCR =
|
|
{{(AXI_ADDR_WIDTH-1){1'b0}}, 1'b1} << LOG_NR_BYTES;
|
|
|
|
reg [AXI_ADDR_WIDTH-1:0] req_addr_d, req_addr_q;
|
|
reg [7:0] cnt_d, cnt_q;
|
|
reg [AXI_ADDR_WIDTH-1:0] wrap_mask_d, wrap_mask_q;
|
|
reg req_bank_d, req_bank_q;
|
|
reg [AXI_DATA_WIDTH-1:0] read_data_q;
|
|
reg read_capture;
|
|
|
|
wire [AXI_ADDR_WIDTH-1:0] ar_burst_bytes =
|
|
({{(AXI_ADDR_WIDTH-8){1'b0}}, s_arlen} + {{(AXI_ADDR_WIDTH-1){1'b0}}, 1'b1}) << LOG_NR_BYTES;
|
|
wire [AXI_ADDR_WIDTH-1:0] aw_burst_bytes =
|
|
({{(AXI_ADDR_WIDTH-8){1'b0}}, s_awlen} + {{(AXI_ADDR_WIDTH-1){1'b0}}, 1'b1}) << LOG_NR_BYTES;
|
|
wire [AXI_ADDR_WIDTH-1:0] ar_wrap_mask = ar_burst_bytes - {{(AXI_ADDR_WIDTH-1){1'b0}}, 1'b1};
|
|
wire [AXI_ADDR_WIDTH-1:0] aw_wrap_mask = aw_burst_bytes - {{(AXI_ADDR_WIDTH-1){1'b0}}, 1'b1};
|
|
|
|
reg [AXI_ADDR_WIDTH-1:0] next_addr;
|
|
reg [AXI_ADDR_WIDTH-1:0] seq_addr;
|
|
|
|
always @ (*) begin
|
|
seq_addr = req_addr_q + ADDR_INCR;
|
|
case (ax_req_q_burst)
|
|
FIXED: next_addr = req_addr_q;
|
|
INCR: next_addr = seq_addr;
|
|
WRAP: next_addr = (req_addr_q & ~wrap_mask_q) | (seq_addr & wrap_mask_q);
|
|
default: next_addr = seq_addr;
|
|
endcase
|
|
|
|
// Transaction attributes
|
|
// default assignments
|
|
state_d = state_q;
|
|
ax_req_d_id = ax_req_q_id;
|
|
ax_req_d_addr = ax_req_q_addr;
|
|
ax_req_d_len = ax_req_q_len;
|
|
ax_req_d_size = ax_req_q_size;
|
|
ax_req_d_burst = ax_req_q_burst;
|
|
req_addr_d = req_addr_q;
|
|
cnt_d = cnt_q;
|
|
wrap_mask_d = wrap_mask_q;
|
|
req_bank_d = req_bank_q;
|
|
read_capture = 1'b0;
|
|
// Memory default assignments
|
|
data_o = s_wdata;
|
|
be_o = s_wstrb;
|
|
we_o = 1'b0;
|
|
req_o = 1'b0;
|
|
addr_o = 'h0;
|
|
bank_o = req_bank_q;
|
|
// AXI assignments
|
|
// request
|
|
s_awready = 1'b0;
|
|
s_arready = 1'b0;
|
|
// read response channel
|
|
s_rvalid = 1'b0;
|
|
s_rdata = 'h0;
|
|
s_rresp = 'h0;
|
|
s_rlast = 'h0;
|
|
s_rid = ax_req_q_id;
|
|
// slave write data channel
|
|
s_wready = 1'b0;
|
|
// write response channel
|
|
s_bvalid = 1'b0;
|
|
s_bresp = 1'b0;
|
|
s_bid = 1'b0;
|
|
|
|
case (state_q)
|
|
|
|
IDLE: begin
|
|
// Wait for a read or write
|
|
// ------------
|
|
// Read
|
|
// ------------
|
|
if (s_arvalid) begin
|
|
s_arready = 1'b1;
|
|
// sample ax
|
|
ax_req_d_id = s_arid;
|
|
ax_req_d_addr = s_araddr;
|
|
ax_req_d_len = s_arlen;
|
|
ax_req_d_size = s_arsize;
|
|
ax_req_d_burst = s_arburst;
|
|
wrap_mask_d = ar_wrap_mask;
|
|
req_bank_d = s_araddr[22];
|
|
state_d = READ_ADDR;
|
|
// we can request the first address, this saves us time
|
|
req_o = 1'b1;
|
|
addr_o = s_araddr;
|
|
bank_o = s_araddr[22];
|
|
// save the address
|
|
req_addr_d = s_araddr;
|
|
// save the ar_len
|
|
cnt_d = 0;
|
|
// ------------
|
|
// Write
|
|
// ------------
|
|
end else if (s_awvalid) begin
|
|
s_awready = 1'b1;
|
|
s_wready = 1'b1;
|
|
addr_o = s_awaddr;
|
|
// sample ax
|
|
ax_req_d_id = s_awid;
|
|
ax_req_d_addr = s_awaddr;
|
|
ax_req_d_len = s_awlen;
|
|
ax_req_d_size = s_awsize;
|
|
ax_req_d_burst = s_awburst;
|
|
wrap_mask_d = aw_wrap_mask;
|
|
req_bank_d = s_awaddr[22];
|
|
bank_o = s_awaddr[22];
|
|
// we've got our first w_valid so start the write process
|
|
if (s_wvalid) begin
|
|
req_o = 1'b1;
|
|
we_o = 1'b1;
|
|
state_d = (s_wlast) ? SEND_B : WRITE;
|
|
cnt_d = 1;
|
|
// we still have to wait for the first w_valid to arrive
|
|
end else
|
|
state_d = WAIT_WVALID;
|
|
end
|
|
end
|
|
|
|
// ~> we are still missing a w_valid
|
|
WAIT_WVALID: begin
|
|
s_wready = 1'b1;
|
|
addr_o = ax_req_q_addr;
|
|
// we can now make our first request
|
|
if (s_wvalid) begin
|
|
req_o = 1'b1;
|
|
we_o = 1'b1;
|
|
state_d = (s_wlast) ? SEND_B : WRITE;
|
|
cnt_d = 1;
|
|
end
|
|
end
|
|
|
|
READ: begin
|
|
// send the response
|
|
s_rvalid = 1'b1;
|
|
s_rdata = read_data_q;
|
|
s_rid = ax_req_q_id;
|
|
s_rlast = (cnt_q == ax_req_q_len + 1);
|
|
if (FAST_READ_BURST && !s_rlast) begin
|
|
req_o = 1'b1;
|
|
addr_o = next_addr;
|
|
end
|
|
// check that the master is ready, the slave must not wait on this
|
|
if (s_rready) begin
|
|
// we sent the last byte -> go back to idle
|
|
if (s_rlast) begin
|
|
state_d = IDLE;
|
|
// we already got everything
|
|
end
|
|
else begin
|
|
if (FAST_READ_BURST) begin
|
|
read_capture = 1'b1;
|
|
req_addr_d = next_addr;
|
|
cnt_d = cnt_q + 1;
|
|
state_d = READ;
|
|
end else begin
|
|
req_addr_d = next_addr;
|
|
state_d = READ_ADDR;
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
READ_ADDR: begin
|
|
// Fill the read data register after the async-SRAM address has
|
|
// been stable for one full sys_clk cycle.
|
|
req_o = 1'b1;
|
|
addr_o = req_addr_q;
|
|
read_capture = 1'b1;
|
|
cnt_d = cnt_q + 1;
|
|
state_d = READ;
|
|
end
|
|
|
|
// ~> we already wrote the first word here
|
|
WRITE: begin
|
|
|
|
s_wready = 1'b1;
|
|
state_d = WRITE;
|
|
|
|
// consume a word here
|
|
if (s_wvalid) begin
|
|
req_o = 1'b1;
|
|
we_o = 1'b1;
|
|
// ----------------------------
|
|
// Next address generation
|
|
// ----------------------------
|
|
// handle the correct burst type
|
|
addr_o = next_addr;
|
|
// save the request address for the next cycle
|
|
req_addr_d = next_addr;
|
|
// we can decrease the counter as the master has consumed the read data
|
|
cnt_d = cnt_q + 1;
|
|
|
|
if (s_wlast)
|
|
state_d = SEND_B;
|
|
end
|
|
end
|
|
// ~> send a write acknowledge back
|
|
SEND_B: begin
|
|
s_bvalid = 1'b1;
|
|
s_bid = ax_req_q_id;
|
|
if (s_bready)
|
|
state_d = IDLE;
|
|
end
|
|
|
|
endcase
|
|
end
|
|
|
|
// --------------
|
|
// Registers
|
|
// --------------
|
|
always @(posedge clk or negedge resetn) begin
|
|
if (~resetn) begin
|
|
state_q <= IDLE;
|
|
ax_req_q_addr <= 32'h0;
|
|
ax_req_q_burst <= 2'h0;
|
|
ax_req_q_id <= 'h0;
|
|
ax_req_q_len <= 8'h0;
|
|
ax_req_q_size <= 3'h0;
|
|
req_addr_q <= 'h0;
|
|
cnt_q <= 8'h0;
|
|
wrap_mask_q <= 'h0;
|
|
req_bank_q <= 1'b0;
|
|
read_data_q <= 'h0;
|
|
end else begin
|
|
state_q <= state_d;
|
|
ax_req_q_addr <= ax_req_d_addr;
|
|
ax_req_q_burst <= ax_req_d_burst;
|
|
ax_req_q_id <= ax_req_d_id;
|
|
ax_req_q_len <= ax_req_d_len;
|
|
ax_req_q_size <= ax_req_d_size;
|
|
req_addr_q <= req_addr_d;
|
|
cnt_q <= cnt_d;
|
|
wrap_mask_q <= wrap_mask_d;
|
|
req_bank_q <= req_bank_d;
|
|
if (read_capture)
|
|
read_data_q <= data_i;
|
|
end
|
|
end
|
|
|
|
|
|
// always @(posedge clk or negedge resetn) begin
|
|
// if (~resetn) begin
|
|
// s_rdata <= 'h0;
|
|
// end else begin
|
|
// if(req_o == 1'b1 && we_o == 1'b0)
|
|
// s_rdata <= data_i;
|
|
// else
|
|
// s_rdata <= s_rdata;
|
|
// end
|
|
// end
|
|
|
|
endmodule
|