| // Copyright lowRISC contributors. |
| // Licensed under the Apache License, Version 2.0, see LICENSE for details. |
| // SPDX-License-Identifier: Apache-2.0 |
| |
| module prim_generic_otp |
| import prim_otp_pkg::*; |
| #( |
| // Native OTP word size. This determines the size_i granule. |
| parameter int Width = 16, |
| parameter int Depth = 1024, |
| // This determines the maximum number of native words that |
| // can be transferred accross the interface in one cycle. |
| parameter int SizeWidth = 2, |
| // Width of the power sequencing signal. |
| parameter int PwrSeqWidth = 2, |
| // Number of Test TL-UL words |
| parameter int TlDepth = 16, |
| // Width of vendor-specific test control signal |
| parameter int TestCtrlWidth = 32, |
| parameter int TestStatusWidth = 32, |
| parameter int TestVectWidth = 8, |
| // Derived parameters |
| localparam int AddrWidth = prim_util_pkg::vbits(Depth), |
| localparam int IfWidth = 2**SizeWidth*Width, |
| // VMEM file to initialize the memory with |
| parameter MemInitFile = "", |
| // Vendor test partition offset and size (both in bytes) |
| parameter int VendorTestOffset, |
| parameter int VendorTestSize |
| ) ( |
| input clk_i, |
| input rst_ni, |
| // Macro-specific power sequencing signals to/from AST |
| output logic [PwrSeqWidth-1:0] pwr_seq_o, |
| input [PwrSeqWidth-1:0] pwr_seq_h_i, |
| // External programming voltage |
| inout wire ext_voltage_io, |
| // Test interfaces |
| input [TestCtrlWidth-1:0] test_ctrl_i, |
| output logic [TestStatusWidth-1:0] test_status_o, |
| output logic [TestVectWidth-1:0] test_vect_o, |
| input tlul_pkg::tl_h2d_t test_tl_i, |
| output tlul_pkg::tl_d2h_t test_tl_o, |
| // Other DFT signals |
| input lc_ctrl_pkg::lc_tx_t scanmode_i, // Scan Mode input |
| input scan_en_i, // Scan Shift |
| input scan_rst_ni, // Scan Reset |
| // Alert indication |
| output ast_pkg::ast_dif_t otp_alert_src_o, |
| // Ready valid handshake for read/write command |
| output logic ready_o, |
| input valid_i, |
| input [SizeWidth-1:0] size_i, // #(Native words)-1, e.g. size == 0 for 1 native word. |
| input cmd_e cmd_i, // 00: read command, 01: write command, 11: init command |
| input [AddrWidth-1:0] addr_i, |
| input [IfWidth-1:0] wdata_i, |
| // Response channel |
| output logic valid_o, |
| output logic [IfWidth-1:0] rdata_o, |
| output err_e err_o |
| ); |
| |
| // This is only restricted by the supported ECC poly further |
| // below, and is straightforward to extend, if needed. |
| localparam int EccWidth = 6; |
| `ASSERT_INIT(SecDecWidth_A, Width == 16) |
| |
| // Not supported in open-source emulation model. |
| logic [PwrSeqWidth-1:0] unused_pwr_seq_h; |
| assign unused_pwr_seq_h = pwr_seq_h_i; |
| assign pwr_seq_o = '0; |
| |
| wire unused_ext_voltage; |
| assign unused_ext_voltage = ext_voltage_io; |
| logic unused_test_ctrl_i; |
| assign unused_test_ctrl_i = ^test_ctrl_i; |
| |
| logic unused_scan; |
| assign unused_scan = ^{scanmode_i, scan_en_i, scan_rst_ni}; |
| |
| assign otp_alert_src_o = '{p: '0, n: '1}; |
| |
| assign test_vect_o = '0; |
| assign test_status_o = '0; |
| |
| //////////////////////////////////// |
| // TL-UL Test Interface Emulation // |
| //////////////////////////////////// |
| |
| // Put down a register that can be used to test the TL interface. |
| // TODO: this emulation may need to be adjusted, once closed source wrapper is |
| // implemented. |
| localparam int TlAddrWidth = prim_util_pkg::vbits(TlDepth); |
| logic tlul_req, tlul_rvalid_q, tlul_wren; |
| logic [TlDepth-1:0][31:0] tlul_regfile_q; |
| logic [31:0] tlul_wdata, tlul_rdata_q; |
| logic [TlAddrWidth-1:0] tlul_addr; |
| |
| tlul_adapter_sram #( |
| .SramAw ( TlAddrWidth ), |
| .SramDw ( 32 ), |
| .Outstanding ( 1 ), |
| .ByteAccess ( 0 ), |
| .ErrOnWrite ( 0 ) |
| ) u_tlul_adapter_sram ( |
| .clk_i, |
| .rst_ni, |
| .tl_i ( test_tl_i ), |
| .tl_o ( test_tl_o ), |
| .en_ifetch_i ( tlul_pkg::InstrDis ), |
| .req_o ( tlul_req ), |
| .gnt_i ( tlul_req ), |
| .we_o ( tlul_wren ), |
| .addr_o ( tlul_addr ), |
| .wdata_o ( tlul_wdata ), |
| .wmask_o ( ), |
| .intg_error_o( ), |
| .rdata_i ( tlul_rdata_q ), |
| .rvalid_i ( tlul_rvalid_q ), |
| .rerror_i ( '0 ), |
| .req_type_o ( ) |
| ); |
| |
| always_ff @(posedge clk_i or negedge rst_ni) begin : p_tlul_testreg |
| if (!rst_ni) begin |
| tlul_rvalid_q <= 1'b0; |
| tlul_rdata_q <= '0; |
| tlul_regfile_q <= '0; |
| end else begin |
| tlul_rvalid_q <= tlul_req & ~tlul_wren; |
| if (tlul_req && tlul_wren) begin |
| tlul_regfile_q[tlul_addr] <= tlul_wdata; |
| end else if (tlul_req && !tlul_wren) begin |
| tlul_rdata_q <= tlul_regfile_q[tlul_addr]; |
| end |
| end |
| end |
| |
| /////////////////// |
| // Control logic // |
| /////////////////// |
| |
| // Encoding generated with ./sparse-fsm-encode.py -d 5 -m 8 -n 10 |
| // Hamming distance histogram: |
| // |
| // 0: -- |
| // 1: -- |
| // 2: -- |
| // 3: -- |
| // 4: -- |
| // 5: |||||||||||||||||||| (53.57%) |
| // 6: ||||||||||||| (35.71%) |
| // 7: | (3.57%) |
| // 8: || (7.14%) |
| // 9: -- |
| // 10: -- |
| // |
| // Minimum Hamming distance: 5 |
| // Maximum Hamming distance: 8 |
| // |
| localparam int StateWidth = 10; |
| typedef enum logic [StateWidth-1:0] { |
| ResetSt = 10'b1100000011, |
| InitSt = 10'b1100110100, |
| IdleSt = 10'b1010111010, |
| ReadSt = 10'b0011100000, |
| ReadWaitSt = 10'b1001011111, |
| WriteCheckSt = 10'b0111010101, |
| WriteWaitSt = 10'b0000001100, |
| WriteSt = 10'b0110101111 |
| } state_e; |
| |
| state_e state_d, state_q; |
| err_e err_d, err_q; |
| logic valid_d, valid_q; |
| logic req, wren, rvalid; |
| logic [1:0] rerror; |
| logic [AddrWidth-1:0] addr_q; |
| logic [SizeWidth-1:0] size_q; |
| logic [SizeWidth-1:0] cnt_d, cnt_q; |
| logic cnt_clr, cnt_en; |
| logic read_ecc_on; |
| logic wdata_inconsistent; |
| |
| |
| assign cnt_d = (cnt_clr) ? '0 : |
| (cnt_en) ? cnt_q + 1'b1 : cnt_q; |
| |
| assign valid_o = valid_q; |
| assign err_o = err_q; |
| |
| always_comb begin : p_fsm |
| // Default |
| state_d = state_q; |
| ready_o = 1'b0; |
| valid_d = 1'b0; |
| err_d = err_q; |
| req = 1'b0; |
| wren = 1'b0; |
| cnt_clr = 1'b0; |
| cnt_en = 1'b0; |
| read_ecc_on = 1'b1; |
| |
| unique case (state_q) |
| // Wait here until we receive an initialization command. |
| ResetSt: begin |
| err_d = NoError; |
| ready_o = 1'b1; |
| if (valid_i) begin |
| if (cmd_i == Init) begin |
| state_d = InitSt; |
| end |
| end |
| end |
| // Wait for some time until the OTP macro is ready. |
| InitSt: begin |
| state_d = IdleSt; |
| valid_d = 1'b1; |
| err_d = NoError; |
| end |
| // In the idle state, we basically wait for read or write commands. |
| IdleSt: begin |
| ready_o = 1'b1; |
| err_d = NoError; |
| if (valid_i) begin |
| cnt_clr = 1'b1; |
| err_d = NoError; |
| unique case (cmd_i) |
| Read: state_d = ReadSt; |
| Write: state_d = WriteCheckSt; |
| default: ; |
| endcase // cmd_i |
| end |
| end |
| // Issue a read command to the macro. |
| ReadSt: begin |
| state_d = ReadWaitSt; |
| req = 1'b1; |
| end |
| // Wait for response from macro. |
| ReadWaitSt: begin |
| if (rvalid) begin |
| cnt_en = 1'b1; |
| // Uncorrectable error, bail out. |
| if (rerror[1]) begin |
| state_d = IdleSt; |
| valid_d = 1'b1; |
| err_d = MacroEccUncorrError; |
| end else begin |
| if (cnt_q == size_q) begin |
| state_d = IdleSt; |
| valid_d = 1'b1; |
| end else begin |
| state_d = ReadSt; |
| end |
| // Correctable error, carry on but signal back. |
| if (rerror[0]) begin |
| err_d = MacroEccCorrError; |
| end |
| end |
| end |
| end |
| // First, read out to perform the write blank check and |
| // read-modify-write operation. |
| WriteCheckSt: begin |
| state_d = WriteWaitSt; |
| req = 1'b1; |
| // Register raw memory contents without correction |
| read_ecc_on = 1'b0; |
| end |
| // Wait for readout to complete first. |
| WriteWaitSt: begin |
| // Register raw memory contents without correction |
| read_ecc_on = 1'b0; |
| if (rvalid) begin |
| cnt_en = 1'b1; |
| |
| if (cnt_q == size_q) begin |
| cnt_clr = 1'b1; |
| state_d = WriteSt; |
| end else begin |
| state_d = WriteCheckSt; |
| end |
| end |
| end |
| // If the write data attempts to clear an already programmed bit, |
| // the MacroWriteBlankError needs to be asserted. |
| WriteSt: begin |
| req = 1'b1; |
| wren = 1'b1; |
| cnt_en = 1'b1; |
| if (wdata_inconsistent) begin |
| err_d = MacroWriteBlankError; |
| end |
| |
| if (cnt_q == size_q) begin |
| valid_d = 1'b1; |
| state_d = IdleSt; |
| end |
| end |
| default: begin |
| state_d = ResetSt; |
| end |
| endcase // state_q |
| end |
| |
| /////////////////////////////////////////// |
| // Emulate using ECC protected Block RAM // |
| /////////////////////////////////////////// |
| |
| logic [AddrWidth-1:0] addr; |
| assign addr = addr_q + AddrWidth'(cnt_q); |
| |
| logic [Width-1:0] rdata_corr; |
| logic [Width+EccWidth-1:0] rdata_d, wdata_ecc, rdata_ecc, wdata_rmw; |
| logic [2**SizeWidth-1:0][Width-1:0] wdata_q, rdata_reshaped; |
| logic [2**SizeWidth-1:0][Width+EccWidth-1:0] rdata_q; |
| |
| // Use a standard Hamming ECC for OTP. |
| prim_secded_hamming_22_16_enc u_enc ( |
| .data_i(wdata_q[cnt_q]), |
| .data_o(wdata_ecc) |
| ); |
| |
| prim_secded_hamming_22_16_dec u_dec ( |
| .data_i (rdata_ecc), |
| .data_o (rdata_corr), |
| .syndrome_o ( ), |
| .err_o (rerror) |
| ); |
| |
| assign rdata_d = (read_ecc_on) ? {{EccWidth{1'b0}}, rdata_corr} |
| : rdata_ecc; |
| |
| // Read-modify-write (OTP can only set bits to 1, but not clear to 0). |
| assign wdata_rmw = wdata_ecc | rdata_q[cnt_q]; |
| // This indicates if the write data is inconsistent (i.e., if the operation attempts to |
| // clear an already programmed bit to zero). |
| assign wdata_inconsistent = (rdata_q[cnt_q] & wdata_ecc) != rdata_q[cnt_q]; |
| |
| // Output data without ECC bits. |
| always_comb begin : p_output_map |
| for (int k = 0; k < 2**SizeWidth; k++) begin |
| rdata_reshaped[k] = rdata_q[k][Width-1:0]; |
| end |
| rdata_o = rdata_reshaped; |
| end |
| |
| prim_ram_1p_adv #( |
| .Depth (Depth), |
| .Width (Width + EccWidth), |
| .MemInitFile (MemInitFile), |
| .EnableInputPipeline (1), |
| .EnableOutputPipeline (1) |
| ) u_prim_ram_1p_adv ( |
| .clk_i, |
| .rst_ni, |
| .req_i ( req ), |
| .write_i ( wren ), |
| .addr_i ( addr ), |
| .wdata_i ( wdata_rmw ), |
| .wmask_i ( {Width+EccWidth{1'b1}} ), |
| .rdata_o ( rdata_ecc ), |
| .rvalid_o ( rvalid ), |
| .rerror_o ( ), |
| .cfg_i ( '0 ) |
| ); |
| |
| // Currently it is assumed that no wrap arounds can occur. |
| `ASSERT(NoWrapArounds_A, req |-> (addr >= addr_q)) |
| |
| ////////// |
| // Regs // |
| ////////// |
| |
| // This primitive is used to place a size-only constraint on the |
| // flops in order to prevent FSM state encoding optimizations. |
| logic [StateWidth-1:0] state_raw_q; |
| assign state_q = state_e'(state_raw_q); |
| prim_flop #( |
| .Width(StateWidth), |
| .ResetValue(StateWidth'(ResetSt)) |
| ) u_state_regs ( |
| .clk_i, |
| .rst_ni, |
| .d_i ( state_d ), |
| .q_o ( state_raw_q ) |
| ); |
| |
| always_ff @(posedge clk_i or negedge rst_ni) begin : p_regs |
| if (!rst_ni) begin |
| valid_q <= '0; |
| err_q <= NoError; |
| addr_q <= '0; |
| wdata_q <= '0; |
| rdata_q <= '0; |
| cnt_q <= '0; |
| size_q <= '0; |
| end else begin |
| valid_q <= valid_d; |
| err_q <= err_d; |
| cnt_q <= cnt_d; |
| if (ready_o && valid_i) begin |
| addr_q <= addr_i; |
| wdata_q <= wdata_i; |
| size_q <= size_i; |
| end |
| if (rvalid) begin |
| rdata_q[cnt_q] <= rdata_d; |
| end |
| end |
| end |
| |
| //////////////// |
| // Assertions // |
| //////////////// |
| |
| // Check that the otp_ctrl FSMs only issue legal commands to the wrapper. |
| `ASSERT(CheckCommands0_A, state_q == ResetSt && valid_i && ready_o |-> cmd_i == Init) |
| `ASSERT(CheckCommands1_A, state_q != ResetSt && valid_i && ready_o |-> cmd_i inside {Read, Write}) |
| |
| |
| endmodule : prim_generic_otp |