[sram_ctrl] Add first cut implementation

Signed-off-by: Michael Schaffner <msf@opentitan.org>
diff --git a/hw/ip/lc_ctrl/rtl/lc_ctrl_pkg.sv b/hw/ip/lc_ctrl/rtl/lc_ctrl_pkg.sv
index fd608d8..82b6ca7 100644
--- a/hw/ip/lc_ctrl/rtl/lc_ctrl_pkg.sv
+++ b/hw/ip/lc_ctrl/rtl/lc_ctrl_pkg.sv
@@ -43,9 +43,10 @@
   // Typedefs for LC Interfaces //
   ////////////////////////////////
 
-  typedef enum logic [2:0] {
-    On  = 3'b101,
-    Off = 3'b000
+  parameter int TxWidth = 4;
+  typedef enum logic [TxWidth-1:0] {
+    On  = 4'b1010,
+    Off = 4'b0101
   } lc_tx_e;
 
   typedef struct packed {
diff --git a/hw/ip/otp_ctrl/data/otp_ctrl.hjson b/hw/ip/otp_ctrl/data/otp_ctrl.hjson
index 14110a7..2c9cdde 100644
--- a/hw/ip/otp_ctrl/data/otp_ctrl.hjson
+++ b/hw/ip/otp_ctrl/data/otp_ctrl.hjson
@@ -154,21 +154,21 @@
       type:    "uni"
       name:    "lc_escalate_en"
       act:     "rcv"
-      default: "'0"
+      default: "lc_ctrl_pkg::Off"
       package: "lc_ctrl_pkg"
     }
     { struct:  "lc_tx"
       type:    "uni"
       name:    "lc_provision_en"
       act:     "rcv"
-      default: "'0"
+      default: "lc_ctrl_pkg::Off"
       package: "lc_ctrl_pkg"
     }
     { struct:  "lc_tx"
       type:    "uni"
       name:    "lc_dft_en"
       act:     "rcv"
-      default: "'0"
+      default: "lc_ctrl_pkg::Off"
       package: "lc_ctrl_pkg"
     }
     // Broadcast to Key Manager
diff --git a/hw/ip/prim/rtl/prim_prince.sv b/hw/ip/prim/rtl/prim_prince.sv
index b8744a7..97b075d 100644
--- a/hw/ip/prim/rtl/prim_prince.sv
+++ b/hw/ip/prim/rtl/prim_prince.sv
@@ -16,7 +16,8 @@
 // References: - https://en.wikipedia.org/wiki/PRESENT
 //             - https://en.wikipedia.org/wiki/Prince_(cipher)
 //             - http://www.lightweightcrypto.org/present/present_ches2007.pdf
-//             - https://csrc.nist.gov/csrc/media/events/lightweight-cryptography-workshop-2015/documents/papers/session7-maene-paper.pdf
+//             - https://csrc.nist.gov/csrc/media/events/lightweight-cryptography-workshop-2015/
+//               documents/papers/session7-maene-paper.pdf
 //             - https://eprint.iacr.org/2012/529.pdf
 //             - https://eprint.iacr.org/2015/372.pdf
 //             - https://eprint.iacr.org/2014/656.pdf
diff --git a/hw/ip/prim/rtl/prim_ram_1p_scr.sv b/hw/ip/prim/rtl/prim_ram_1p_scr.sv
index 600784d..39a646e 100644
--- a/hw/ip/prim/rtl/prim_ram_1p_scr.sv
+++ b/hw/ip/prim/rtl/prim_ram_1p_scr.sv
@@ -19,9 +19,6 @@
 // that nonce, the address mapping is not fully baked into RTL and can be changed at runtime as
 // well.
 //
-// Note that this design is not final nor tested, and its main purpose is to be instantiated in
-// Bronze for area and timing estimates.
-//
 // See also: prim_cipher_pkg, prim_prince
 
 `include "prim_assert.sv"
@@ -30,7 +27,7 @@
   parameter  int Depth                = 512, // Needs to be a power of 2 if NumAddrScrRounds > 0.
   parameter  int Width                = 256, // Needs to be Byte aligned for parity
   parameter  int DataBitsPerMask      = 8,   // Currently only 8 is supported
-  parameter  int CfgW                 = 8,   // WTC, RTC, etc
+  parameter  int CfgWidth             = 8,   // WTC, RTC, etc
 
   // Scrambling parameters. Note that this needs to be low-latency, hence we have to keep the
   // amount of cipher rounds low. PRINCE has 5 half rounds in its original form, which corresponds
@@ -63,6 +60,7 @@
   input        [DataKeyWidth-1:0]   key_i,
   input        [NonceWidth-1:0]     nonce_i,
 
+  // Interface to TL-UL SRAM adapter
   input                             req_i,
   input                             write_i,
   input        [AddrWidth-1:0]      addr_i,
@@ -71,9 +69,10 @@
   output logic [Width-1:0]          rdata_o,
   output logic                      rvalid_o, // Read response (rdata_o) is valid
   output logic [1:0]                rerror_o, // Bit1: Uncorrectable, Bit0: Correctable
+  output logic [AddrWidth-1:0]      raddr_o,  // Read address for error reporting.
 
   // config
-  input [CfgW-1:0]                  cfg_i
+  input [CfgWidth-1:0]              cfg_i
 );
 
   //////////////////////
@@ -146,6 +145,11 @@
     assign addr_scr = addr_mux;
   end
 
+  // We latch the non-scrambled address for error reporting.
+  logic [AddrWidth-1:0] raddr_d, raddr_q;
+  assign raddr_d = addr_mux;
+  assign raddr_o = raddr_q;
+
   //////////////////////////////////////////////
   // Keystream Generation for Data Scrambling //
   //////////////////////////////////////////////
@@ -176,6 +180,13 @@
       .data_o  ( keystream[k * 64 +: 64] ),
       .valid_o ( )
     );
+
+    // Unread unused bits from keystream
+    if (k == NumParKeystr-1 && (Width % 64) > 0) begin : gen_unread_last
+      localparam int UnusedWidth = 64 - (Width % 64);
+      logic [UnusedWidth-1:0] unused_keystream;
+      assign unused_keystream = keystream[(k+1) * 64 - 1 -: UnusedWidth];
+    end
   end
 
   // Replicate keystream if needed
@@ -197,7 +208,7 @@
 
   // Write path. Note that since this does not fan out into the interconnect, the write path is not
   // as critical as the read path below in terms of timing.
-  logic [Width-1:0] wdata_scr_d, wdata_scr_q;
+  logic [Width-1:0] wdata_scr_d, wdata_scr_q, wdata_q;
   for (genvar k = 0; k < Width/8; k++) begin : gen_diffuse_wdata
     // Apply the keystream first
     logic [7:0] wdata_xor;
@@ -277,7 +288,6 @@
   // Registers //
   ///////////////
 
-  logic [Width-1:0] wdata_q;
   logic [Width-1:0] wmask_q;
   always_ff @(posedge clk_i or negedge rst_ni) begin : p_wdata_buf
     if (!rst_ni) begin
@@ -289,11 +299,13 @@
       wdata_q             <= '0;
       wdata_scr_q         <= '0;
       wmask_q             <= '0;
+      raddr_q             <= '0;
     end else begin
       write_scr_pending_q <= write_scr_pending_d;
       write_pending_q     <= write_pending_d;
       collision_q         <= collision_d;
       rvalid_q            <= read_en;
+      raddr_q             <= raddr_d;
       if (write_en) begin
         waddr_q <= addr_i;
         wmask_q <= wmask_i;
@@ -313,7 +325,7 @@
     .Depth(Depth),
     .Width(Width),
     .DataBitsPerMask(DataBitsPerMask),
-    .CfgW(CfgW),
+    .CfgW(CfgWidth),
     .EnableECC(1'b0),
     .EnableParity(1'b1), // We are using Byte parity
     .EnableInputPipeline(1'b0),
diff --git a/hw/ip/prim/rtl/prim_subst_perm.sv b/hw/ip/prim/rtl/prim_subst_perm.sv
index a882dff..5f96f3a 100644
--- a/hw/ip/prim/rtl/prim_subst_perm.sv
+++ b/hw/ip/prim/rtl/prim_subst_perm.sv
@@ -42,7 +42,7 @@
         end
         // Flip vector
         for (int k = 0; k < DataWidth; k++) begin
-          data_state_flipped[DataWidth - 1 - k] = data_state_sbox[k];
+          data_state_sbox[DataWidth - 1 - k] = data_state_flipped[k];
         end
         // Inverse SBox layer
         for (int k = 0; k < DataWidth/4; k++) begin
@@ -66,7 +66,7 @@
           data_state_flipped[DataWidth - 1 - k] = data_state_sbox[k];
         end
         // Regroup bits such that all even indices are stacked up first, followed by all odd
-        // indices, and then flip the vector. Note that if the Width is odd, this is still ok, since
+        // indices. Note that if the Width is odd, this is still ok, since
         // the uppermost bit just stays in place in that case.
         for (int k = 0; k < DataWidth/2; k++) begin
           data_state_sbox[k]               = data_state_flipped[k * 2];
diff --git a/hw/ip/sram_ctrl/lint/sram_ctrl.vlt b/hw/ip/sram_ctrl/lint/sram_ctrl.vlt
new file mode 100644
index 0000000..901b4bd
--- /dev/null
+++ b/hw/ip/sram_ctrl/lint/sram_ctrl.vlt
@@ -0,0 +1,6 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+//
+// waiver file for SRAM controller
+
diff --git a/hw/ip/sram_ctrl/lint/sram_ctrl.waiver b/hw/ip/sram_ctrl/lint/sram_ctrl.waiver
new file mode 100644
index 0000000..0282f17
--- /dev/null
+++ b/hw/ip/sram_ctrl/lint/sram_ctrl.waiver
@@ -0,0 +1,6 @@
+# Copyright lowRISC contributors.
+# Licensed under the Apache License, Version 2.0, see LICENSE for details.
+# SPDX-License-Identifier: Apache-2.0
+#
+# waiver file for SRAM controller
+
diff --git a/hw/ip/sram_ctrl/rtl/sram_ctrl.sv b/hw/ip/sram_ctrl/rtl/sram_ctrl.sv
new file mode 100644
index 0000000..1bfc665
--- /dev/null
+++ b/hw/ip/sram_ctrl/rtl/sram_ctrl.sv
@@ -0,0 +1,286 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+//
+// SRAM controller.
+//
+
+`include "prim_assert.sv"
+
+module sram_ctrl
+  import sram_ctrl_reg_pkg::*;
+#(
+  parameter  int Depth                = 512, // Needs to be a power of 2 if NumAddrScrRounds > 0.
+  parameter  int Width                = 32,  // Needs to be Byte aligned for parity
+  parameter  int CfgWidth             = 8,   // WTC, RTC, etc
+  // Scrambling parameters. Note that this needs to be low-latency, hence we have to keep the
+  // amount of cipher rounds low. PRINCE has 5 half rounds in its original form, which corresponds
+  // to 2*5 + 1 effective rounds. Setting this to 2 halves this to approximately 5 effective rounds.
+  parameter  int NumPrinceRoundsHalf  = 2,   // Number of PRINCE half rounds, can be [1..5]
+  // Number of extra intra-Byte diffusion rounds. Setting this to 0 disables intra-Byte diffusion.
+  parameter  int NumByteScrRounds     = 2,
+  // Number of address scrambling rounds. Setting this to 0 disables address scrambling.
+  parameter  int NumAddrScrRounds     = 2,
+  // Derived parameters
+  localparam int AddrWidth            = prim_util_pkg::vbits(Depth)
+) (
+  // SRAM Clock
+  input                                   clk_i,
+  input                                   rst_ni,
+  // OTP Clock (for key interface)
+  input                                   clk_otp_i,
+  input                                   rst_otp_ni,
+  // Bus Interface (device) for CSRs
+  input  tlul_pkg::tl_h2d_t               tl_i,
+  output tlul_pkg::tl_d2h_t               tl_o,
+  // Bus Interface (device) for SRAM
+  input  tlul_pkg::tl_h2d_t               sram_tl_i,
+  output tlul_pkg::tl_d2h_t               sram_tl_o,
+  // Interrupt Requests
+  output logic                            sram_integ_error_o,
+  // Life-cycle escalation input (scraps the scrambling keys)
+  input  lc_ctrl_pkg::lc_tx_t             lc_escalate_en_i,
+  // Key request to OTP (running on clk_fixed)
+  output otp_ctrl_pkg::sram_otp_key_req_t sram_otp_key_o,
+  input  otp_ctrl_pkg::sram_otp_key_rsp_t sram_otp_key_i
+);
+
+  // This peripheral only works up to a width of 64bits.
+  `ASSERT_INIT(WidthMustBeBelow64_A, Width <= 64)
+
+  //////////////////////////
+  // CSR Node and Mapping //
+  //////////////////////////
+
+  sram_ctrl_reg_pkg::sram_ctrl_reg2hw_t    reg2hw;
+  sram_ctrl_reg_pkg::sram_ctrl_hw2reg_t    hw2reg;
+
+  sram_ctrl_reg_top u_reg (
+    .clk_i,
+    .rst_ni,
+    .tl_i,
+    .tl_o,
+    .reg2hw,
+    .hw2reg,
+    .devmode_i (1'b1)
+  );
+
+  // Key and attribute outputs to scrambling device
+  logic [otp_ctrl_pkg::SramKeyWidth-1:0]   key_d, key_q;
+  logic [otp_ctrl_pkg::SramNonceWidth-1:0] nonce_d, nonce_q;
+  logic [otp_ctrl_pkg::SramKeyWidth-1:0]   sram_scr_key;
+  logic [otp_ctrl_pkg::SramNonceWidth-1:0] sram_scr_nonce;
+  logic [CfgWidth-1:0]                     sram_scr_cfg;
+  assign sram_scr_key    = key_q;
+  assign sram_scr_nonce  = nonce_q;
+  assign sram_scr_cfg    = reg2hw.sram_cfg.q;
+
+  // Status register outputs
+  logic [AddrWidth-1:0] sram_scr_raddr;
+  logic key_valid_d, key_valid_q;
+  logic key_seed_valid_d, key_seed_valid_q;
+  // Flag an error if any of the error bits is set.
+  assign hw2reg.status.error              = |reg2hw.error_type;
+  assign hw2reg.status.scr_key_valid      = key_valid_q;
+  assign hw2reg.status.scr_key_seed_valid = key_seed_valid_q;
+
+  // Control register
+  logic key_req;
+  assign key_req = reg2hw.ctrl.q & reg2hw.ctrl.qe;
+
+  // Integrity errors
+  logic [1:0] sram_rerror;
+  assign hw2reg.error_type.correctable.d    = sram_rerror[0];
+  assign hw2reg.error_type.correctable.de   = sram_rerror[0];
+  assign hw2reg.error_type.uncorrectable.d  = sram_rerror[1];
+  assign hw2reg.error_type.uncorrectable.de = sram_rerror[1];
+  assign hw2reg.error_address.d             = 32'(sram_scr_raddr);
+  assign hw2reg.error_address.de            = |sram_rerror;
+
+  ////////////////
+  // Interrupts //
+  ////////////////
+
+  prim_intr_hw #(
+    .Width(1)
+  ) u_intr_sram_integ_error (
+    .clk_i,
+    .rst_ni,
+    .event_intr_i           ( |sram_rerror         ),
+    .reg2hw_intr_enable_q_i ( reg2hw.intr_enable.q ),
+    .reg2hw_intr_test_q_i   ( reg2hw.intr_test.q   ),
+    .reg2hw_intr_test_qe_i  ( reg2hw.intr_test.qe  ),
+    .reg2hw_intr_state_q_i  ( reg2hw.intr_state.q  ),
+    .hw2reg_intr_state_de_o ( hw2reg.intr_state.de ),
+    .hw2reg_intr_state_d_o  ( hw2reg.intr_state.d  ),
+    .intr_o                 ( sram_integ_error_o   )
+  );
+
+  ///////////////////////////////////
+  // Lifecycle Escalation Receiver //
+  ///////////////////////////////////
+
+  lc_ctrl_pkg::lc_tx_t escalate_en;
+  prim_multibit_sync #(
+    .Width(lc_ctrl_pkg::TxWidth),
+    .NumChecks (2),
+    .ResetValue(lc_ctrl_pkg::TxWidth'(lc_ctrl_pkg::Off))
+  ) u_prim_multibit_sync (
+    .clk_i,
+    .rst_ni,
+    .data_i (lc_escalate_en_i),
+    .data_o (escalate_en)
+  );
+
+  ////////////////////////////
+  // Scrambling Key Request //
+  ////////////////////////////
+
+  // The scrambling key and nonce have to be requested from the OTP controller via a req/ack
+  // protocol. Since the OTP controller works in a different clock domain, we have to synchronize
+  // the req/ack protocol as described in more details here:
+  // https://docs.opentitan.org/hw/ip/otp_ctrl/doc/index.html#interfaces-to-sram-and-otbn-scramblers
+  logic key_ack;
+  logic key_req_pending_d, key_req_pending_q;
+  assign key_req_pending_d = (key_req) ? 1'b1 :
+                             (key_ack) ? 1'b0 : key_req_pending_q;
+
+  assign key_valid_d       = (key_req) ? 1'b0 :
+                             (key_ack) ? 1'b1 : key_valid_q;
+
+  assign key_d            = sram_otp_key_i.key;
+  assign nonce_d          = sram_otp_key_i.nonce;
+  assign key_seed_valid_d = sram_otp_key_i.seed_valid;
+
+  always_ff @(posedge clk_i or negedge rst_ni) begin : p_regs
+    if (!rst_ni) begin
+      key_req_pending_q <= 1'b0;
+      key_valid_q       <= 1'b0;
+      key_seed_valid_q  <= 1'b0;
+      key_q             <= '0;
+      nonce_q           <= '0;
+    end else begin
+      key_req_pending_q <= key_req_pending_d;
+      key_valid_q       <= key_valid_d;
+      if (key_ack) begin
+        key_seed_valid_q  <= key_seed_valid_d;
+        key_q             <= key_d;
+        nonce_q           <= nonce_d;
+      end
+      // This scraps the keys
+      if (escalate_en.state != lc_ctrl_pkg::Off) begin
+        key_seed_valid_q <= '0;
+        key_q            <= '0;
+        nonce_q          <= '0;
+      end
+    end
+  end
+
+  prim_sync_reqack u_prim_sync_reqack (
+    .clk_src_i  ( clk_i              ),
+    .rst_src_ni ( rst_ni             ),
+    .clk_dst_i  ( clk_otp_i          ),
+    .rst_dst_ni ( rst_otp_ni         ),
+    .src_req_i  ( key_req_pending_q  ),
+    .src_ack_o  ( key_ack            ),
+    .dst_req_o  ( sram_otp_key_o.req ),
+    .dst_ack_i  ( sram_otp_key_i.ack )
+  );
+
+  ////////////////////////////////
+  // Scrambled Memory Primitive //
+  ////////////////////////////////
+
+  logic tlul_req;
+  logic tlul_we;
+  logic [AddrWidth-1:0] tlul_addr;
+  logic [Width-1:0] tlul_wdata;
+  logic [Width-1:0] tlul_wmask;
+  logic [Width-1:0] tlul_rdata;
+  logic tlul_rvalid;
+  logic [1:0] tlul_rerror;
+
+  tlul_adapter_sram #(
+    .SramAw(AddrWidth),
+    .SramDw(Width)
+  ) u_tlul_adapter_sram (
+    .clk_i,
+    .rst_ni,
+    .tl_i     ( sram_tl_i   ),
+    .tl_o     ( sram_tl_o   ),
+    .req_o    ( tlul_req    ),
+    .gnt_i    ( 1'b1        ),
+    .we_o     ( tlul_we     ),
+    .addr_o   ( tlul_addr   ),
+    .wdata_o  ( tlul_wdata  ),
+    .wmask_o  ( tlul_wmask  ),
+    .rdata_i  ( tlul_rdata  ),
+    .rvalid_i ( tlul_rvalid ),
+    .rerror_i ( tlul_rerror )
+  );
+
+  logic sram_req;
+  logic sram_we;
+  logic [AddrWidth-1:0] sram_addr;
+  logic [Width-1:0] sram_wdata;
+  logic [Width-1:0] sram_wmask;
+  logic [Width-1:0] sram_rdata;
+  logic sram_rvalid;
+
+  prim_ram_1p_scr #(
+    .Depth               ( Depth               ),
+    .Width               ( Width               ),
+    .CfgWidth            ( CfgWidth            ),
+    .NumPrinceRoundsHalf ( NumPrinceRoundsHalf ),
+    .NumByteScrRounds    ( NumByteScrRounds    ),
+    .NumAddrScrRounds    ( NumAddrScrRounds    )
+  ) i_prim_ram_1p_scr (
+    .clk_i,
+    .rst_ni,
+    .key_i    ( sram_scr_key   ),
+    .nonce_i  ( sram_scr_nonce ),
+    .req_i    ( sram_req       ),
+    .write_i  ( sram_we        ),
+    .addr_i   ( sram_addr      ),
+    .wdata_i  ( sram_wdata     ),
+    .wmask_i  ( sram_wmask     ),
+    .rdata_o  ( sram_rdata     ),
+    .rvalid_o ( sram_rvalid    ),
+    .rerror_o ( sram_rerror    ),
+    .raddr_o  ( sram_scr_raddr ),
+    .cfg_i    ( sram_scr_cfg   )
+  );
+
+  // Do not forward requests to SRAM while key request is pending.
+  // Instead, we return a TLUL error.
+  assign sram_req    = ~key_req_pending_q & tlul_req;
+  assign sram_we     = ~key_req_pending_q & tlul_we;
+  assign sram_addr   = tlul_addr;
+  assign sram_wdata  = tlul_wdata;
+  assign sram_wmask  = tlul_wmask;
+
+  // Delay the key pending error by one cycle in order to match with the SRAM read latency.
+  logic  key_pending_error_d, key_pending_error_q;
+  assign key_pending_error_d = key_req_pending_q & tlul_req;
+  assign tlul_rdata  = (key_pending_error_q) ? '0    : sram_rdata;
+  assign tlul_rvalid = (key_pending_error_q) ? 1'b1  : sram_rvalid;
+  assign tlul_rerror = (key_pending_error_q) ? 2'b10 : sram_rerror; // signal as uncorrectable
+
+  always_ff @(posedge clk_i or negedge rst_ni) begin : p_error_reg
+    if (!rst_ni) begin
+      key_pending_error_q <= 1'b0;
+    end else begin
+      key_pending_error_q <= key_pending_error_d;
+    end
+  end
+
+  ////////////////
+  // Assertions //
+  ////////////////
+
+  `ASSERT_KNOWN(TlOutKnown_A,                tl_o)
+  `ASSERT_KNOWN(TlSramOutKnown_A,            sram_tl_o)
+  `ASSERT_KNOWN(IntrSramParityErrorKnown_A,  sram_integ_error_o)
+  `ASSERT_KNOWN(SramOtpKeyKnown_A,           sram_otp_key_o)
+
+endmodule : sram_ctrl
diff --git a/hw/ip/sram_ctrl/sram_ctrl.core b/hw/ip/sram_ctrl/sram_ctrl.core
new file mode 100644
index 0000000..45a216c
--- /dev/null
+++ b/hw/ip/sram_ctrl/sram_ctrl.core
@@ -0,0 +1,81 @@
+CAPI=2:
+# Copyright lowRISC contributors.
+# Licensed under the Apache License, Version 2.0, see LICENSE for details.
+# SPDX-License-Identifier: Apache-2.0
+name: "lowrisc:ip:sram_ctrl:0.1"
+description: "SRAM Controller"
+
+filesets:
+  files_rtl:
+    depend:
+      - lowrisc:prim:all
+      - lowrisc:prim:util
+      - lowrisc:prim:multibit_sync
+      - lowrisc:prim:ram_1p_scr
+      - lowrisc:tlul:adapter_sram
+      - lowrisc:ip:tlul
+      - lowrisc:ip:sram_ctrl_pkg
+      - lowrisc:ip:lc_ctrl_pkg
+      - lowrisc:ip:otp_ctrl_pkg
+    files:
+      - rtl/sram_ctrl_reg_top.sv
+      - rtl/sram_ctrl.sv
+    file_type: systemVerilogSource
+
+  files_verilator_waiver:
+    depend:
+      # common waivers
+      - lowrisc:lint:common
+      - lowrisc:lint:comportable
+    files:
+      - lint/sram_ctrl.vlt
+    file_type: vlt
+
+  files_ascentlint_waiver:
+    depend:
+      # common waivers
+      - lowrisc:lint:common
+      - lowrisc:lint:comportable
+    files:
+      - lint/sram_ctrl.waiver
+    file_type: waiver
+
+  files_formal:
+    files:
+      - dv/tb/sram_ctrl_bind.sv
+    file_type: systemVerilogSource
+
+parameters:
+  SYNTHESIS:
+    datatype: bool
+    paramtype: vlogdefine
+
+
+targets:
+  default: &default_target
+    filesets:
+      - tool_verilator  ? (files_verilator_waiver)
+      - tool_ascentlint ? (files_ascentlint_waiver)
+      - target_formal   ? (files_formal)
+      - files_rtl
+    toplevel: sram_ctrl
+
+  formal:
+    filesets:
+      - files_rtl
+      - files_formal
+    toplevel: sram_ctrl
+
+  lint:
+    <<: *default_target
+    default_tool: verilator
+    parameters:
+      - SYNTHESIS=true
+    tools:
+      ascentlint:
+        ascentlint_options:
+          - "-wait_license"
+      verilator:
+        mode: lint-only
+        verilator_options:
+          - "-Wall"
diff --git a/hw/ip/sram_ctrl/sram_ctrl_pkg.core b/hw/ip/sram_ctrl/sram_ctrl_pkg.core
new file mode 100644
index 0000000..1af89c5
--- /dev/null
+++ b/hw/ip/sram_ctrl/sram_ctrl_pkg.core
@@ -0,0 +1,21 @@
+CAPI=2:
+# Copyright lowRISC contributors.
+# Licensed under the Apache License, Version 2.0, see LICENSE for details.
+# SPDX-License-Identifier: Apache-2.0
+name: "lowrisc:ip:sram_ctrl_pkg:0.1"
+description: "SRAM Controller Package"
+filesets:
+  files_rtl:
+    depend:
+      - lowrisc:tlul:headers
+      - lowrisc:ip:otp_ctrl_pkg
+
+    files:
+      - rtl/sram_ctrl_reg_pkg.sv
+    file_type: systemVerilogSource
+
+
+targets:
+  default: &default_target
+    filesets:
+      - files_rtl
diff --git a/hw/top_earlgrey/data/autogen/top_earlgrey.gen.hjson b/hw/top_earlgrey/data/autogen/top_earlgrey.gen.hjson
index 203ddfa..418fad3 100644
--- a/hw/top_earlgrey/data/autogen/top_earlgrey.gen.hjson
+++ b/hw/top_earlgrey/data/autogen/top_earlgrey.gen.hjson
@@ -2741,7 +2741,7 @@
           type: uni
           name: lc_escalate_en
           act: rcv
-          default: "'0"
+          default: lc_ctrl_pkg::Off
           package: lc_ctrl_pkg
           inst_name: otp_ctrl
           index: -1
@@ -2751,7 +2751,7 @@
           type: uni
           name: lc_provision_en
           act: rcv
-          default: "'0"
+          default: lc_ctrl_pkg::Off
           package: lc_ctrl_pkg
           inst_name: otp_ctrl
           index: -1
@@ -2761,7 +2761,7 @@
           type: uni
           name: lc_dft_en
           act: rcv
-          default: "'0"
+          default: lc_ctrl_pkg::Off
           package: lc_ctrl_pkg
           inst_name: otp_ctrl
           index: -1
@@ -6308,7 +6308,7 @@
         type: uni
         name: lc_escalate_en
         act: rcv
-        default: "'0"
+        default: lc_ctrl_pkg::Off
         package: lc_ctrl_pkg
         inst_name: otp_ctrl
         index: -1
@@ -6318,7 +6318,7 @@
         type: uni
         name: lc_provision_en
         act: rcv
-        default: "'0"
+        default: lc_ctrl_pkg::Off
         package: lc_ctrl_pkg
         inst_name: otp_ctrl
         index: -1
@@ -6328,7 +6328,7 @@
         type: uni
         name: lc_dft_en
         act: rcv
-        default: "'0"
+        default: lc_ctrl_pkg::Off
         package: lc_ctrl_pkg
         inst_name: otp_ctrl
         index: -1
diff --git a/hw/top_earlgrey/lint/top_earlgrey_lint_cfgs.hjson b/hw/top_earlgrey/lint/top_earlgrey_lint_cfgs.hjson
index e91c2c8..c4841ec 100644
--- a/hw/top_earlgrey/lint/top_earlgrey_lint_cfgs.hjson
+++ b/hw/top_earlgrey/lint/top_earlgrey_lint_cfgs.hjson
@@ -151,6 +151,17 @@
                   import_cfgs: ["{proj_root}/hw/lint/data/common_lint_cfg.hjson"]
                   rel_path: "hw/ip/spi_device/lint/{tool}"
              },
+             {    name: sram_ctrl
+                  fusesoc_core: lowrisc:ip:sram_ctrl
+                  import_cfgs: ["{proj_root}/hw/lint/data/common_lint_cfg.hjson"]
+                  rel_path: "hw/ip/sram_ctrl/lint/{tool}"
+                  overrides: [
+                    {
+                      name: design_level
+                      value: "top"
+                    }
+                  ]
+             },
              {    name: uart
                   fusesoc_core: lowrisc:ip:uart
                   import_cfgs: ["{proj_root}/hw/lint/data/common_lint_cfg.hjson"]
diff --git a/hw/top_earlgrey/rtl/autogen/top_earlgrey.sv b/hw/top_earlgrey/rtl/autogen/top_earlgrey.sv
index 13c1df1..d413b46 100644
--- a/hw/top_earlgrey/rtl/autogen/top_earlgrey.sv
+++ b/hw/top_earlgrey/rtl/autogen/top_earlgrey.sv
@@ -1030,9 +1030,9 @@
       .lc_otp_token_i('0),
       .lc_otp_token_o(),
       .otp_lc_data_o(),
-      .lc_escalate_en_i('0),
-      .lc_provision_en_i('0),
-      .lc_dft_en_i('0),
+      .lc_escalate_en_i(lc_ctrl_pkg::Off),
+      .lc_provision_en_i(lc_ctrl_pkg::Off),
+      .lc_dft_en_i(lc_ctrl_pkg::Off),
       .otp_keymgr_key_o(),
       .flash_otp_key_i('0),
       .flash_otp_key_o(),