1032 lines
26 KiB
Verilog
1032 lines
26 KiB
Verilog
////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Filename: dblfetch.v
|
|
// {{{
|
|
// Project: 10Gb Ethernet switch
|
|
//
|
|
// Purpose: This is one step beyond the simplest instruction fetch,
|
|
// prefetch.v. dblfetch.v uses memory pipelining to fetch two
|
|
// (or more) instruction words in one bus cycle. If the CPU consumes
|
|
// either of these before the bus cycle completes, a new request will be
|
|
// made of the bus. In this way, we can keep the CPU filled in spite
|
|
// of a (potentially) slow memory operation. The bus request will end
|
|
// when both requests have been sent and both result locations are empty.
|
|
//
|
|
// This routine is designed to be a touch faster than the single
|
|
// instruction prefetch (prefetch.v), although not as fast as the
|
|
// prefetch and cache approach found elsewhere (pfcache.v).
|
|
//
|
|
// 20180222: Completely rebuilt.
|
|
//
|
|
// Creator: Dan Gisselquist, Ph.D.
|
|
// Gisselquist Technology, LLC
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// }}}
|
|
// Copyright (C) 2023, Gisselquist Technology, LLC
|
|
// {{{
|
|
// This file is part of the ETH10G project.
|
|
//
|
|
// The ETH10G 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 dblfetch #(
|
|
// {{{
|
|
parameter ADDRESS_WIDTH=30, // Byte addr
|
|
parameter INSN_WIDTH=32,
|
|
parameter DATA_WIDTH = INSN_WIDTH,
|
|
localparam AW=ADDRESS_WIDTH,
|
|
DW=DATA_WIDTH,
|
|
parameter [0:0] OPT_LITTLE_ENDIAN = 1'b1
|
|
// }}}
|
|
) (
|
|
// {{{
|
|
input wire i_clk, i_reset,
|
|
// CPU signals--from the CPU
|
|
input wire i_new_pc, i_clear_cache,i_ready,
|
|
input wire [AW-1:0] i_pc,
|
|
// ... and in return
|
|
output reg o_valid,
|
|
output reg o_illegal,
|
|
output reg [INSN_WIDTH-1:0] o_insn,
|
|
output reg [AW-1:0] o_pc,
|
|
// Wishbone outputs
|
|
output reg o_wb_cyc, o_wb_stb,
|
|
// verilator coverage_off
|
|
output wire o_wb_we,
|
|
// verilator coverage_on
|
|
output reg [AW-$clog2(DW/8)-1:0] o_wb_addr,
|
|
// verilator coverage_off
|
|
output wire [DW-1:0] o_wb_data,
|
|
// verilator coverage_on
|
|
// And return inputs
|
|
input wire i_wb_stall, i_wb_ack, i_wb_err,
|
|
input wire [DW-1:0] i_wb_data
|
|
// }}}
|
|
);
|
|
|
|
// Local declarations
|
|
// {{{
|
|
wire last_stb;
|
|
reg invalid_bus_cycle;
|
|
|
|
reg [(DW-1):0] cache_word;
|
|
reg cache_valid;
|
|
reg [1:0] inflight;
|
|
reg cache_illegal;
|
|
|
|
wire r_valid;
|
|
wire [DATA_WIDTH-1:0] r_insn, i_wb_shifted;
|
|
// }}}
|
|
|
|
assign o_wb_we = 1'b0;
|
|
assign o_wb_data = {(DATA_WIDTH){1'b0}};
|
|
|
|
// o_wb_cyc, o_wb_stb
|
|
// {{{
|
|
initial o_wb_cyc = 1'b0;
|
|
initial o_wb_stb = 1'b0;
|
|
always @(posedge i_clk)
|
|
if (i_reset || i_clear_cache || (o_wb_cyc && i_wb_err))
|
|
begin : RESET_ABORT
|
|
// {{{
|
|
o_wb_cyc <= 1'b0;
|
|
o_wb_stb <= 1'b0;
|
|
// }}}
|
|
end else if (o_wb_cyc)
|
|
begin : END_CYCLE
|
|
// {{{
|
|
if (!o_wb_stb || !i_wb_stall)
|
|
o_wb_stb <= (!last_stb);
|
|
|
|
// Relase the bus on the second ack
|
|
if ((!o_wb_stb || !i_wb_stall) && last_stb
|
|
&& inflight + (o_wb_stb ? 1:0) == (i_wb_ack ? 1:0))
|
|
begin
|
|
o_wb_cyc <= 1'b0;
|
|
o_wb_stb <= 1'b0;
|
|
end
|
|
|
|
if (i_new_pc) // || i_clear_cache)
|
|
{ o_wb_cyc, o_wb_stb } <= 2'b0;
|
|
// }}}
|
|
end else if ((i_new_pc || invalid_bus_cycle)
|
|
||(o_valid && i_ready && !r_valid && !cache_illegal))
|
|
begin : START_CYCLE
|
|
// {{{
|
|
// Initiate a bus cycle if ... the last bus cycle was
|
|
// aborted (bus error or new_pc), we've been given a
|
|
// new PC to go get, or we just exhausted our one
|
|
// instruction cache
|
|
o_wb_cyc <= 1'b1;
|
|
o_wb_stb <= 1'b1;
|
|
// }}}
|
|
end
|
|
// }}}
|
|
|
|
// inflight
|
|
// {{{
|
|
initial inflight = 2'b00;
|
|
always @(posedge i_clk)
|
|
if (!o_wb_cyc)
|
|
inflight <= 2'b00;
|
|
else begin
|
|
case({ (o_wb_stb && !i_wb_stall), i_wb_ack })
|
|
2'b01: inflight <= inflight - 1'b1;
|
|
2'b10: inflight <= inflight + 1'b1;
|
|
// If neither ack nor request, then no change. Likewise
|
|
// if we have both an ack and a request, there's no change
|
|
// in the number of requests in flight.
|
|
default: begin end
|
|
endcase
|
|
end
|
|
// }}}
|
|
|
|
// last_stb
|
|
// {{{
|
|
// assign last_stb = (inflight != 2'b00)||(o_valid&& (!i_ready||r_valid));
|
|
assign last_stb = (!o_wb_stb||!i_wb_stall)&&(inflight
|
|
+ (o_wb_stb ? 1:0)
|
|
+ (o_valid&&(!i_ready || r_valid)) >= 2'b10);
|
|
// }}}
|
|
|
|
// invalid_bus_cycle
|
|
// {{{
|
|
initial invalid_bus_cycle = 1'b0;
|
|
always @(posedge i_clk)
|
|
if (i_reset)
|
|
invalid_bus_cycle <= 1'b0;
|
|
else if (o_wb_cyc && i_new_pc)
|
|
invalid_bus_cycle <= 1'b1;
|
|
else if (!o_wb_cyc)
|
|
invalid_bus_cycle <= 1'b0;
|
|
// }}}
|
|
|
|
// o_wb_addr
|
|
// {{{
|
|
initial o_wb_addr = {(AW-$clog2(DATA_WIDTH/8)){1'b1}};
|
|
always @(posedge i_clk)
|
|
if (i_new_pc)
|
|
o_wb_addr <= i_pc[AW-1:$clog2(DATA_WIDTH/8)];
|
|
// else if (i_clear_cache)
|
|
// o_wb_addr <= o_pc[AW-1:$clog2(DATA_WIDTH/8)];
|
|
else if (o_wb_stb && !i_wb_stall)
|
|
o_wb_addr <= o_wb_addr + 1'b1;
|
|
// }}}
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Now for the immediate output word to the CPU
|
|
// {{{
|
|
////////////////////////////////////////////////////////////////////////
|
|
//
|
|
//
|
|
|
|
// This only applies when the bus size doesn't match the instruction
|
|
// word size. Here, we only support bus sizes greater than the
|
|
// instruction word size.
|
|
`ifdef FORMAL
|
|
wire [DATA_WIDTH-1:0] f_bus_word;
|
|
`endif
|
|
|
|
generate if (DATA_WIDTH > INSN_WIDTH)
|
|
begin : GEN_SUBSHIFT
|
|
// {{{
|
|
localparam NSHIFT = $clog2(DATA_WIDTH/INSN_WIDTH);
|
|
|
|
reg rg_valid;
|
|
reg [DATA_WIDTH-1:0] rg_insn;
|
|
reg [NSHIFT:0] r_count;
|
|
reg [NSHIFT-1:0] r_shift;
|
|
|
|
// rg_valid
|
|
// {{{
|
|
always @(posedge i_clk)
|
|
if (i_reset || i_new_pc) // || i_clear_cache)
|
|
rg_valid <= 1'b0;
|
|
else if (r_valid)
|
|
rg_valid <= !i_ready || (r_count > 1);
|
|
else if (!o_valid || i_ready)
|
|
begin
|
|
rg_valid <= 1'b0;
|
|
if (cache_valid)
|
|
rg_valid <= 1'b1;
|
|
if (o_wb_cyc && i_wb_ack && !(&r_shift))
|
|
rg_valid <= 1'b1;
|
|
end
|
|
// }}}
|
|
|
|
// rg_insn
|
|
// {{{
|
|
always @(posedge i_clk)
|
|
if (!o_valid || i_ready)
|
|
begin
|
|
if (cache_valid && !r_valid)
|
|
begin
|
|
if (OPT_LITTLE_ENDIAN)
|
|
rg_insn <= cache_word >> INSN_WIDTH;
|
|
else
|
|
rg_insn <= cache_word << INSN_WIDTH;
|
|
end else if (i_wb_ack && !r_valid)
|
|
begin
|
|
rg_insn <= i_wb_data;
|
|
if (OPT_LITTLE_ENDIAN)
|
|
rg_insn <= i_wb_shifted >> INSN_WIDTH;
|
|
else
|
|
rg_insn <= i_wb_shifted << INSN_WIDTH;
|
|
end else begin
|
|
if (OPT_LITTLE_ENDIAN)
|
|
rg_insn <= rg_insn >> INSN_WIDTH;
|
|
else
|
|
rg_insn <= rg_insn << INSN_WIDTH;
|
|
end
|
|
end
|
|
// }}}
|
|
|
|
// r_count
|
|
// {{{
|
|
always @(posedge i_clk)
|
|
if (i_reset || i_new_pc) // || i_clear_cache)
|
|
r_count <= 0;
|
|
else if (o_valid && i_ready && r_valid)
|
|
begin
|
|
r_count <= r_count - 1;
|
|
end else if (!o_valid || (i_ready && !r_valid))
|
|
begin
|
|
if (cache_valid)
|
|
r_count <= { 1'b0, {(NSHIFT){1'b1}} };
|
|
else if (o_wb_cyc && i_wb_ack)
|
|
r_count <= { 1'b0, ~r_shift };
|
|
end
|
|
`ifdef FORMAL
|
|
always @(*)
|
|
if (!i_reset && !i_new_pc && !i_clear_cache)
|
|
begin
|
|
if (!o_valid)
|
|
assert(!r_valid);
|
|
assert(r_valid == (r_count > 0));
|
|
assert(r_count <= (1<<NSHIFT));
|
|
if (r_valid)
|
|
begin
|
|
assert(!cache_valid || !o_wb_cyc);
|
|
assert(r_shift == 0);
|
|
end else if (!i_new_pc && !i_clear_cache && !o_illegal)
|
|
assert(invalid_bus_cycle || o_valid || o_wb_cyc);
|
|
end
|
|
`endif
|
|
// }}}
|
|
|
|
// r_shift
|
|
// {{{
|
|
always @(posedge i_clk)
|
|
if (i_new_pc)
|
|
r_shift <= i_pc[$clog2(DW/8)-1:$clog2(INSN_WIDTH/8)];
|
|
// else if (i_clear_cache)
|
|
// r_shift <= o_pc[$clog2(DW/8)-1:$clog2(INSN_WIDTH/8)];
|
|
else if (o_wb_cyc && (i_wb_ack || i_wb_err))
|
|
r_shift <= 0;
|
|
|
|
`ifdef FORMAL
|
|
always @(*)
|
|
if (!i_reset && r_shift > 0)
|
|
assert(!o_valid && !r_valid);
|
|
`endif
|
|
// }}}
|
|
|
|
assign r_valid = rg_valid;
|
|
assign r_insn = rg_insn;
|
|
if (OPT_LITTLE_ENDIAN)
|
|
begin : GEN_LITTLE_ENDIAN_SHIFT
|
|
assign i_wb_shifted = i_wb_data >> (r_shift * INSN_WIDTH);
|
|
end else begin : GEN_BIGENDIAN_SHIFT
|
|
assign i_wb_shifted = i_wb_data << (r_shift * INSN_WIDTH);
|
|
end
|
|
|
|
// Keep Verilator happy
|
|
// {{{
|
|
// Verilator lint_off UNUSED
|
|
wire unused_shift;
|
|
assign unused_shift = &{ 1'b0,
|
|
r_insn[DATA_WIDTH-1:INSN_WIDTH],
|
|
i_wb_shifted[DATA_WIDTH-1:INSN_WIDTH] };
|
|
// Verilator lint_on UNUSED
|
|
// }}}
|
|
`ifdef FORMAL
|
|
assign f_bus_word = rg_insn << ((r_count-1)* INSN_WIDTH);
|
|
always @(*)
|
|
if (!i_reset && r_valid)
|
|
begin
|
|
assert(i_clear_cache || i_new_pc || o_valid);
|
|
assert(r_shift == 0);
|
|
assert((r_count + o_pc[$clog2(DW/8)-1:$clog2(INSN_WIDTH/8)])
|
|
== ((1<<NSHIFT)-1));
|
|
end else if (!i_reset && !o_valid && o_wb_cyc && !invalid_bus_cycle)
|
|
begin
|
|
assert(r_shift == o_pc[$clog2(DW/8)-1:$clog2(INSN_WIDTH/8)]);
|
|
end
|
|
`endif
|
|
// }}}
|
|
end else begin : NO_SUBSHIFT
|
|
// {{{
|
|
assign r_valid = 1'b0;
|
|
assign r_insn = {(INSN_WIDTH){1'b0}};
|
|
assign i_wb_shifted = i_wb_data;
|
|
// }}}
|
|
end endgenerate
|
|
|
|
// o_valid
|
|
// {{{
|
|
initial o_valid = 1'b0;
|
|
always @(posedge i_clk)
|
|
if (i_reset || i_new_pc || i_clear_cache)
|
|
o_valid <= 1'b0;
|
|
else if (o_wb_cyc &&(i_wb_ack || i_wb_err))
|
|
o_valid <= 1'b1;
|
|
else if (i_ready)
|
|
o_valid <= cache_valid || r_valid;
|
|
// }}}
|
|
|
|
// o_insn
|
|
// {{{
|
|
always @(posedge i_clk)
|
|
if (!o_valid || i_ready)
|
|
begin
|
|
if (OPT_LITTLE_ENDIAN)
|
|
begin
|
|
if (r_valid)
|
|
o_insn <= r_insn[INSN_WIDTH-1:0];
|
|
else if (cache_valid)
|
|
o_insn <= cache_word[INSN_WIDTH-1:0];
|
|
else
|
|
o_insn <= i_wb_shifted[INSN_WIDTH-1:0];
|
|
end else begin
|
|
if (r_valid)
|
|
o_insn <= r_insn[DW-1:DW-INSN_WIDTH];
|
|
else if (cache_valid)
|
|
o_insn <= cache_word[DW-1:DW-INSN_WIDTH];
|
|
else
|
|
o_insn <= i_wb_shifted[DW-1:DW-INSN_WIDTH];
|
|
end
|
|
end
|
|
// }}}
|
|
|
|
// o_pc
|
|
// {{{
|
|
always @(posedge i_clk)
|
|
if (i_new_pc)
|
|
o_pc <= i_pc;
|
|
else if (o_valid && i_ready) // && !i_clear_cache
|
|
begin
|
|
o_pc <= 0;
|
|
o_pc[AW-1:$clog2(INSN_WIDTH/8)]
|
|
<= o_pc[AW-1:$clog2(INSN_WIDTH/8)] + 1'b1;
|
|
end
|
|
// }}}
|
|
|
|
// o_illegal
|
|
// {{{
|
|
initial o_illegal = 1'b0;
|
|
always @(posedge i_clk)
|
|
if (i_reset || i_new_pc || i_clear_cache)
|
|
o_illegal <= 1'b0;
|
|
else if (!r_valid && (!o_valid || i_ready) && !o_illegal)
|
|
begin
|
|
if (cache_valid)
|
|
o_illegal <= cache_illegal;
|
|
else if (o_wb_cyc && i_wb_err)
|
|
o_illegal <= 1'b1;
|
|
end
|
|
// }}}
|
|
|
|
// }}}
|
|
////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Now for the output/cached word
|
|
// {{{
|
|
////////////////////////////////////////////////////////////////////////
|
|
//
|
|
//
|
|
|
|
// cache_valid
|
|
// {{{
|
|
initial cache_valid = 1'b0;
|
|
always @(posedge i_clk)
|
|
if ((i_reset)||(i_new_pc)||(i_clear_cache))
|
|
cache_valid <= 1'b0;
|
|
else begin
|
|
if (o_valid && o_wb_cyc &&(i_wb_ack || i_wb_err))
|
|
cache_valid <= !i_ready || r_valid;
|
|
else if (i_ready && !r_valid)
|
|
cache_valid <= 1'b0;
|
|
end
|
|
`ifdef FORMAL
|
|
always @(*)
|
|
if (!i_reset && cache_valid && o_wb_cyc)
|
|
assert(!i_wb_ack && !i_wb_err);
|
|
`endif
|
|
// }}}
|
|
|
|
// cache_word
|
|
// {{{
|
|
always @(posedge i_clk)
|
|
if (i_wb_ack)
|
|
cache_word <= i_wb_data;
|
|
// }}}
|
|
|
|
// cache_illegal
|
|
// {{{
|
|
initial cache_illegal = 1'b0;
|
|
always @(posedge i_clk)
|
|
if (i_reset || i_clear_cache || i_new_pc)
|
|
cache_illegal <= 1'b0;
|
|
// Older logic ...
|
|
// else if ((o_wb_cyc)&&(i_wb_err)&&(o_valid)&&(!i_ready))
|
|
// cache_illegal <= 1'b1;
|
|
else if (o_wb_cyc && i_wb_err)
|
|
// && o_valid && (!i_ready || r_valid))
|
|
cache_illegal <= 1'b1;
|
|
// }}}
|
|
|
|
// }}}
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Formal property section
|
|
// {{{
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
`ifdef FORMAL
|
|
// Local declarations
|
|
// {{{
|
|
// Keep track of a flag telling us whether or not $past()
|
|
// will return valid results
|
|
reg f_past_valid;
|
|
|
|
// Keep track of some alternatives to $past that can still be used
|
|
// in a VERILATOR environment
|
|
reg [AW-1:0] f_next_addr, f_dbl_next, f_cache_addr; //bytaddr
|
|
localparam F_LGDEPTH=2;
|
|
wire [(F_LGDEPTH-1):0] f_nreqs, f_nacks, f_outstanding;
|
|
wire [AW-1:0] f_const_addr;
|
|
wire [DW-1:0] f_const_insn;
|
|
wire f_const_illegal;
|
|
wire f_this_pc,
|
|
f_this_insn, f_this_return;
|
|
wire [AW-$clog2(DATA_WIDTH/8)-1:0] this_return_address,
|
|
f_const_bus_addr, next_pc_address;
|
|
wire [AW-1:0] f_address;
|
|
(* anyconst *) wire [DW-1:0] f_const_bus_word;
|
|
reg [AW-$clog2(DW/8)-1:0] pc_bus_addr, pc_bus_addr_next,
|
|
pc_bus_addr_dbl;
|
|
|
|
initial f_past_valid = 1'b0;
|
|
always @(posedge i_clk)
|
|
f_past_valid <= 1'b1;
|
|
// }}}
|
|
|
|
generate if (INSN_WIDTH == 8)
|
|
begin : F_NEXT_BYTE_ADDR
|
|
// {{{
|
|
// BUS addresses
|
|
always @(*)
|
|
f_next_addr = o_pc + (r_valid ? 0:1);
|
|
// }}}
|
|
end else begin : F_NEXT_ADDR
|
|
// {{{
|
|
// BUS addresses
|
|
always @(*)
|
|
if (r_valid)
|
|
f_next_addr = o_pc;
|
|
else begin
|
|
f_next_addr = o_pc + (INSN_WIDTH/8);
|
|
f_next_addr[$clog2(INSN_WIDTH/8)-1:0] = 0;
|
|
end
|
|
// }}}
|
|
end endgenerate
|
|
|
|
always @(*)
|
|
begin
|
|
// BUS addresses
|
|
f_dbl_next = f_next_addr + (DATA_WIDTH/8);
|
|
if (r_valid)
|
|
f_dbl_next = f_dbl_next + (DATA_WIDTH/8);
|
|
f_dbl_next[$clog2(DATA_WIDTH/8)-1:0] = 0;
|
|
|
|
f_cache_addr = o_pc + (DATA_WIDTH/8);
|
|
f_cache_addr[$clog2(DATA_WIDTH/8)-1:0] = 0;
|
|
end
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Assumptions about our inputs
|
|
// {{{
|
|
////////////////////////////////////////////////////////////////////////
|
|
//
|
|
//
|
|
//
|
|
|
|
//
|
|
// Assume that resets, new-pc commands, and clear-cache commands
|
|
// are never more than pulses--one clock wide at most.
|
|
//
|
|
// It may be that the CPU treats us differently. We'll only restrict
|
|
// our solver to this here.
|
|
// }}}
|
|
////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Wishbone bus properties
|
|
// {{{
|
|
////////////////////////////////////////////////////////////////////////
|
|
//
|
|
//
|
|
|
|
//
|
|
// Add a bunch of wishbone-based asserts
|
|
fwb_master #(
|
|
// {{{
|
|
.AW(AW-$clog2(DATA_WIDTH/8)), .DW(DW), .F_LGDEPTH(F_LGDEPTH),
|
|
.F_MAX_STALL(2),
|
|
.F_MAX_REQUESTS(0), .F_OPT_SOURCE(1),
|
|
.F_OPT_RMW_BUS_OPTION(1),
|
|
.F_OPT_DISCONTINUOUS(0)
|
|
// }}}
|
|
) f_wbm(
|
|
// {{{
|
|
i_clk, i_reset,
|
|
o_wb_cyc, o_wb_stb, o_wb_we, o_wb_addr, o_wb_data,
|
|
{(DW/8){1'b1}},
|
|
i_wb_ack, i_wb_stall, i_wb_data, i_wb_err,
|
|
f_nreqs, f_nacks, f_outstanding
|
|
// }}}
|
|
);
|
|
// }}}
|
|
////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Assumptions about our interaction with the CPU
|
|
// {{{
|
|
////////////////////////////////////////////////////////////////////////
|
|
//
|
|
//
|
|
|
|
ffetch #(
|
|
// {{{
|
|
.ADDRESS_WIDTH(AW-$clog2(INSN_WIDTH/8)),
|
|
.INSN_WIDTH(INSN_WIDTH)
|
|
// }}}
|
|
) cpu(
|
|
// {{{
|
|
.i_clk(i_clk), .i_reset(i_reset),
|
|
.cpu_new_pc(i_new_pc), .cpu_clear_cache(i_clear_cache),
|
|
.cpu_pc(i_pc), .pf_valid(o_valid), .cpu_ready(i_ready),
|
|
.pf_pc(o_pc), .pf_insn(o_insn), .pf_illegal(o_illegal),
|
|
.fc_illegal(f_const_illegal), .fc_insn(f_const_insn),
|
|
.fc_pc(f_const_addr), .f_address(f_address)
|
|
// }}}
|
|
);
|
|
|
|
assign f_const_bus_addr = f_const_addr[AW-1:$clog2(DATA_WIDTH/8)];
|
|
|
|
//
|
|
// Let's make some assumptions about how long it takes our phantom
|
|
// (i.e. assumed) CPU to respond.
|
|
//
|
|
// This delay needs to be long enough to flush out any potential
|
|
// errors, yet still short enough that the formal method doesn't
|
|
// take forever to solve.
|
|
//
|
|
localparam F_CPU_DELAY = 4;
|
|
reg [4:0] f_cpu_delay;
|
|
|
|
// Now, let's look at the delay the CPU takes to accept an instruction.
|
|
always @(posedge i_clk)
|
|
// If no instruction is ready, then keep our counter at zero
|
|
if ((!o_valid)||(i_ready))
|
|
f_cpu_delay <= 0;
|
|
else
|
|
// Otherwise, count the clocks the CPU takes to respond
|
|
f_cpu_delay <= f_cpu_delay + 1'b1;
|
|
|
|
always @(posedge i_clk)
|
|
assume(f_cpu_delay < F_CPU_DELAY);
|
|
// }}}
|
|
////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Assertions about our outputs
|
|
// {{{
|
|
//
|
|
////////////////////////////////////////////////////////////////////////
|
|
//
|
|
//
|
|
always @(posedge i_clk)
|
|
if ((f_past_valid)&&($past(o_wb_stb))&&(!$past(i_wb_stall))
|
|
&&($past(!i_new_pc && !i_clear_cache)))
|
|
assert(o_wb_addr <= $past(o_wb_addr)+1'b1);
|
|
|
|
//
|
|
// The cache doesn't change if we are stalled
|
|
//
|
|
always @(posedge i_clk)
|
|
if ((f_past_valid)&&(!$past(i_reset))
|
|
&&(!$past(i_new_pc))&&(!$past(i_clear_cache))
|
|
&&($past(o_valid))&&(!$past(i_ready || r_valid))
|
|
&&($past(cache_valid)))
|
|
begin
|
|
assert($stable(cache_valid));
|
|
assert($stable(cache_word));
|
|
assert($stable(cache_illegal));
|
|
end
|
|
|
|
// Consider it invalid to present the CPU with the same instruction
|
|
// twice in a row. Any effort to present the CPU with the same
|
|
// instruction twice in a row must go through i_new_pc, and thus a
|
|
// new bus cycle--hence the assertion below makes sense.
|
|
always @(posedge i_clk)
|
|
if ((f_past_valid)&& $past(!i_new_pc && !i_clear_cache)
|
|
&&($past(o_valid && i_ready)))
|
|
assert(o_pc == f_address);
|
|
|
|
always @(posedge i_clk)
|
|
if (!i_reset && !i_clear_cache && !i_new_pc)
|
|
assert(o_pc == f_address);
|
|
|
|
|
|
always @(posedge i_clk)
|
|
if ((f_past_valid)&&(!$past(i_reset))
|
|
&&(!$past(i_new_pc))
|
|
&&(!$past(i_clear_cache))
|
|
&&($past(o_wb_cyc))&&($past(i_wb_err)))
|
|
assert( ((o_valid)&&(o_illegal))
|
|
||((cache_valid)&&(cache_illegal)) );
|
|
|
|
always @(posedge i_clk)
|
|
if ((f_past_valid)&&(!$past(o_illegal))&&(o_illegal))
|
|
assert(o_valid);
|
|
|
|
always @(posedge i_clk)
|
|
if (!i_reset && cache_illegal)
|
|
assert(!o_wb_cyc);
|
|
|
|
always @(posedge i_clk)
|
|
if ((f_past_valid)&&($past(i_new_pc)))
|
|
assert(!o_valid);
|
|
|
|
always @(posedge i_clk)
|
|
if ((f_past_valid)&&(!$past(i_reset))&&(!$past(i_clear_cache))
|
|
&&($past(o_valid))&&(!o_valid)&&(!o_illegal))
|
|
assert((o_wb_cyc)||(invalid_bus_cycle));
|
|
// }}}
|
|
////////////////////////////////////////////////////////////////////////
|
|
//
|
|
//
|
|
// Our "contract" with the CPU
|
|
// {{{
|
|
//
|
|
////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// For any particular address, that address is associated with an
|
|
// instruction and a flag regarding whether or not it is illegal.
|
|
//
|
|
// Any attempt to return to the CPU a value from this address,
|
|
// must return the value and the illegal flag.
|
|
//
|
|
|
|
//
|
|
// While these wires may seem like overkill, and while they make the
|
|
// following logic perhaps a bit more obscure, these predicates make
|
|
// it easier to follow the complex logic on a scope. They don't
|
|
// affect anything synthesized.
|
|
//
|
|
assign f_this_pc = (o_pc[AW-1:$clog2(INSN_WIDTH/8)]
|
|
== f_const_addr[AW-1:$clog2(INSN_WIDTH/8)]);
|
|
assign f_this_insn = (o_insn == f_const_insn);
|
|
|
|
// Verilator lint_off WIDTH
|
|
assign f_this_return = (o_wb_addr - f_outstanding == f_const_addr[AW-1:$clog2(DATA_WIDTH/8)]);
|
|
// Verilator lint_on WIDTH
|
|
|
|
|
|
//
|
|
//
|
|
// Here's our contract:
|
|
//
|
|
// Any time we return a value for the address above, it *must* be
|
|
// the "right" value.
|
|
//
|
|
always @(*)
|
|
if (o_valid && f_this_pc)
|
|
begin
|
|
if (f_const_illegal)
|
|
assert(o_illegal);
|
|
if (!o_illegal)
|
|
assert(f_this_insn);
|
|
end
|
|
|
|
//
|
|
// The contract will only work if we assume the return from the
|
|
// bus at this address will be the right return.
|
|
always @(*)
|
|
if ((o_wb_cyc)&&(f_this_return))
|
|
begin
|
|
if (i_wb_ack)
|
|
assume(i_wb_data == f_const_bus_word);
|
|
|
|
if (f_const_illegal)
|
|
begin
|
|
assume(!i_wb_ack);
|
|
end else
|
|
assume(!i_wb_err);
|
|
end
|
|
|
|
generate if (INSN_WIDTH == DATA_WIDTH)
|
|
begin : F_SAME_BUS_WORD
|
|
// {{{
|
|
always @(*)
|
|
assume(f_const_bus_word == f_const_insn);
|
|
// }}}
|
|
end else begin : F_CHECK_SHIFTED_WORD
|
|
// {{{
|
|
wire [DW-1:0] f_shifted_insn;
|
|
localparam IW = INSN_WIDTH;
|
|
|
|
if (OPT_LITTLE_ENDIAN)
|
|
begin
|
|
assign f_shifted_insn = f_const_bus_word
|
|
>> (f_const_addr[$clog2(DW/8)-1:$clog2(IW/8)] * IW);
|
|
always @(*)
|
|
assume(f_shifted_insn[IW-1:0] == f_const_insn);
|
|
|
|
end else begin
|
|
assign f_shifted_insn = f_const_bus_word
|
|
<< (f_const_addr[$clog2(DW/8)-1:$clog2(IW/8)] * IW);
|
|
|
|
always @(*)
|
|
assume(f_shifted_insn[DW-1:DW-IW]
|
|
== f_const_insn);
|
|
|
|
end
|
|
// }}}
|
|
end endgenerate
|
|
|
|
//
|
|
// Here is a corrollary to our contract. Anything in the one-word
|
|
// cache must also match the contract as well.
|
|
//
|
|
always @(*)
|
|
if ((f_cache_addr[AW-1:$clog2(DATA_WIDTH/8)] == f_const_bus_addr)
|
|
&&(cache_valid))
|
|
begin
|
|
if (!cache_illegal)
|
|
assert(cache_word == f_const_bus_word);
|
|
|
|
if (f_const_illegal)
|
|
assert(cache_illegal);
|
|
else
|
|
assert(o_illegal || !cache_illegal);
|
|
end
|
|
|
|
// always @(posedge i_clk)
|
|
// if ((f_past_valid)&&(!$past(cache_illegal))&&(!cache_valid))
|
|
// assert(!cache_illegal);
|
|
// }}}
|
|
////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Additional assertions necessary to pass induction
|
|
// {{{
|
|
////////////////////////////////////////////////////////////////////////
|
|
//
|
|
//
|
|
// We have only a one word cache. Hence, we shouldn't be asking
|
|
// for more data any time we have nowhere to put it.
|
|
always @(*)
|
|
if (o_wb_stb)
|
|
assert(!cache_valid || i_ready);
|
|
|
|
always @(*)
|
|
if (o_valid && cache_valid)
|
|
assert((f_outstanding == 0)&&(!o_wb_stb));
|
|
|
|
always @(*)
|
|
if (o_valid && (r_valid || !i_ready))
|
|
assert(f_outstanding < 2);
|
|
|
|
always @(*)
|
|
if (!o_valid || (i_ready && !r_valid))
|
|
assert(f_outstanding <= 2);
|
|
|
|
// always @(posedge i_clk)
|
|
// if ((f_past_valid)&&($past(o_wb_cyc && !o_wb_stb)) &&(o_wb_cyc))
|
|
// assert(inflight != 0);
|
|
|
|
always @(*)
|
|
if (o_wb_cyc && i_wb_ack)
|
|
assert(!cache_valid);
|
|
|
|
always @(posedge i_clk)
|
|
if (o_wb_cyc)
|
|
assert(inflight == f_outstanding);
|
|
|
|
// Verilator lint_off WIDTH
|
|
assign this_return_address = o_wb_addr - f_outstanding;
|
|
// Verilator lint_on WIDTH
|
|
assign next_pc_address = f_next_addr[AW-1:$clog2(DATA_WIDTH/8)];
|
|
// }}}
|
|
////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Address checking
|
|
// {{{
|
|
////////////////////////////////////////////////////////////////////////
|
|
//
|
|
//
|
|
|
|
always @(*)
|
|
begin
|
|
pc_bus_addr = o_pc[AW-1:$clog2(DW/8)];
|
|
pc_bus_addr_next = pc_bus_addr + 1;
|
|
pc_bus_addr_dbl = pc_bus_addr + 2;
|
|
end
|
|
|
|
always @(*)
|
|
if (i_reset || o_illegal || invalid_bus_cycle)
|
|
begin
|
|
end else if (!o_valid)
|
|
begin
|
|
if (o_wb_cyc)
|
|
begin
|
|
if (inflight == 0)
|
|
assert(o_wb_addr == pc_bus_addr);
|
|
assert(pc_bus_addr == this_return_address);
|
|
end
|
|
end else if (cache_valid || (o_wb_cyc && !o_wb_stb))
|
|
begin
|
|
assert(o_wb_addr == pc_bus_addr_dbl);
|
|
end else if (o_valid)
|
|
assert(o_wb_addr == pc_bus_addr_next);
|
|
|
|
always @(posedge i_clk)
|
|
if (f_past_valid &&($past(!i_reset && !i_new_pc && !i_clear_cache))
|
|
&&(!$past(invalid_bus_cycle)))
|
|
begin
|
|
if (($past(o_wb_cyc && (i_wb_ack || i_wb_err)))
|
|
&&($past(!o_valid || (i_ready && !r_valid)))
|
|
&&(!$past(cache_valid)))
|
|
begin
|
|
assert(o_pc[AW-1:$clog2(DW/8)] == $past(this_return_address));
|
|
end else if (cache_valid)
|
|
begin
|
|
assert(o_wb_addr == pc_bus_addr_dbl);
|
|
assert(f_outstanding == 0);
|
|
end else if (o_valid)
|
|
begin
|
|
if (f_outstanding == 0)
|
|
assert(o_illegal || o_wb_addr == pc_bus_addr_next);
|
|
else if (f_outstanding == 1)
|
|
assert(o_illegal || o_wb_addr == pc_bus_addr_dbl);
|
|
else if (f_outstanding == 2)
|
|
assert(o_wb_addr == pc_bus_addr_dbl);
|
|
end else if (o_wb_cyc)
|
|
assert(o_pc[AW-1:$clog2(DW/8)] == this_return_address);
|
|
end
|
|
|
|
always @(posedge i_clk)
|
|
if (!i_reset && o_illegal)
|
|
begin
|
|
assert(!o_wb_cyc);
|
|
assert(cache_illegal);
|
|
end
|
|
|
|
always @(posedge i_clk)
|
|
if ((f_past_valid)&&($past(o_wb_cyc && !i_new_pc))&&(!o_valid)
|
|
&&(o_wb_cyc))
|
|
assert(o_pc[AW-1:$clog2(DW/8)] == this_return_address);
|
|
|
|
always @(posedge i_clk)
|
|
if (o_valid && !o_wb_cyc && !o_illegal)
|
|
begin
|
|
if (cache_valid)
|
|
assert(f_dbl_next[AW-1:$clog2(DW/8)] == o_wb_addr);
|
|
else
|
|
assert(pc_bus_addr_next == o_wb_addr);
|
|
|
|
end
|
|
|
|
always @(*)
|
|
if (o_wb_cyc || o_valid)
|
|
begin
|
|
assert(o_wb_addr == pc_bus_addr
|
|
|| o_wb_addr == pc_bus_addr_next
|
|
|| o_wb_addr == pc_bus_addr_dbl);
|
|
end
|
|
|
|
// always @(posedge i_clk)
|
|
// if ((f_past_valid)&& $past(o_wb_cyc && !cache_valid) && cache_valid)
|
|
// assert(next_pc_address == $past(this_return_address));
|
|
|
|
//
|
|
//
|
|
|
|
always @(posedge i_clk)
|
|
if ((f_past_valid)&&($past(o_wb_cyc))&&(o_wb_cyc))
|
|
begin
|
|
if (o_valid && !cache_valid)
|
|
begin
|
|
// assert(this_return_address == next_pc_address);
|
|
end else if (!o_valid)
|
|
assert(this_return_address == o_pc[AW-1:$clog2(DW/8)]);
|
|
end else if ((f_past_valid)&&(!invalid_bus_cycle)
|
|
&&(!o_wb_cyc)&&(o_valid)&&(!o_illegal)
|
|
&&(!cache_valid))
|
|
assert(o_wb_addr == next_pc_address);
|
|
|
|
|
|
always @(*)
|
|
if (invalid_bus_cycle)
|
|
assert(!o_wb_cyc);
|
|
|
|
always @(*)
|
|
if (cache_valid)
|
|
assert(o_valid);
|
|
// }}}
|
|
////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Cover statements
|
|
// {{{
|
|
////////////////////////////////////////////////////////////////////////
|
|
//
|
|
//
|
|
reg f_cvr_aborted, f_cvr_fourth_ack;
|
|
|
|
always @(posedge i_clk)
|
|
cover((f_past_valid)&&($past(f_nacks)==3)
|
|
&&($past(i_wb_ack))&&($past(o_wb_cyc)));
|
|
|
|
initial f_cvr_aborted = 0;
|
|
always @(posedge i_clk)
|
|
if (i_reset)
|
|
f_cvr_aborted <= 0;
|
|
else if (!o_wb_cyc && (f_nreqs != f_nacks))
|
|
f_cvr_aborted <= 1;
|
|
|
|
initial f_cvr_fourth_ack = 0;
|
|
always @(posedge i_clk)
|
|
if (i_reset)
|
|
f_cvr_fourth_ack <= 0;
|
|
else if ((f_nacks == 3)&&(o_wb_cyc && i_wb_ack))
|
|
f_cvr_fourth_ack <= 1;
|
|
|
|
always @(posedge i_clk)
|
|
cover(!o_wb_cyc && (f_nreqs == f_nacks)
|
|
&& !f_cvr_aborted && f_cvr_fourth_ack);
|
|
// }}}
|
|
////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Temporary simplifications
|
|
// {{{
|
|
////////////////////////////////////////////////////////////////////////
|
|
//
|
|
//
|
|
|
|
// }}}
|
|
|
|
// Make Verilator happy -- formal section
|
|
// {{{
|
|
// Verilator lint_off UNUSED
|
|
wire unused_formal;
|
|
assign unused_formal = &{ f_dbl_next[1:0], f_const_addr[1:0] };
|
|
// Verilator lint_on UNUSED
|
|
// }}}
|
|
`endif // FORMAL
|
|
// }}}
|
|
endmodule
|
|
//
|
|
// Usage: (this) (prior) (old) (S6)
|
|
// Cells 374 387 585 459
|
|
// FDRE 135 108 203 171
|
|
// LUT1 2 3 2
|
|
// LUT2 9 3 4 5
|
|
// LUT3 98 76 104 71
|
|
// LUT4 2 0 2 2
|
|
// LUT5 3 35 35 3
|
|
// LUT6 6 5 10 43
|
|
// MUXCY 58 62 93 62
|
|
// MUXF7 1 0 2 3
|
|
// MUXF8 0 1 1
|
|
// RAM64X1D 0 32 32 32
|
|
// XORCY 60 64 96 64
|
|
//
|