blob: 942ea082d71611febd9fa103ae4e23d39c11403b [file] [log] [blame]
////////////////////////////////////////////////////////////////////////////////
//
// Filename:
// {{{
// Project: WB2AXIPSP: bus bridges and other odds and ends
//
// Purpose: This is a very simple instruction fetch approach based around
// AXI-lite.
//
// Creator: Dan Gisselquist, Ph.D.
// Gisselquist Technology, LLC
//
////////////////////////////////////////////////////////////////////////////////
// }}}
// Copyright (C) 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 axilfetch #(
parameter C_AXI_ADDR_WIDTH = 32,
parameter C_AXI_DATA_WIDTH = 64,
parameter DATA_WIDTH=32,
parameter FETCH_LIMIT=16,
parameter [0:0] SWAP_ENDIANNESS = 1'b1,
localparam AW=C_AXI_ADDR_WIDTH,
localparam DW=DATA_WIDTH,
localparam AXILLSB = $clog2(C_AXI_DATA_WIDTH/8),
localparam INSNS_PER_WORD = C_AXI_DATA_WIDTH / DATA_WIDTH
) (
input wire S_AXI_ACLK,
input wire S_AXI_ARESETN,
//
// CPU interaction wires
input wire i_cpu_reset,
input wire i_new_pc,
input wire i_clear_cache,
input wire i_ready,
input wire [AW-1:0] i_pc, // Ignored unless i_new_pc as well
output reg [DATA_WIDTH-1:0] o_insn, // Instruction read from bus
output reg [AW-1:0] o_pc, // Address of that instruction
output reg o_valid, // If the output is valid
output reg o_illegal, // Result is from a bus error
//
output reg M_AXI_ARVALID,
input wire M_AXI_ARREADY,
output reg [C_AXI_ADDR_WIDTH-1:0] M_AXI_ARADDR,
output reg [2:0] M_AXI_ARPROT,
//
input reg M_AXI_RVALID,
output reg M_AXI_RREADY,
input reg [C_AXI_DATA_WIDTH-1:0] M_AXI_RDATA,
input reg [1:0] M_AXI_RRESP
);
localparam LGDEPTH = $clog2(FETCH_LIMIT)+4;
localparam LGFIFO = $clog2(FETCH_LIMIT);
localparam W = LGDEPTH;
localparam FILLBITS = $clog2(INSNS_PER_WORD);
// ($clog2(INSNS_PER_WORD) > 0)
// ? $clog2(INSNS_PER_WORD) : 1);
reg [W:0] new_flushcount, outstanding,
next_outstanding, flushcount;
reg flushing, flush_request, full_bus;
reg [((AXILLSB>2)?(AXILLSB-3):0):0] shift;
wire fifo_reset, fifo_wr, fifo_rd;
wire ign_fifo_full, fifo_empty;
wire [LGFIFO:0] ign_fifo_fill;
wire [C_AXI_DATA_WIDTH:0] fifo_data;
reg pending_new_pc;
reg [C_AXI_ADDR_WIDTH-1:0] pending_pc;
reg [W-1:0] fill;
reg [FILLBITS:0] out_fill;
reg [C_AXI_DATA_WIDTH-1:0] out_data;
reg [C_AXI_DATA_WIDTH-1:0] endian_swapped_rdata;
assign fifo_reset = i_cpu_reset || i_clear_cache || i_new_pc;
assign fifo_wr = M_AXI_RVALID && !flushing;
// ARPROT = 3'b100 for an unprivileged, secure instruction access
// (not sure what unprivileged or secure mean--even after reading the
// spec)
always @(*)
M_AXI_ARPROT = 3'b100;
always @(*)
begin
next_outstanding = outstanding;
case({ M_AXI_ARVALID && M_AXI_ARREADY, M_AXI_RVALID })
2'b10: next_outstanding = outstanding + 1;
2'b01: next_outstanding = outstanding - 1;
default: begin end
endcase
end
initial outstanding = 0;
initial full_bus = 0;
always @(posedge S_AXI_ACLK)
if (!S_AXI_ARESETN)
begin
outstanding <= 0;
full_bus <= 0;
end else begin
outstanding <= next_outstanding;
full_bus <= (next_outstanding
// + (((M_AXI_ARVALID && !M_AXI_ARREADY) ? 1:0)
>= (1<<LGDEPTH)-1);
end
initial fill = 0;
always @(posedge S_AXI_ACLK)
if (fifo_reset)
fill <= 0;
// else if (fo_reset || flushing)
// fill <= 0;
else case({ M_AXI_ARVALID && M_AXI_ARREADY && !flush_request,
fifo_rd && !fifo_empty })
2'b10: fill <= fill + 1;
2'b01: fill <= fill - 1;
default: begin end
endcase
always @(*)
new_flushcount = outstanding + (M_AXI_ARVALID ? 1:0)
- (M_AXI_RVALID ? 1:0);
initial flushcount = 0;
initial flushing = 0;
initial flush_request = 0;
always @(posedge S_AXI_ACLK)
if (!S_AXI_ARESETN)
begin
flushcount <= 0;
flushing <= 0;
flush_request <= 0;
end else if (fifo_reset)
begin
flushcount <= new_flushcount;
flushing <= (new_flushcount > 0);
flush_request <= (M_AXI_ARVALID && !M_AXI_ARREADY);
end else begin
if (M_AXI_RVALID && flushcount > 0)
begin
flushcount <= flushcount - 1;
// Verilator lint_off CMPCONST
flushing <= (flushcount > 1);
// Verilator lint_on CMPCONST
end
if (M_AXI_ARREADY)
flush_request <= 0;
end
initial M_AXI_ARVALID = 1'b0;
always @(posedge S_AXI_ACLK)
if (!S_AXI_ARESETN)
M_AXI_ARVALID <= 1'b0;
else if (!M_AXI_ARVALID || M_AXI_ARREADY)
begin
M_AXI_ARVALID <= 1;
if (i_new_pc || pending_new_pc)
M_AXI_ARVALID <= 1'b1;
//
// Throttle the number of requests we make
if (fill + (M_AXI_ARVALID ? 1:0)
+ ((o_valid &&(!i_ready || out_fill > 1)) ? 1:0)
>= FETCH_LIMIT)
M_AXI_ARVALID <= 1'b0;
if (i_cpu_reset || i_clear_cache || full_bus)
M_AXI_ARVALID <= 1'b0;
end
always @(*)
M_AXI_RREADY = 1'b1;
initial pending_new_pc = 0;
always @(posedge S_AXI_ACLK)
if (!S_AXI_ARESETN || i_clear_cache)
pending_new_pc <= 1'b0;
else if (!M_AXI_ARVALID || M_AXI_ARREADY)
pending_new_pc <= 1'b0;
else if (i_new_pc)
pending_new_pc <= 1'b1;
initial pending_pc = 0;
always @(posedge S_AXI_ACLK)
if (i_new_pc)
pending_pc <= i_pc;
always @(posedge S_AXI_ACLK)
if (!M_AXI_ARVALID || M_AXI_ARREADY)
begin
if (i_new_pc)
M_AXI_ARADDR <= i_pc;
else if (pending_new_pc)
M_AXI_ARADDR <= pending_pc;
else if (M_AXI_ARVALID)
begin
M_AXI_ARADDR[C_AXI_ADDR_WIDTH-1:AXILLSB]
<= M_AXI_ARADDR[C_AXI_ADDR_WIDTH-1:AXILLSB] +1;
M_AXI_ARADDR[AXILLSB-1:0] <= 0;
end
end
initial o_pc = 0;
always @(posedge S_AXI_ACLK)
if (i_new_pc)
o_pc <= i_pc;
else if (o_valid && i_ready && !o_illegal)
begin
o_pc[AW-1:2] <= o_pc[AW-1:2] + 1;
o_pc[1:0] <= 2'b00;
end
generate if (AXILLSB > 2)
begin : BIG_WORD
always @(*)
begin
shift = o_pc[AXILLSB-1:2];
if (FETCH_LIMIT > 0 && o_valid)
shift = 0;
end
end else begin : NO_SHIFT
always @(*)
shift = 0;
end endgenerate
generate if (SWAP_ENDIANNESS)
begin : SWAPPED_ENDIANNESS
genvar gw, gb; // Word count, byte count
for(gw=0; gw<C_AXI_DATA_WIDTH/32; gw=gw+1) // For each bus word
for(gb=0; gb<4; gb=gb+1) // For each bus byte
always @(*)
endian_swapped_rdata[gw*32+(3-gb)*8 +: 8]
= M_AXI_RDATA[gw*32+gb*8 +: 8];
end else begin : NO_ENDIAN_SWAP
always @(*)
endian_swapped_rdata = M_AXI_RDATA;
end endgenerate
generate if (FETCH_LIMIT <= 1)
begin : NOCACHE
// No cache
// assign fifo_rd = fifo_wr;
assign fifo_rd = !o_valid || (i_ready && (out_fill <= 1));
assign fifo_empty = !fifo_wr; //(out_fill <= (i_aready ? 1:0));
assign fifo_data = { M_AXI_RRESP[1], endian_swapped_rdata };
assign ign_fifo_fill = 1'b0;
assign ign_fifo_full = 1'b0;
`ifdef FORMAL
always @(*)
if (M_AXI_RVALID || M_AXI_ARVALID || outstanding > 0)
assert(!o_valid);
`endif
end else if (FETCH_LIMIT == 2)
begin : DBLFETCH
// Single word cache
reg cache_valid;
reg [C_AXI_DATA_WIDTH:0] cache_data;
assign fifo_rd = !o_valid || (i_ready && (out_fill <= 1));
assign fifo_empty =(!M_AXI_RVALID && !cache_valid) || flushing;
assign fifo_data = cache_valid ? cache_data
: ({ M_AXI_RRESP[1], endian_swapped_rdata });
assign ign_fifo_fill = cache_valid ? 1 : 0;
assign ign_fifo_full = cache_valid;
initial cache_valid = 1'b0;
always @(posedge S_AXI_ACLK)
if (fifo_reset)
cache_valid <= 1'b0;
else if (fifo_wr && o_valid && !fifo_rd)
cache_valid <= 1;
else if (fifo_rd)
cache_valid <= 1'b0;
always @(posedge S_AXI_ACLK)
if (M_AXI_RVALID)
cache_data <= { M_AXI_RRESP[1], endian_swapped_rdata };
end else begin : FIFO_FETCH
// FIFO cache
assign fifo_rd = !o_valid || (i_ready && (out_fill <= 1));
sfifo #(.BW(1+C_AXI_DATA_WIDTH), .LGFLEN(LGFIFO))
fcache(.i_clk(S_AXI_ACLK), .i_reset(fifo_reset),
.i_wr(fifo_wr),
.i_data({M_AXI_RRESP[1], endian_swapped_rdata }),
.o_full(ign_fifo_full), .o_fill(ign_fifo_fill),
.i_rd(fifo_rd),.o_data(fifo_data),.o_empty(fifo_empty));
end endgenerate
initial o_valid = 1'b0;
always @(posedge S_AXI_ACLK)
if (fifo_reset)
o_valid <= 1'b0;
else if (!o_valid || i_ready)
o_valid <= (fifo_rd && !fifo_empty)
|| out_fill > (o_valid ? 1:0);
initial out_fill = 0;
always @(posedge S_AXI_ARESETN)
if (fifo_reset)
out_fill <= 0;
else if (fifo_rd)
// Verilator lint_off WIDTH
out_fill <= (fifo_empty) ? 0: (INSNS_PER_WORD - shift);
// Verilator lint_on WIDTH
else if (i_ready && out_fill > 0)
out_fill <= out_fill - 1;
always @(posedge S_AXI_ARESETN)
if (fifo_rd)
out_data <= fifo_data[C_AXI_DATA_WIDTH-1:0]>>(DATA_WIDTH*shift);
else if (i_ready)
out_data <= out_data >> DATA_WIDTH;
always @(*)
o_insn = out_data[DATA_WIDTH-1:0];
initial o_illegal = 1'b0;
always @(posedge S_AXI_ARESETN)
if (fifo_reset)
o_illegal <= 1'b0;
else if (!o_illegal && fifo_rd && !fifo_empty)
o_illegal <= fifo_data[C_AXI_DATA_WIDTH];
// Make verilator happy
// verilator lint_off UNUSED
wire unused;
assign unused = & { 1'b0, M_AXI_RRESP[0], ign_fifo_full, ign_fifo_fill };
// verilator lint_on UNUSED
`ifdef FORMAL
// Formal properties for this module are maintained elsewhere
`endif
endmodule