blob: d3de6f67d930db633848395e42d4ae2447378c4d [file] [log] [blame]
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
//
// Asynchronous Dual-Port SRAM Wrapper
//
// Supported configurations:
// - ECC for 32b and 64b wide memories with no write mask
// (Width == 32 or Width == 64, DataBitsPerMask is ignored).
// - Byte parity if Width is a multiple of 8 bit and write masks have Byte
// granularity (DataBitsPerMask == 8).
//
// Note that the write mask needs to be per Byte if parity is enabled. If ECC is enabled, the write
// mask cannot be used and has to be tied to {Width{1'b1}}.
`include "prim_assert.sv"
module prim_ram_2p_async_adv import prim_ram_2p_pkg::*; #(
parameter int Depth = 512,
parameter int Width = 32,
parameter int DataBitsPerMask = 1, // Number of data bits per bit of write mask
parameter MemInitFile = "", // VMEM file to initialize the memory with
// Configurations
parameter bit EnableECC = 0, // Enables per-word ECC
parameter bit EnableParity = 0, // Enables per-Byte Parity
parameter bit EnableInputPipeline = 0, // Adds an input register (read latency +1)
parameter bit EnableOutputPipeline = 0, // Adds an output register (read latency +1)
// This switch allows to switch to standard Hamming ECC instead of the HSIAO ECC.
// It is recommended to leave this parameter at its default setting (HSIAO),
// since this results in a more compact and faster implementation.
parameter bit HammingECC = 0,
localparam int Aw = prim_util_pkg::vbits(Depth)
) (
input clk_a_i,
input clk_b_i,
input rst_a_ni,
input rst_b_ni,
input a_req_i,
input a_write_i,
input [Aw-1:0] a_addr_i,
input [Width-1:0] a_wdata_i,
input [Width-1:0] a_wmask_i, // cannot be used with ECC, tie to 1 in that case
output logic [Width-1:0] a_rdata_o,
output logic a_rvalid_o, // read response (a_rdata_o) is valid
output logic [1:0] a_rerror_o, // Bit1: Uncorrectable, Bit0: Correctable
input b_req_i,
input b_write_i,
input [Aw-1:0] b_addr_i,
input [Width-1:0] b_wdata_i,
input [Width-1:0] b_wmask_i, // cannot be used with ECC, tie to 1 in that case
output logic [Width-1:0] b_rdata_o,
output logic b_rvalid_o, // read response (b_rdata_o) is valid
output logic [1:0] b_rerror_o, // Bit1: Uncorrectable, Bit0: Correctable
// config
input ram_2p_cfg_t cfg_i
);
`ASSERT_INIT(CannotHaveEccAndParity_A, !(EnableParity && EnableECC))
// Calculate ECC width
localparam int ParWidth = (EnableParity) ? Width/8 :
(!EnableECC) ? 0 :
(Width <= 4) ? 4 :
(Width <= 11) ? 5 :
(Width <= 26) ? 6 :
(Width <= 57) ? 7 :
(Width <= 120) ? 8 : 8 ;
localparam int TotalWidth = Width + ParWidth;
// If byte parity is enabled, the write enable bits are used to write memory colums
// with 8 + 1 = 9 bit width (data plus corresponding parity bit).
// If ECC is enabled, the DataBitsPerMask is ignored.
localparam int LocalDataBitsPerMask = (EnableParity) ? 9 :
(EnableECC) ? TotalWidth :
DataBitsPerMask;
////////////////////////////
// RAM Primitive Instance //
////////////////////////////
logic a_req_q, a_req_d ;
logic a_write_q, a_write_d ;
logic [Aw-1:0] a_addr_q, a_addr_d ;
logic [TotalWidth-1:0] a_wdata_q, a_wdata_d ;
logic [TotalWidth-1:0] a_wmask_q, a_wmask_d ;
logic a_rvalid_q, a_rvalid_d, a_rvalid_sram_q ;
logic [Width-1:0] a_rdata_q, a_rdata_d ;
logic [TotalWidth-1:0] a_rdata_sram ;
logic [1:0] a_rerror_q, a_rerror_d ;
logic b_req_q, b_req_d ;
logic b_write_q, b_write_d ;
logic [Aw-1:0] b_addr_q, b_addr_d ;
logic [TotalWidth-1:0] b_wdata_q, b_wdata_d ;
logic [TotalWidth-1:0] b_wmask_q, b_wmask_d ;
logic b_rvalid_q, b_rvalid_d, b_rvalid_sram_q ;
logic [Width-1:0] b_rdata_q, b_rdata_d ;
logic [TotalWidth-1:0] b_rdata_sram ;
logic [1:0] b_rerror_q, b_rerror_d ;
prim_ram_2p #(
.MemInitFile (MemInitFile),
.Width (TotalWidth),
.Depth (Depth),
.DataBitsPerMask (LocalDataBitsPerMask)
) u_mem (
.clk_a_i (clk_a_i),
.clk_b_i (clk_b_i),
.a_req_i (a_req_q),
.a_write_i (a_write_q),
.a_addr_i (a_addr_q),
.a_wdata_i (a_wdata_q),
.a_wmask_i (a_wmask_q),
.a_rdata_o (a_rdata_sram),
.b_req_i (b_req_q),
.b_write_i (b_write_q),
.b_addr_i (b_addr_q),
.b_wdata_i (b_wdata_q),
.b_wmask_i (b_wmask_q),
.b_rdata_o (b_rdata_sram),
.cfg_i
);
always_ff @(posedge clk_a_i or negedge rst_a_ni) begin
if (!rst_a_ni) begin
a_rvalid_sram_q <= 1'b0;
end else begin
a_rvalid_sram_q <= a_req_q & ~a_write_q;
end
end
always_ff @(posedge clk_b_i or negedge rst_b_ni) begin
if (!rst_b_ni) begin
b_rvalid_sram_q <= 1'b0;
end else begin
b_rvalid_sram_q <= b_req_q & ~b_write_q;
end
end
assign a_req_d = a_req_i;
assign a_write_d = a_write_i;
assign a_addr_d = a_addr_i;
assign a_rvalid_o = a_rvalid_q;
assign a_rdata_o = a_rdata_q;
assign a_rerror_o = a_rerror_q;
assign b_req_d = b_req_i;
assign b_write_d = b_write_i;
assign b_addr_d = b_addr_i;
assign b_rvalid_o = b_rvalid_q;
assign b_rdata_o = b_rdata_q;
assign b_rerror_o = b_rerror_q;
/////////////////////////////
// ECC / Parity Generation //
/////////////////////////////
if (EnableParity == 0 && EnableECC) begin : gen_secded
// check supported widths
`ASSERT_INIT(SecDecWidth_A, Width inside {32})
// the wmask is constantly set to 1 in this case
`ASSERT(OnlyWordWritePossibleWithEccPortA_A, a_req_i |->
a_wmask_i == {Width{1'b1}}, clk_a_i, rst_a_ni)
`ASSERT(OnlyWordWritePossibleWithEccPortB_A, b_req_i |->
b_wmask_i == {Width{1'b1}}, clk_b_i, rst_b_ni)
assign a_wmask_d = {TotalWidth{1'b1}};
assign b_wmask_d = {TotalWidth{1'b1}};
if (Width == 32) begin : gen_secded_39_32
if (HammingECC) begin : gen_hamming
prim_secded_inv_hamming_39_32_enc u_enc_a (
.data_i(a_wdata_i),
.data_o(a_wdata_d)
);
prim_secded_inv_hamming_39_32_dec u_dec_a (
.data_i (a_rdata_sram),
.data_o (a_rdata_d[0+:Width]),
.syndrome_o ( ),
.err_o (a_rerror_d)
);
prim_secded_inv_hamming_39_32_enc u_enc_b (
.data_i(b_wdata_i),
.data_o(b_wdata_d)
);
prim_secded_inv_hamming_39_32_dec u_dec_b (
.data_i (b_rdata_sram),
.data_o (b_rdata_d[0+:Width]),
.syndrome_o ( ),
.err_o (b_rerror_d)
);
end else begin : gen_hsiao
prim_secded_inv_39_32_enc u_enc_a (
.data_i(a_wdata_i),
.data_o(a_wdata_d)
);
prim_secded_inv_39_32_dec u_dec_a (
.data_i (a_rdata_sram),
.data_o (a_rdata_d[0+:Width]),
.syndrome_o ( ),
.err_o (a_rerror_d)
);
prim_secded_inv_39_32_enc u_enc_b (
.data_i(b_wdata_i),
.data_o(b_wdata_d)
);
prim_secded_inv_39_32_dec u_dec_b (
.data_i (b_rdata_sram),
.data_o (b_rdata_d[0+:Width]),
.syndrome_o ( ),
.err_o (b_rerror_d)
);
end
end
end else if (EnableParity) begin : gen_byte_parity
`ASSERT_INIT(ParityNeedsByteWriteMask_A, DataBitsPerMask == 8)
`ASSERT_INIT(WidthNeedsToBeByteAligned_A, Width % 8 == 0)
always_comb begin : p_parity
a_rerror_d = '0;
b_rerror_d = '0;
for (int i = 0; i < Width/8; i ++) begin
// Data mapping. We have to make 8+1 = 9 bit groups
// that have the same write enable such that FPGA tools
// can map this correctly to BRAM resources.
a_wmask_d[i*9 +: 8] = a_wmask_i[i*8 +: 8];
a_wdata_d[i*9 +: 8] = a_wdata_i[i*8 +: 8];
a_rdata_d[i*8 +: 8] = a_rdata_sram[i*9 +: 8];
b_wmask_d[i*9 +: 8] = b_wmask_i[i*8 +: 8];
b_wdata_d[i*9 +: 8] = b_wdata_i[i*8 +: 8];
b_rdata_d[i*8 +: 8] = b_rdata_sram[i*9 +: 8];
// parity generation (odd parity)
a_wdata_d[i*9 + 8] = ~(^a_wdata_i[i*8 +: 8]);
a_wmask_d[i*9 + 8] = &a_wmask_i[i*8 +: 8];
b_wdata_d[i*9 + 8] = ~(^b_wdata_i[i*8 +: 8]);
b_wmask_d[i*9 + 8] = &b_wmask_i[i*8 +: 8];
// parity decoding (errors are always uncorrectable)
a_rerror_d[1] |= ~(^{a_rdata_sram[i*9 +: 8], a_rdata_sram[i*9 + 8]});
b_rerror_d[1] |= ~(^{b_rdata_sram[i*9 +: 8], b_rdata_sram[i*9 + 8]});
end
end
end else begin : gen_nosecded_noparity
assign a_wmask_d = a_wmask_i;
assign b_wmask_d = b_wmask_i;
assign a_wdata_d = a_wdata_i;
assign b_wdata_d = b_wdata_i;
assign a_rdata_d = a_rdata_sram[0+:Width];
assign b_rdata_d = b_rdata_sram[0+:Width];
assign a_rerror_d = '0;
assign b_rerror_d = '0;
end
assign a_rvalid_d = a_rvalid_sram_q;
assign b_rvalid_d = b_rvalid_sram_q;
/////////////////////////////////////
// Input/Output Pipeline Registers //
/////////////////////////////////////
if (EnableInputPipeline) begin : gen_regslice_input
// Put the register slices between ECC encoding to SRAM port
always_ff @(posedge clk_a_i or negedge rst_a_ni) begin
if (!rst_a_ni) begin
a_req_q <= '0;
a_write_q <= '0;
a_addr_q <= '0;
a_wdata_q <= '0;
a_wmask_q <= '0;
end else begin
a_req_q <= a_req_d;
a_write_q <= a_write_d;
a_addr_q <= a_addr_d;
a_wdata_q <= a_wdata_d;
a_wmask_q <= a_wmask_d;
end
end
always_ff @(posedge clk_b_i or negedge rst_b_ni) begin
if (!rst_b_ni) begin
b_req_q <= '0;
b_write_q <= '0;
b_addr_q <= '0;
b_wdata_q <= '0;
b_wmask_q <= '0;
end else begin
b_req_q <= b_req_d;
b_write_q <= b_write_d;
b_addr_q <= b_addr_d;
b_wdata_q <= b_wdata_d;
b_wmask_q <= b_wmask_d;
end
end
end else begin : gen_dirconnect_input
assign a_req_q = a_req_d;
assign a_write_q = a_write_d;
assign a_addr_q = a_addr_d;
assign a_wdata_q = a_wdata_d;
assign a_wmask_q = a_wmask_d;
assign b_req_q = b_req_d;
assign b_write_q = b_write_d;
assign b_addr_q = b_addr_d;
assign b_wdata_q = b_wdata_d;
assign b_wmask_q = b_wmask_d;
end
if (EnableOutputPipeline) begin : gen_regslice_output
// Put the register slices between ECC decoding to output
always_ff @(posedge clk_a_i or negedge rst_a_ni) begin
if (!rst_a_ni) begin
a_rvalid_q <= '0;
a_rdata_q <= '0;
a_rerror_q <= '0;
end else begin
a_rvalid_q <= a_rvalid_d;
a_rdata_q <= a_rdata_d;
// tie to zero if the read data is not valid
a_rerror_q <= a_rerror_d & {2{a_rvalid_d}};
end
end
always_ff @(posedge clk_b_i or negedge rst_b_ni) begin
if (!rst_b_ni) begin
b_rvalid_q <= '0;
b_rdata_q <= '0;
b_rerror_q <= '0;
end else begin
b_rvalid_q <= b_rvalid_d;
b_rdata_q <= b_rdata_d;
// tie to zero if the read data is not valid
b_rerror_q <= b_rerror_d & {2{b_rvalid_d}};
end
end
end else begin : gen_dirconnect_output
assign a_rvalid_q = a_rvalid_d;
assign a_rdata_q = a_rdata_d;
// tie to zero if the read data is not valid
assign a_rerror_q = a_rerror_d & {2{a_rvalid_d}};
assign b_rvalid_q = b_rvalid_d;
assign b_rdata_q = b_rdata_d;
// tie to zero if the read data is not valid
assign b_rerror_q = b_rerror_d & {2{b_rvalid_d}};
end
endmodule : prim_ram_2p_async_adv