| // 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 |