1019 lines
25 KiB
Verilog
1019 lines
25 KiB
Verilog
////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Filename: memops.v
|
|
// {{{
|
|
// Project: 10Gb Ethernet switch
|
|
//
|
|
// Purpose: A memory unit to support a CPU.
|
|
//
|
|
// In the interests of code simplicity, this memory operator is
|
|
// susceptible to unknown results should a new command be sent to it
|
|
// before it completes the last one. Unpredictable results might then
|
|
// occurr.
|
|
//
|
|
// BIG ENDIAN
|
|
// Note that this core assumes a big endian bus, with the MSB
|
|
// of the bus word being the least bus address
|
|
//
|
|
// 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 memops #(
|
|
// {{{
|
|
parameter ADDRESS_WIDTH=28,
|
|
parameter DATA_WIDTH=32, // CPU's register width
|
|
parameter BUS_WIDTH=32,
|
|
parameter [0:0] OPT_LOCK=1'b1,
|
|
WITH_LOCAL_BUS=1'b1,
|
|
OPT_ALIGNMENT_ERR=1'b1,
|
|
OPT_LOWPOWER=1'b0,
|
|
OPT_LITTLE_ENDIAN = 1'b0,
|
|
localparam AW=ADDRESS_WIDTH
|
|
`ifdef FORMAL
|
|
, parameter F_LGDEPTH = 2
|
|
`endif
|
|
// }}}
|
|
) (
|
|
// {{{
|
|
input wire i_clk, i_reset,
|
|
// CPU interface
|
|
// {{{
|
|
input wire i_stb, i_lock,
|
|
input wire [2:0] i_op,
|
|
input wire [31:0] i_addr,
|
|
input wire [DATA_WIDTH-1:0] i_data,
|
|
input wire [4:0] i_oreg,
|
|
// CPU outputs
|
|
output wire o_busy,
|
|
output reg o_rdbusy,
|
|
output reg o_valid,
|
|
output reg o_err,
|
|
output reg [4:0] o_wreg,
|
|
output reg [DATA_WIDTH-1:0] o_result,
|
|
// }}}
|
|
// Wishbone
|
|
// {{{
|
|
output wire o_wb_cyc_gbl,
|
|
output wire o_wb_cyc_lcl,
|
|
output reg o_wb_stb_gbl,
|
|
output reg o_wb_stb_lcl,
|
|
output reg o_wb_we,
|
|
output reg [AW-1:0] o_wb_addr,
|
|
output reg [BUS_WIDTH-1:0] o_wb_data,
|
|
output reg [BUS_WIDTH/8-1:0] o_wb_sel,
|
|
// Wishbone inputs
|
|
input wire i_wb_stall, i_wb_ack, i_wb_err,
|
|
input wire [BUS_WIDTH-1:0] i_wb_data
|
|
// }}}
|
|
// }}}
|
|
);
|
|
|
|
// Declarations
|
|
// {{{
|
|
localparam WBLSB = $clog2(BUS_WIDTH/8);
|
|
`ifdef FORMAL
|
|
wire [(F_LGDEPTH-1):0] f_nreqs, f_nacks, f_outstanding;
|
|
`endif
|
|
|
|
wire misaligned;
|
|
reg r_wb_cyc_gbl, r_wb_cyc_lcl;
|
|
reg [2+WBLSB-1:0] r_op;
|
|
wire lock_gbl, lock_lcl;
|
|
wire lcl_bus, gbl_stb, lcl_stb;
|
|
|
|
reg [BUS_WIDTH/8-1:0] oword_sel;
|
|
wire [BUS_WIDTH/8-1:0] pre_sel;
|
|
wire [BUS_WIDTH-1:0] pre_result;
|
|
|
|
wire [1:0] oshift2;
|
|
wire [WBLSB-1:0] oshift;
|
|
|
|
// }}}
|
|
|
|
// misaligned
|
|
// {{{
|
|
generate if (OPT_ALIGNMENT_ERR)
|
|
begin : GENERATE_ALIGNMENT_ERR
|
|
reg r_misaligned;
|
|
|
|
always @(*)
|
|
casez({ i_op[2:1], i_addr[1:0] })
|
|
4'b01?1: r_misaligned = i_stb; // Words must be halfword aligned
|
|
4'b0110: r_misaligned = i_stb; // Words must be word aligned
|
|
4'b10?1: r_misaligned = i_stb; // Halfwords must be aligned
|
|
// 4'b11??: r_misaligned <= 1'b0; Byte access are never misaligned
|
|
default: r_misaligned = 1'b0;
|
|
endcase
|
|
|
|
assign misaligned = r_misaligned;
|
|
end else begin : NO_MISALIGNMENT_ERR
|
|
assign misaligned = 1'b0;
|
|
end endgenerate
|
|
// }}}
|
|
|
|
// lcl_stb, gbl_stb
|
|
// {{{
|
|
assign lcl_bus = (WITH_LOCAL_BUS)&&(i_addr[31:24]==8'hff);
|
|
assign lcl_stb = (i_stb)&&( lcl_bus)&&(!misaligned);
|
|
assign gbl_stb = (i_stb)&&(!lcl_bus)&&(!misaligned);
|
|
// }}}
|
|
|
|
// r_wb_cyc_gbl, r_wb_cyc_lcl
|
|
// {{{
|
|
initial r_wb_cyc_gbl = 1'b0;
|
|
initial r_wb_cyc_lcl = 1'b0;
|
|
always @(posedge i_clk)
|
|
if (i_reset)
|
|
begin
|
|
r_wb_cyc_gbl <= 1'b0;
|
|
r_wb_cyc_lcl <= 1'b0;
|
|
end else if ((r_wb_cyc_gbl)||(r_wb_cyc_lcl))
|
|
begin
|
|
if ((i_wb_ack)||(i_wb_err))
|
|
begin
|
|
r_wb_cyc_gbl <= 1'b0;
|
|
r_wb_cyc_lcl <= 1'b0;
|
|
end
|
|
end else begin // New memory operation
|
|
// Grab the wishbone
|
|
r_wb_cyc_lcl <= (lcl_stb);
|
|
r_wb_cyc_gbl <= (gbl_stb);
|
|
end
|
|
// }}}
|
|
|
|
// o_wb_stb_gbl
|
|
// {{{
|
|
initial o_wb_stb_gbl = 1'b0;
|
|
always @(posedge i_clk)
|
|
if (i_reset)
|
|
o_wb_stb_gbl <= 1'b0;
|
|
else if ((i_wb_err)&&(r_wb_cyc_gbl))
|
|
o_wb_stb_gbl <= 1'b0;
|
|
else if (gbl_stb)
|
|
o_wb_stb_gbl <= 1'b1;
|
|
else if (o_wb_cyc_gbl)
|
|
o_wb_stb_gbl <= (o_wb_stb_gbl)&&(i_wb_stall);
|
|
// }}}
|
|
|
|
// o_wb_stb_lcl
|
|
// {{{
|
|
initial o_wb_stb_lcl = 1'b0;
|
|
always @(posedge i_clk)
|
|
if (i_reset)
|
|
o_wb_stb_lcl <= 1'b0;
|
|
else if ((i_wb_err)&&(r_wb_cyc_lcl))
|
|
o_wb_stb_lcl <= 1'b0;
|
|
else if (lcl_stb)
|
|
o_wb_stb_lcl <= 1'b1;
|
|
else if (o_wb_cyc_lcl)
|
|
o_wb_stb_lcl <= (o_wb_stb_lcl)&&(i_wb_stall);
|
|
// }}}
|
|
|
|
// o_wb_we, o_wb_data, o_wb_sel
|
|
// {{{
|
|
always @(*)
|
|
begin
|
|
oword_sel = 0;
|
|
|
|
casez({ OPT_LITTLE_ENDIAN, i_op[2:1], i_addr[1:0] })
|
|
5'b00???: oword_sel[3:0] = 4'b1111;
|
|
5'b0100?: oword_sel[3:0] = 4'b1100;
|
|
5'b0101?: oword_sel[3:0] = 4'b0011;
|
|
5'b01100: oword_sel[3:0] = 4'b1000;
|
|
5'b01101: oword_sel[3:0] = 4'b0100;
|
|
5'b01110: oword_sel[3:0] = 4'b0010;
|
|
5'b01111: oword_sel[3:0] = 4'b0001;
|
|
//
|
|
// verilator coverage_off
|
|
5'b10???: oword_sel[3:0] = 4'b1111;
|
|
5'b1100?: oword_sel[3:0] = 4'b0011;
|
|
5'b1101?: oword_sel[3:0] = 4'b1100;
|
|
5'b11100: oword_sel[3:0] = 4'b0001;
|
|
5'b11101: oword_sel[3:0] = 4'b0010;
|
|
5'b11110: oword_sel[3:0] = 4'b0100;
|
|
5'b11111: oword_sel[3:0] = 4'b1000;
|
|
// verilator coverage_on
|
|
//
|
|
default: oword_sel[3:0] = 4'b1111;
|
|
endcase
|
|
end
|
|
|
|
// pre_sel
|
|
// {{{
|
|
generate if (BUS_WIDTH == 32)
|
|
begin : COPY_PRESEL
|
|
assign pre_sel = oword_sel;
|
|
end else if (OPT_LITTLE_ENDIAN)
|
|
begin : GEN_LILPRESEL
|
|
wire [WBLSB-3:0] shift;
|
|
|
|
assign shift = i_addr[WBLSB-1:2];
|
|
assign pre_sel = oword_sel << (4 * i_addr[WBLSB-1:2]);
|
|
end else begin : GEN_PRESEL
|
|
wire [WBLSB-3:0] shift;
|
|
|
|
assign shift = {(WBLSB-2){1'b1}} ^ i_addr[WBLSB-1:2];
|
|
assign pre_sel = oword_sel << (4 * shift);
|
|
end endgenerate
|
|
// }}}
|
|
|
|
assign oshift = i_addr[WBLSB-1:0];
|
|
assign oshift2 = i_addr[1:0];
|
|
|
|
initial o_wb_we = 1'b0;
|
|
initial o_wb_data = 0;
|
|
initial o_wb_sel = 0;
|
|
always @(posedge i_clk)
|
|
if (i_stb)
|
|
begin
|
|
o_wb_we <= i_op[0];
|
|
if (OPT_LOWPOWER)
|
|
begin
|
|
if (lcl_bus)
|
|
begin
|
|
// {{{
|
|
o_wb_data <= 0;
|
|
casez({ OPT_LITTLE_ENDIAN, i_op[2:1] })
|
|
3'b010: o_wb_data[31:0] <= { i_data[15:0], {(16){1'b0}} } >> (8*oshift2);
|
|
3'b011: o_wb_data[31:0] <= { i_data[ 7:0], {(24){1'b0}} } >> (8*oshift2);
|
|
3'b00?: o_wb_data[31:0] <= i_data[31:0];
|
|
//
|
|
// verilator coverage_off
|
|
3'b110: o_wb_data <= { {(BUS_WIDTH-16){1'b0}}, i_data[15:0] } << (8*oshift2);
|
|
3'b111: o_wb_data <= { {(BUS_WIDTH-8){1'b0}}, i_data[ 7:0] } << (8*oshift2);
|
|
3'b10?: o_wb_data <= { {(BUS_WIDTH-32){1'b0}}, i_data[31:0] } << (8*oshift2);
|
|
// verilator coverage_on
|
|
//
|
|
endcase
|
|
// }}}
|
|
end else begin
|
|
// {{{
|
|
casez({ OPT_LITTLE_ENDIAN, i_op[2:1] })
|
|
3'b010: o_wb_data <= { i_data[15:0], {(BUS_WIDTH-16){1'b0}} } >> (8*oshift);
|
|
3'b011: o_wb_data <= { i_data[ 7:0], {(BUS_WIDTH- 8){1'b0}} } >> (8*oshift);
|
|
3'b00?: o_wb_data <= { i_data[31:0], {(BUS_WIDTH-32){1'b0}} } >> (8*oshift);
|
|
//
|
|
3'b110: o_wb_data <= { {(BUS_WIDTH-16){1'b0}}, i_data[15:0] } << (8*oshift);
|
|
3'b111: o_wb_data <= { {(BUS_WIDTH-8){1'b0}}, i_data[ 7:0] } << (8*oshift);
|
|
3'b10?: o_wb_data <= { {(BUS_WIDTH-32){1'b0}}, i_data[31:0] } << (8*oshift);
|
|
//
|
|
endcase
|
|
// }}}
|
|
end
|
|
end else
|
|
casez({ i_op[2:1] })
|
|
2'b10: o_wb_data <= { (BUS_WIDTH/16){ i_data[15:0] } };
|
|
2'b11: o_wb_data <= { (BUS_WIDTH/ 8){ i_data[7:0] } };
|
|
default: o_wb_data <= {(BUS_WIDTH/32){i_data}};
|
|
endcase
|
|
|
|
if (lcl_bus)
|
|
begin
|
|
o_wb_addr <= i_addr[2 +: (AW+2>32 ? (32-2) : AW)];
|
|
o_wb_sel <= oword_sel;
|
|
end else begin
|
|
o_wb_addr <= i_addr[WBLSB +: (AW+WBLSB>32 ? (32-WBLSB) : AW)];
|
|
o_wb_sel <= pre_sel;
|
|
end
|
|
|
|
r_op <= { i_op[2:1] , i_addr[WBLSB-1:0] };
|
|
end else if ((OPT_LOWPOWER)&&(!o_wb_cyc_gbl)&&(!o_wb_cyc_lcl))
|
|
begin
|
|
o_wb_we <= 1'b0;
|
|
o_wb_addr <= 0;
|
|
o_wb_data <= {(BUS_WIDTH){1'b0}};
|
|
o_wb_sel <= {(BUS_WIDTH/8){1'b0}};
|
|
end
|
|
// }}}
|
|
|
|
// o_valid
|
|
// {{{
|
|
initial o_valid = 1'b0;
|
|
always @(posedge i_clk)
|
|
if (i_reset)
|
|
o_valid <= 1'b0;
|
|
else
|
|
o_valid <= (((o_wb_cyc_gbl)||(o_wb_cyc_lcl))
|
|
&&(i_wb_ack)&&(!o_wb_we));
|
|
// }}}
|
|
|
|
// o_err
|
|
// {{{
|
|
initial o_err = 1'b0;
|
|
always @(posedge i_clk)
|
|
if (i_reset)
|
|
o_err <= 1'b0;
|
|
else if ((r_wb_cyc_gbl)||(r_wb_cyc_lcl))
|
|
o_err <= i_wb_err;
|
|
else if ((i_stb)&&(!o_busy))
|
|
o_err <= misaligned;
|
|
else
|
|
o_err <= 1'b0;
|
|
// }}}
|
|
|
|
assign o_busy = (r_wb_cyc_gbl)||(r_wb_cyc_lcl);
|
|
|
|
// o_rdbusy
|
|
// {{{
|
|
initial o_rdbusy = 1'b0;
|
|
always @(posedge i_clk)
|
|
if (i_reset|| ((o_wb_cyc_gbl || o_wb_cyc_lcl)&&(i_wb_err || i_wb_ack)))
|
|
o_rdbusy <= 1'b0;
|
|
else if (i_stb && !i_op[0] && !misaligned)
|
|
o_rdbusy <= 1'b1;
|
|
else if (o_valid)
|
|
o_rdbusy <= 1'b0;
|
|
// }}}
|
|
|
|
always @(posedge i_clk)
|
|
if (i_stb)
|
|
o_wreg <= i_oreg;
|
|
|
|
// o_result
|
|
// {{{
|
|
generate if (OPT_LITTLE_ENDIAN)
|
|
begin : LILEND_RESULT
|
|
|
|
assign pre_result = i_wb_data >> (8*r_op[$clog2(BUS_WIDTH/8)-1:0]);
|
|
|
|
end else begin : BIGEND_RESULT
|
|
|
|
assign pre_result = i_wb_data << (8*r_op[$clog2(BUS_WIDTH/8)-1:0]);
|
|
|
|
end endgenerate
|
|
|
|
always @(posedge i_clk)
|
|
if ((OPT_LOWPOWER)&&(!i_wb_ack))
|
|
o_result <= 32'h0;
|
|
else if (o_wb_cyc_lcl && (BUS_WIDTH != 32))
|
|
begin
|
|
// The Local bus is naturally (and only) a 32-bit bus
|
|
casez({ OPT_LITTLE_ENDIAN, r_op[WBLSB +: 2], r_op[1:0] })
|
|
5'b?01??: o_result <= i_wb_data[31:0];
|
|
//
|
|
// Big endian
|
|
5'b0100?: o_result <= { 16'h00, i_wb_data[31:16] };
|
|
5'b0101?: o_result <= { 16'h00, i_wb_data[15: 0] };
|
|
5'b01100: o_result <= { 24'h00, i_wb_data[31:24] };
|
|
5'b01101: o_result <= { 24'h00, i_wb_data[23:16] };
|
|
5'b01110: o_result <= { 24'h00, i_wb_data[15: 8] };
|
|
5'b01111: o_result <= { 24'h00, i_wb_data[ 7: 0] };
|
|
//
|
|
// Little endian : Same bus result, just grab a different bits
|
|
// from the bus return to send back to the CPU.
|
|
// verilator coverage_off
|
|
5'b1100?: o_result <= { 16'h00, i_wb_data[15: 0] };
|
|
5'b1101?: o_result <= { 16'h00, i_wb_data[31:16] };
|
|
5'b11100: o_result <= { 24'h00, i_wb_data[ 7: 0] };
|
|
5'b11101: o_result <= { 24'h00, i_wb_data[15: 8] };
|
|
5'b11110: o_result <= { 24'h00, i_wb_data[23:16] };
|
|
5'b11111: o_result <= { 24'h00, i_wb_data[31:24] };
|
|
// verilator coverage_on
|
|
default: o_result <= i_wb_data[31:0];
|
|
endcase
|
|
end else begin
|
|
casez({ OPT_LITTLE_ENDIAN, r_op[$clog2(BUS_WIDTH/8) +: 2] })
|
|
// Word
|
|
//
|
|
// Big endian
|
|
3'b00?: o_result <= pre_result[BUS_WIDTH-1:BUS_WIDTH-32];
|
|
3'b010: o_result <= { 16'h00, pre_result[BUS_WIDTH-1:BUS_WIDTH-16] };
|
|
3'b011: o_result <= { 24'h00, pre_result[BUS_WIDTH-1:BUS_WIDTH-8] };
|
|
//
|
|
// Little endian : Same bus result, just grab a different bits
|
|
// from the bus return to send back to the CPU.
|
|
// verilator coverage_off
|
|
3'b10?: o_result <= pre_result[31: 0];
|
|
3'b110: o_result <= { 16'h00, pre_result[15: 0] };
|
|
3'b111: o_result <= { 24'h00, pre_result[ 7: 0] };
|
|
// verilator coverage_on
|
|
//
|
|
// Just to have an (unused) default
|
|
// default: o_result <= pre_result[31:0]; (Messes w/ coverage)
|
|
endcase
|
|
end
|
|
// }}}
|
|
|
|
// lock_gbl and lock_lcl
|
|
// {{{
|
|
generate
|
|
if (OPT_LOCK)
|
|
begin : GEN_LOCK
|
|
// {{{
|
|
reg r_lock_gbl, r_lock_lcl;
|
|
|
|
initial r_lock_gbl = 1'b0;
|
|
initial r_lock_lcl = 1'b0;
|
|
|
|
always @(posedge i_clk)
|
|
if (i_reset)
|
|
begin
|
|
r_lock_gbl <= 1'b0;
|
|
r_lock_lcl <= 1'b0;
|
|
end else if (((i_wb_err)&&((r_wb_cyc_gbl)||(r_wb_cyc_lcl)))
|
|
||(misaligned))
|
|
begin
|
|
// Kill the lock if
|
|
// there's a bus error, or
|
|
// User requests a misaligned memory op
|
|
r_lock_gbl <= 1'b0;
|
|
r_lock_lcl <= 1'b0;
|
|
end else begin
|
|
// Kill the lock if
|
|
// i_lock goes down
|
|
// User starts on the global bus, then switches
|
|
// to local or vice versa
|
|
r_lock_gbl <= (i_lock)&&((r_wb_cyc_gbl)||(lock_gbl))
|
|
&&(!lcl_stb);
|
|
r_lock_lcl <= (i_lock)&&((r_wb_cyc_lcl)||(lock_lcl))
|
|
&&(!gbl_stb);
|
|
end
|
|
|
|
assign lock_gbl = r_lock_gbl;
|
|
assign lock_lcl = r_lock_lcl;
|
|
|
|
assign o_wb_cyc_gbl = (r_wb_cyc_gbl)||(lock_gbl);
|
|
assign o_wb_cyc_lcl = (r_wb_cyc_lcl)||(lock_lcl);
|
|
// }}}
|
|
end else begin : NO_LOCK
|
|
// {{{
|
|
assign o_wb_cyc_gbl = (r_wb_cyc_gbl);
|
|
assign o_wb_cyc_lcl = (r_wb_cyc_lcl);
|
|
|
|
assign { lock_gbl, lock_lcl } = 2'b00;
|
|
|
|
// Make verilator happy
|
|
// verilator lint_off UNUSED
|
|
wire [2:0] lock_unused;
|
|
assign lock_unused = { i_lock, lock_gbl, lock_lcl };
|
|
// verilator lint_on UNUSED
|
|
// }}}
|
|
end endgenerate
|
|
// }}}
|
|
|
|
`ifdef VERILATOR
|
|
always @(posedge i_clk)
|
|
if ((r_wb_cyc_gbl)||(r_wb_cyc_lcl))
|
|
assert(!i_stb);
|
|
`endif
|
|
|
|
|
|
// Make verilator happy
|
|
// {{{
|
|
// verilator coverage_off
|
|
// verilator lint_off UNUSED
|
|
wire unused;
|
|
assign unused = &{ 1'b0, pre_result };
|
|
generate if (AW < 22)
|
|
begin : TOO_MANY_ADDRESS_BITS
|
|
|
|
wire [(21-AW):0] unused_addr;
|
|
assign unused_addr = i_addr[23:(AW+2)];
|
|
|
|
end endgenerate
|
|
// verilator lint_on UNUSED
|
|
// verilator coverage_on
|
|
// }}}
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Formal properties
|
|
// {{{
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
`ifdef FORMAL
|
|
`define ASSERT assert
|
|
`ifdef MEMOPS
|
|
`define ASSUME assume
|
|
`else
|
|
`define ASSUME assert
|
|
`endif
|
|
reg f_past_valid;
|
|
|
|
reg [2:0] fcpu_op;
|
|
reg [31:0] fcpu_addr, fcpu_data;;
|
|
reg [BUS_WIDTH-1:0] fbus_data, fpre_data;
|
|
reg [$clog2(BUS_WIDTH/8)-1:0] fcpu_shift;
|
|
reg fcpu_local, fcpu_misaligned;
|
|
reg [BUS_WIDTH/8-1:0] fbus_sel, fpre_sel;
|
|
|
|
initial f_past_valid = 0;
|
|
always @(posedge i_clk)
|
|
f_past_valid <= 1'b1;
|
|
|
|
always @(*)
|
|
if (!f_past_valid)
|
|
`ASSUME(i_reset);
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Bus properties
|
|
// {{{
|
|
////////////////////////////////////////////////////////////////////////
|
|
//
|
|
//
|
|
initial `ASSUME(!i_stb);
|
|
|
|
wire f_cyc, f_stb;
|
|
assign f_cyc = (o_wb_cyc_gbl)||(o_wb_cyc_lcl);
|
|
assign f_stb = (o_wb_stb_gbl)||(o_wb_stb_lcl);
|
|
|
|
fwb_master #(
|
|
// {{{
|
|
.AW(AW), .F_LGDEPTH(F_LGDEPTH), .DW(BUS_WIDTH),
|
|
.F_OPT_RMW_BUS_OPTION(OPT_LOCK),
|
|
.F_OPT_DISCONTINUOUS(OPT_LOCK)
|
|
// }}}
|
|
) f_wb(
|
|
// {{{
|
|
i_clk, i_reset,
|
|
f_cyc, f_stb, o_wb_we, o_wb_addr, o_wb_data, o_wb_sel,
|
|
i_wb_ack, i_wb_stall, i_wb_data, i_wb_err,
|
|
f_nreqs, f_nacks, f_outstanding
|
|
// }}}
|
|
);
|
|
|
|
|
|
// Rule: Only one of the two CYC's may be valid, never both
|
|
always @(posedge i_clk)
|
|
`ASSERT((!o_wb_cyc_gbl)||(!o_wb_cyc_lcl));
|
|
|
|
// Rule: Only one of the two STB's may be valid, never both
|
|
always @(posedge i_clk)
|
|
`ASSERT((!o_wb_stb_gbl)||(!o_wb_stb_lcl));
|
|
|
|
// Rule: if WITH_LOCAL_BUS is ever false, neither the local STB nor CYC
|
|
// may be valid
|
|
always @(*)
|
|
if (!WITH_LOCAL_BUS)
|
|
begin
|
|
`ASSERT(!o_wb_cyc_lcl);
|
|
`ASSERT(!o_wb_stb_lcl);
|
|
end
|
|
|
|
// Rule: If the global CYC is ever true, the LCL one cannot be true
|
|
// on the next clock without an intervening idle of both
|
|
always @(posedge i_clk)
|
|
if ((f_past_valid)&&($past(r_wb_cyc_gbl)))
|
|
`ASSERT(!r_wb_cyc_lcl);
|
|
|
|
// Same for if the LCL CYC is true
|
|
always @(posedge i_clk)
|
|
if ((f_past_valid)&&($past(r_wb_cyc_lcl)))
|
|
`ASSERT(!r_wb_cyc_gbl);
|
|
|
|
// STB can never be true unless CYC is also true
|
|
always @(posedge i_clk)
|
|
if (o_wb_stb_gbl)
|
|
`ASSERT(r_wb_cyc_gbl);
|
|
|
|
always @(posedge i_clk)
|
|
if (o_wb_stb_lcl)
|
|
`ASSERT(r_wb_cyc_lcl);
|
|
|
|
// This core only ever has zero or one outstanding transaction(s)
|
|
always @(posedge i_clk)
|
|
if ((o_wb_stb_gbl)||(o_wb_stb_lcl))
|
|
begin
|
|
`ASSERT(f_outstanding == 0);
|
|
end else
|
|
`ASSERT((f_outstanding == 0)||(f_outstanding == 1));
|
|
|
|
// The LOCK function only allows up to two transactions (at most)
|
|
// before CYC must be dropped.
|
|
always @(posedge i_clk)
|
|
if ((o_wb_stb_gbl)||(o_wb_stb_lcl))
|
|
begin
|
|
if (OPT_LOCK)
|
|
begin
|
|
`ASSERT((f_outstanding == 0)||(f_outstanding == 1));
|
|
end else
|
|
`ASSERT(f_nreqs <= 1);
|
|
end
|
|
// }}}
|
|
////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// CPU properties
|
|
// {{{
|
|
////////////////////////////////////////////////////////////////////////
|
|
//
|
|
//
|
|
reg f_done;
|
|
wire [(F_LGDEPTH-1):0] cpu_outstanding;
|
|
wire f_pc, f_rdbusy, f_gie, f_read_cycle;
|
|
wire [4:0] f_last_reg;
|
|
wire [4:0] f_addr_reg;
|
|
// Verilator lint_off UNDRIVEN
|
|
(* anyseq *) reg [4:0] f_areg;
|
|
// Verilator lint_on UNDRIVEN
|
|
|
|
assign f_rdbusy = f_cyc && (f_stb || f_outstanding > 0) && !o_wb_we;
|
|
|
|
initial f_done = 1'b0;
|
|
always @(posedge i_clk)
|
|
if (i_reset)
|
|
f_done <= 1'b0;
|
|
else
|
|
f_done <= ((o_wb_cyc_gbl)||(o_wb_cyc_lcl))&&(i_wb_ack);
|
|
|
|
fmem #(
|
|
// {{{
|
|
.F_LGDEPTH(F_LGDEPTH),
|
|
.OPT_LOCK(OPT_LOCK),
|
|
.OPT_MAXDEPTH(1)
|
|
// }}}
|
|
) fmemi(
|
|
// {{{
|
|
.i_clk(i_clk),
|
|
.i_sys_reset(i_reset),
|
|
.i_cpu_reset(i_reset),
|
|
.i_stb(i_stb),
|
|
.i_pipe_stalled(o_busy),
|
|
.i_clear_cache(1'b0),
|
|
.i_lock(i_lock),
|
|
.i_op(i_op), .i_addr(i_addr), .i_data(i_data), .i_oreg(i_oreg),
|
|
.i_areg(f_areg),
|
|
.i_busy(o_busy),
|
|
.i_rdbusy(f_rdbusy),
|
|
.i_valid(o_valid), .i_done(f_done), .i_err(o_err),
|
|
.i_wreg(o_wreg), .i_result(o_result),
|
|
.f_outstanding(cpu_outstanding),
|
|
.f_pc(f_pc),
|
|
.f_gie(f_gie),
|
|
.f_read_cycle(f_read_cycle),
|
|
.f_last_reg(f_last_reg), .f_addr_reg(f_addr_reg)
|
|
// }}}
|
|
);
|
|
|
|
always @(*)
|
|
if (!o_err)
|
|
assert(cpu_outstanding == f_outstanding + (f_stb ? 1:0)
|
|
+ ((f_done || o_err) ? 1:0));
|
|
|
|
always @(*)
|
|
assert(cpu_outstanding <= 1);
|
|
|
|
always @(*)
|
|
if (f_pc)
|
|
begin
|
|
assert(o_wreg[3:1] == 3'h7);
|
|
end else if (f_rdbusy)
|
|
assert(o_wreg[3:1] != 3'h7);
|
|
|
|
always @(*)
|
|
if (o_busy)
|
|
assert(o_wreg[4] == f_gie);
|
|
|
|
always @(*)
|
|
if (!o_err)
|
|
assert(f_rdbusy == o_rdbusy);
|
|
|
|
always @(*)
|
|
if (o_busy)
|
|
assert(o_wb_we == !f_read_cycle);
|
|
|
|
always @(*)
|
|
if (cpu_outstanding > 0)
|
|
assert(f_last_reg == o_wreg);
|
|
// }}}
|
|
////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Tying the two together
|
|
// {{{
|
|
////////////////////////////////////////////////////////////////////////
|
|
//
|
|
//
|
|
|
|
// Following any i_stb request, assuming we are idle, immediately
|
|
// begin a bus transaction
|
|
always @(posedge i_clk)
|
|
if ((f_past_valid)&&($past(i_stb))
|
|
&&(!$past(f_cyc))&&(!$past(i_reset)))
|
|
begin
|
|
if ($past(misaligned))
|
|
begin
|
|
`ASSERT(!f_cyc);
|
|
`ASSERT(!o_busy);
|
|
`ASSERT(o_err);
|
|
`ASSERT(!o_valid);
|
|
end else begin
|
|
`ASSERT(f_cyc);
|
|
`ASSERT(o_busy);
|
|
end
|
|
end
|
|
|
|
// always @(posedge i_clk)
|
|
// if (o_busy)
|
|
// `ASSUME(!i_stb);
|
|
|
|
always @(*)
|
|
if (o_err || o_valid)
|
|
`ASSERT(!o_busy);
|
|
|
|
always @(posedge i_clk)
|
|
if (o_wb_cyc_gbl)
|
|
`ASSERT((o_busy)||(lock_gbl));
|
|
|
|
always @(posedge i_clk)
|
|
if (o_wb_cyc_lcl)
|
|
`ASSERT((o_busy)||(lock_lcl));
|
|
|
|
always @(posedge i_clk)
|
|
if (f_outstanding > 0)
|
|
`ASSERT(o_busy);
|
|
|
|
// If a transaction ends in an error, send o_err on the output port.
|
|
always @(posedge i_clk)
|
|
if (f_past_valid && !$past(i_reset))
|
|
begin
|
|
if (($past(f_cyc))&&($past(i_wb_err)))
|
|
begin
|
|
`ASSERT(o_err);
|
|
end else if ($past(misaligned))
|
|
`ASSERT(o_err);
|
|
end
|
|
|
|
// Always following a successful ACK, return an O_VALID value.
|
|
always @(posedge i_clk)
|
|
if (f_past_valid && !$past(i_reset))
|
|
begin
|
|
if(($past(f_cyc))&&($past(i_wb_ack))
|
|
&&(!$past(o_wb_we)))
|
|
begin
|
|
`ASSERT(o_valid);
|
|
end else if ($past(misaligned))
|
|
begin
|
|
`ASSERT((!o_valid)&&(o_err));
|
|
end else
|
|
`ASSERT(!o_valid);
|
|
end
|
|
|
|
always @(posedge i_clk)
|
|
if (i_stb)
|
|
begin
|
|
fcpu_op <= i_op;
|
|
fcpu_addr <= i_addr;
|
|
fcpu_data <= i_data;
|
|
end
|
|
|
|
always @(*)
|
|
begin
|
|
fcpu_local = (&fcpu_addr[31:24]) && WITH_LOCAL_BUS;
|
|
|
|
if (OPT_LITTLE_ENDIAN)
|
|
begin
|
|
// {{{
|
|
casez(fcpu_op[2:1])
|
|
2'b11: fpre_sel = { {(BUS_WIDTH/8-1){1'b0}}, 1'b1 };
|
|
2'b10: fpre_sel = { {(BUS_WIDTH/8-2){1'b0}}, 2'b11 };
|
|
2'b0?: fpre_sel = { {(BUS_WIDTH/8-4){1'b0}}, 4'b1111 };
|
|
endcase
|
|
|
|
casez(fcpu_op[2:1])
|
|
2'b11: fpre_data = { {(BUS_WIDTH- 8){1'b0}}, fcpu_data[ 7:0] };
|
|
2'b10: fpre_data = { {(BUS_WIDTH-16){1'b0}}, fcpu_data[15:0] };
|
|
2'b0?: fpre_data = { {(BUS_WIDTH-32){1'b0}}, fcpu_data[31:0] };
|
|
endcase
|
|
// }}}
|
|
end else if (fcpu_local)
|
|
begin
|
|
// {{{
|
|
fpre_sel = 0;
|
|
casez(fcpu_op[2:1])
|
|
2'b11: fpre_sel[3:0] = 4'b1000;
|
|
2'b10: fpre_sel[3:0] = 4'b1100;
|
|
2'b0?: fpre_sel[3:0] = 4'b1111;
|
|
endcase
|
|
|
|
fpre_data = 0;
|
|
casez(fcpu_op[2:1])
|
|
2'b11: fpre_data[31:0] = { fcpu_data[ 7:0], {(24){1'b0}} };
|
|
2'b10: fpre_data[31:0] = { fcpu_data[15:0], {(16){1'b0}} };
|
|
2'b0?: fpre_data[31:0] = fcpu_data[31:0];
|
|
endcase
|
|
// }}}
|
|
end else begin
|
|
// {{{
|
|
casez(fcpu_op[2:1])
|
|
2'b11: fpre_sel = { 1'b1, {(BUS_WIDTH/8-1){1'b0}} };
|
|
2'b10: fpre_sel = { 2'b11, {(BUS_WIDTH/8-2){1'b0}} };
|
|
2'b0?: fpre_sel = { 4'b1111, {(BUS_WIDTH/8-4){1'b0}} };
|
|
endcase
|
|
|
|
casez(fcpu_op[2:1])
|
|
2'b11: fpre_data = { fcpu_data[ 7:0], {(BUS_WIDTH- 8){1'b0}} };
|
|
2'b10: fpre_data = { fcpu_data[15:0], {(BUS_WIDTH-16){1'b0}} };
|
|
2'b0?: fpre_data = { fcpu_data[31:0], {(BUS_WIDTH-32){1'b0}} };
|
|
endcase
|
|
// }}}
|
|
end
|
|
|
|
|
|
casez({ fcpu_op[2:1], fcpu_addr[1:0] })
|
|
4'b01?1: fcpu_misaligned = 1'b1; // Words must be halfword aligned
|
|
4'b0110: fcpu_misaligned = 1'b1; // Words must be word aligned
|
|
4'b10?1: fcpu_misaligned = 1'b1; // Halfwords must be aligned
|
|
// 4'b11??: fcpu_misaligned <= 1'b0; Byte access are never misaligned
|
|
default: fcpu_misaligned = 1'b0;
|
|
endcase
|
|
|
|
if (fcpu_local)
|
|
begin
|
|
fcpu_shift = fcpu_addr[1:0];
|
|
if (OPT_LITTLE_ENDIAN)
|
|
begin
|
|
fbus_sel = fpre_sel << fcpu_shift;
|
|
fbus_data = fpre_data << (8*fcpu_shift);
|
|
end else begin
|
|
fbus_sel = fpre_sel >> (fcpu_shift + (DATA_WIDTH/8-4));
|
|
fbus_data = fpre_data >> (8*(fcpu_shift + (DATA_WIDTH/8-4)));
|
|
end
|
|
end else begin
|
|
fcpu_shift = fcpu_addr[WBLSB-1:0];
|
|
if (OPT_LITTLE_ENDIAN)
|
|
begin
|
|
fbus_sel = fpre_sel << fcpu_shift;
|
|
fbus_data = fpre_data << (8*fcpu_shift);
|
|
end else begin
|
|
fbus_sel = fpre_sel >> fcpu_shift;
|
|
fbus_data = fpre_data >> (8*fcpu_shift);
|
|
end
|
|
end
|
|
|
|
if (!OPT_LOWPOWER)
|
|
casez(fcpu_op[2:1])
|
|
2'b11: fbus_data = {(BUS_WIDTH/ 8){fcpu_data[ 7:0] } };
|
|
2'b10: fbus_data = {(BUS_WIDTH/16){fcpu_data[15:0] } };
|
|
2'b0?: fbus_data = {(BUS_WIDTH/32){fcpu_data[31:0] } };
|
|
endcase
|
|
end
|
|
|
|
always @(*)
|
|
if (OPT_ALIGNMENT_ERR && fcpu_misaligned)
|
|
assert(!o_valid && !f_cyc);
|
|
|
|
always @(*)
|
|
if (f_stb)
|
|
begin
|
|
if (fcpu_local)
|
|
begin
|
|
assert(o_wb_stb_lcl);
|
|
assert(o_wb_addr == fcpu_addr[AW+1:2]);
|
|
end else begin
|
|
assert(o_wb_stb_gbl);
|
|
assert(o_wb_addr == fcpu_addr[WBLSB +: AW]);
|
|
end
|
|
|
|
if (fcpu_op[0])
|
|
begin
|
|
`ASSERT(o_wb_we);
|
|
`ASSERT(fcpu_misaligned || o_wb_sel == fbus_sel);
|
|
`ASSERT(fcpu_misaligned || o_wb_data == fbus_data);
|
|
end else begin
|
|
`ASSERT(!o_wb_we);
|
|
end
|
|
end
|
|
|
|
always @(*)
|
|
if (f_cyc)
|
|
assert(o_wb_cyc_lcl == fcpu_local);
|
|
|
|
initial o_wb_we = 1'b0;
|
|
always @(posedge i_clk)
|
|
if ((f_past_valid)&&(!$past(i_reset))&&($past(i_stb)))
|
|
begin
|
|
// On a write, assert o_wb_we should be true
|
|
assert( $past(i_op[0]) == o_wb_we);
|
|
end
|
|
|
|
always @(posedge i_clk)
|
|
if (o_wb_stb_lcl)
|
|
`ASSERT(fcpu_local);
|
|
|
|
always @(posedge i_clk)
|
|
if ((f_past_valid)&&(!$past(i_reset))&&($past(misaligned)))
|
|
begin
|
|
`ASSERT(!o_wb_cyc_gbl);
|
|
`ASSERT(!o_wb_cyc_lcl);
|
|
`ASSERT(!o_wb_stb_gbl);
|
|
`ASSERT(!o_wb_stb_lcl);
|
|
`ASSERT(o_err);
|
|
end
|
|
|
|
// always @(posedge i_clk)
|
|
// if ((!f_past_valid)||($past(i_reset)))
|
|
// `ASSUME(!i_stb);
|
|
|
|
always @(posedge i_clk)
|
|
if ((f_past_valid)&&(OPT_LOCK)
|
|
&&(!$past(i_reset))&&(!$past(i_wb_err))
|
|
&&(!$past(misaligned))
|
|
&&(!$past(lcl_stb))
|
|
&&($past(i_lock))&&($past(lock_gbl)))
|
|
assert(lock_gbl);
|
|
|
|
always @(posedge i_clk)
|
|
if ((f_past_valid)&&(OPT_LOCK)
|
|
&&(!$past(i_reset))&&(!$past(i_wb_err))
|
|
&&(!$past(misaligned))
|
|
&&(!$past(lcl_stb))
|
|
&&($past(o_wb_cyc_gbl))&&($past(i_lock))
|
|
&&($past(lock_gbl)))
|
|
assert(o_wb_cyc_gbl);
|
|
|
|
always @(posedge i_clk)
|
|
if ((f_past_valid)&&(OPT_LOCK)
|
|
&&(!$past(i_reset))&&(!$past(i_wb_err))
|
|
&&(!$past(misaligned))
|
|
&&(!$past(gbl_stb))
|
|
&&($past(o_wb_cyc_lcl))&&($past(i_lock))
|
|
&&($past(lock_lcl)))
|
|
assert(o_wb_cyc_lcl);
|
|
// }}}
|
|
////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Cover properties
|
|
// {{{
|
|
////////////////////////////////////////////////////////////////////////
|
|
//
|
|
//
|
|
always @(posedge i_clk)
|
|
cover(i_wb_ack);
|
|
|
|
// Cover a response on the same clock it is made
|
|
always @(posedge i_clk)
|
|
cover((o_wb_stb_gbl)&&(i_wb_ack));
|
|
|
|
// Cover a response a clock later
|
|
always @(posedge i_clk)
|
|
cover((o_wb_stb_gbl)&&(i_wb_ack));
|
|
|
|
always @(posedge i_clk)
|
|
cover(f_done);
|
|
|
|
always @(posedge i_clk)
|
|
cover(f_done && !o_busy);
|
|
|
|
generate if (WITH_LOCAL_BUS)
|
|
begin
|
|
|
|
// Same things on the local bus
|
|
always @(posedge i_clk)
|
|
cover((o_wb_cyc_lcl)&&(!o_wb_stb_lcl)&&(i_wb_ack));
|
|
always @(posedge i_clk)
|
|
cover((o_wb_stb_lcl)&&(i_wb_ack));
|
|
|
|
end endgenerate
|
|
// }}}
|
|
|
|
// Make Verilator happy
|
|
// {{{
|
|
// Verilator lint_off UNUSED
|
|
wire unused;
|
|
assign unused = &{ 1'b0, f_nacks, f_addr_reg };
|
|
// Verilator lint_on UNUSED
|
|
// }}}
|
|
`endif
|
|
// }}}
|
|
endmodule
|
|
//
|
|
//
|
|
// Usage (from yosys):
|
|
// (BFOR) (!ZOI,ALIGN) (ZOI,ALIGN) (!ZOI,!ALIGN)
|
|
// Cells 230 226 281 225
|
|
// FDRE 114 116 116 116
|
|
// LUT2 17 23 76 19
|
|
// LUT3 9 23 17 20
|
|
// LUT4 15 4 11 14
|
|
// LUT5 18 18 7 15
|
|
// LUT6 33 18 54 38
|
|
// MUX7 16 12 2
|
|
// MUX8 8 1 1
|
|
//
|
|
//
|