180 lines
5.7 KiB
Systemverilog
180 lines
5.7 KiB
Systemverilog
`timescale 1ps/1ps
|
||
|
||
module picorv32_tcm #(parameter ADDR_WIDTH = 8,
|
||
parameter USE_LOOK_AHEAD = 0,
|
||
parameter USE_ADDR_MUX = 1,
|
||
parameter MEM_INIT_FILE = "")
|
||
(input wire clock,
|
||
/* verilator lint_off UNUSED */
|
||
// Not used in look-ahead mode
|
||
input wire reset,
|
||
/* verilator lint_on UNUSED */
|
||
|
||
/* PicoRV32 bus interface */
|
||
input wire mem_valid,
|
||
output wire mem_ready,
|
||
input wire [ADDR_WIDTH-1:0] mem_addr,
|
||
input wire [31:0] mem_wdata,
|
||
input wire [3:0] mem_wstrb,
|
||
output reg [31:0] mem_rdata,
|
||
|
||
// PicoRV32 look-ahead address.
|
||
// Not used in non-look-ahead mode.
|
||
/* verilator lint_off UNUSED */
|
||
input wire [ADDR_WIDTH-1:0] mem_la_addr
|
||
/* verilator lint_off UNUSED */
|
||
);
|
||
|
||
logic [31:0] ram[0:(2 ** (ADDR_WIDTH-2))-1];
|
||
if (MEM_INIT_FILE != "")
|
||
initial $readmemh(MEM_INIT_FILE, ram);
|
||
|
||
/* verilator lint_off UNUSED */
|
||
// Bits [1:0] are not used
|
||
logic [ADDR_WIDTH-1:0] byte_addr;
|
||
/* verilator lint_on UNUSED */
|
||
logic [ADDR_WIDTH-3:0] word_addr;
|
||
logic write;
|
||
|
||
assign word_addr = byte_addr[ADDR_WIDTH-1:2];
|
||
|
||
always @(posedge clock) begin
|
||
for (int n = 0; n < 4; n += 1)
|
||
if (write && mem_wstrb[n])
|
||
ram[word_addr][n*8 +: 8] <= mem_wdata[n*8 +: 8];
|
||
|
||
mem_rdata <= ram[word_addr];
|
||
end
|
||
|
||
if (USE_LOOK_AHEAD == 0) begin : no_look_ahead
|
||
logic data_ready;
|
||
|
||
always_ff @(posedge clock)
|
||
if (reset) data_ready <= 1'b0;
|
||
else
|
||
// Don't use ternary operator to prevent
|
||
// X-propagation from PicoRV32 core
|
||
// data_ready <= mem_valid & ~(|mem_wstrb);
|
||
if (mem_valid && mem_wstrb == '0)
|
||
data_ready <= 1'b1;
|
||
else
|
||
data_ready <= 1'b0;
|
||
|
||
|
||
assign byte_addr = mem_addr;
|
||
assign write = mem_valid & (|mem_wstrb);
|
||
assign mem_ready = data_ready | write;
|
||
end
|
||
else begin : look_ahead
|
||
logic data_ready;
|
||
|
||
always_ff @(posedge clock)
|
||
if (reset) data_ready <= 1'b0;
|
||
else
|
||
// Don't use ternary operator to prevent
|
||
// X-propagation from PicoRV32 core
|
||
// data_ready <= ~(mem_valid && (|mem_wstrb));
|
||
if (mem_valid && mem_wstrb != '0)
|
||
data_ready <= 1'b0;
|
||
else
|
||
data_ready <= 1'b1;
|
||
|
||
/* mem_la_addr валиден как минимум один такт после поднятия mem_valid.
|
||
* Т.е. в принципе можно обойтись без мультиплескора. В формальной части
|
||
* добавлено соответствуюшее утверждение. */
|
||
if (USE_ADDR_MUX == 0)
|
||
assign byte_addr = mem_la_addr[ADDR_WIDTH-1:0];
|
||
else
|
||
assign byte_addr = mem_valid ?
|
||
mem_addr[ADDR_WIDTH-1:0] :
|
||
mem_la_addr[ADDR_WIDTH-1:0];
|
||
|
||
assign write = mem_valid & (|mem_wstrb);
|
||
assign mem_ready = data_ready;
|
||
end
|
||
|
||
`ifdef FORMAL
|
||
// Past valid flag
|
||
logic have_past = 1'b0;
|
||
always_ff @(posedge clock) have_past <= 1'b1;
|
||
|
||
// Assumptions
|
||
always @(*) assume(reset == 1'b0);
|
||
|
||
always @(posedge clock)
|
||
if (have_past) begin
|
||
// mem_addr <= mem_la_addr
|
||
if ($rose(mem_valid))
|
||
assume(mem_addr == $past(mem_la_addr));
|
||
|
||
// Stable mem_addr and mem_data when mem_valid is active
|
||
if (mem_valid) begin
|
||
assume($stable(mem_addr));
|
||
assume($stable(mem_wdata));
|
||
assume($stable(mem_wstrb));
|
||
end
|
||
|
||
// Assume mem_valid will not cleared while mem_ready is not active
|
||
if ($past(mem_valid) && !$past(mem_ready))
|
||
assume(mem_valid == 1'b1);
|
||
|
||
// Assume mem_valid will cleared after memory transaction complete
|
||
if ($past(mem_valid) && $past(mem_ready))
|
||
assume(mem_valid == 1'b0);
|
||
|
||
// WARN: May be wrong assumption
|
||
// Assume mem_add == mem_la_addr on first clock cycle of mem_valid activity
|
||
if ($rose(mem_valid))
|
||
assume(mem_addr == mem_la_addr);
|
||
end
|
||
else begin
|
||
// Initial mem_valid = 1'b0
|
||
assume(mem_valid == 1'b0);
|
||
end
|
||
|
||
// Data read
|
||
always_ff @(posedge clock)
|
||
if (have_past)
|
||
if (mem_valid && mem_ready && mem_wstrb == '0)
|
||
assert(mem_rdata == ram[mem_addr[ADDR_WIDTH-1:2]]);
|
||
|
||
// Data write
|
||
always_ff @(posedge clock) begin
|
||
logic [3:0] mem_wstrb_past;
|
||
|
||
if (have_past) begin
|
||
mem_wstrb_past = $past(mem_wstrb);
|
||
|
||
if ($past(mem_valid) && $past(mem_ready) && mem_wstrb_past != '0)
|
||
for (int n = 0; n < 4; n += 1)
|
||
if (mem_wstrb_past[n])
|
||
assert(ram[$past(mem_addr[ADDR_WIDTH-1:2])][n*8 +: 8] == $past(mem_wdata[n*8 +: 8]));
|
||
end
|
||
end
|
||
|
||
// Mem ready
|
||
always_ff @(posedge clock)
|
||
if (have_past)
|
||
if (USE_LOOK_AHEAD == 0) begin
|
||
// Write transaction
|
||
if (mem_wstrb != '0 && mem_valid)
|
||
assert(mem_ready);
|
||
|
||
// First cycle of read transaction
|
||
if (mem_wstrb == '0 && !$past(mem_valid)&& mem_valid)
|
||
assert(!mem_ready);
|
||
|
||
// Second cycle of read transaction
|
||
if ($past(mem_wstrb) == '0 && $past(mem_valid) && mem_valid)
|
||
assert(mem_ready);
|
||
end
|
||
else begin
|
||
// In look-ahead mode mem_ready always active
|
||
if (mem_valid)
|
||
assert(mem_ready);
|
||
end
|
||
|
||
`endif
|
||
|
||
endmodule // picorv32_tcm
|