| // 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 #( |
| // Native OTP word size. This determines the size_i granule. |
| parameter int Width = 16, |
| parameter int Depth = 1024, |
| parameter int CmdWidth = otp_ctrl_pkg::OtpCmdWidth, |
| // This determines the maximum number of native words that |
| // can be transferred accross the interface in one cycle. |
| parameter int SizeWidth = otp_ctrl_pkg::OtpSizeWidth, |
| // Width of the power sequencing signal. |
| parameter int PwrSeqWidth = otp_ctrl_pkg::OtpPwrSeqWidth, |
| // Number of Test TL-UL words |
| parameter int TlDepth = 16, |
| // 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 = "" |
| ) ( |
| 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, |
| // Test interface |
| input tlul_pkg::tl_h2d_t test_tl_i, |
| output tlul_pkg::tl_d2h_t test_tl_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 [CmdWidth-1:0] 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 otp_ctrl_pkg::otp_err_e err_o |
| ); |
| |
| // 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; |
| |
| //////////////////////////////////// |
| // 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 ), |
| .req_o ( tlul_req ), |
| .gnt_i ( tlul_req ), |
| .we_o ( tlul_wren ), |
| .addr_o ( tlul_addr ), |
| .wdata_o ( tlul_wdata ), |
| .wmask_o ( ), |
| .rdata_i ( tlul_rdata_q ), |
| .rvalid_i ( tlul_rvalid_q ), |
| .rerror_i ( '0 ) |
| ); |
| |
| 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; |
| otp_ctrl_pkg::otp_err_e err_d, err_q; |
| logic valid_d, valid_q; |
| logic req, wren, rvalid; |
| logic [1:0] rerror; |
| logic [Width-1:0] rdata_d; |
| logic [2**SizeWidth-1:0][Width-1:0] rdata_q, wdata_q; |
| 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; |
| |
| assign cnt_d = (cnt_clr) ? '0 : |
| (cnt_en) ? cnt_q + 1'b1 : cnt_q; |
| |
| assign valid_o = valid_q; |
| assign rdata_o = rdata_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 = otp_ctrl_pkg::NoError; |
| req = 1'b0; |
| wren = 1'b0; |
| cnt_clr = 1'b0; |
| cnt_en = 1'b0; |
| |
| unique case (state_q) |
| // Wait here until we receive an initialization command. |
| ResetSt: begin |
| ready_o = 1'b1; |
| if (valid_i) begin |
| if (cmd_i == otp_ctrl_pkg::OtpInit) begin |
| state_d = InitSt; |
| end else begin |
| // Invalid commands get caught here |
| valid_d = 1'b1; |
| err_d = otp_ctrl_pkg::MacroError; |
| end |
| end |
| end |
| // Wait for some time until the OTP macro is ready. |
| InitSt: begin |
| state_d = IdleSt; |
| valid_d = 1'b1; |
| end |
| // In the idle state, we basically wait for read or write commands. |
| IdleSt: begin |
| ready_o = 1'b1; |
| if (valid_i) begin |
| cnt_clr = 1'b1; |
| err_d = otp_ctrl_pkg::NoError; |
| unique case (cmd_i) |
| otp_ctrl_pkg::OtpRead: state_d = ReadSt; |
| otp_ctrl_pkg::OtpWrite: state_d = WriteCheckSt; |
| default: begin |
| // Invalid commands get caught here |
| valid_d = 1'b1; |
| err_d = otp_ctrl_pkg::MacroError; |
| end |
| 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 = otp_ctrl_pkg::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 = otp_ctrl_pkg::MacroEccCorrError; |
| end |
| end |
| end |
| end |
| // First, perform a blank check. |
| WriteCheckSt: begin |
| state_d = WriteWaitSt; |
| req = 1'b1; |
| end |
| // Wait for readout to complete first. |
| // If the write data would clear an already programmed bit, or if we got an uncorrectable |
| // ECC error, the check has failed and we abort the write at this point. |
| WriteWaitSt: begin |
| if (rvalid) begin |
| cnt_en = 1'b1; |
| // TODO: this blank check needs to be extended to account for the ECC bits as well. |
| if (rerror[1] || (rdata_d & wdata_q[cnt_q]) != rdata_d) begin |
| state_d = IdleSt; |
| valid_d = 1'b1; |
| err_d = otp_ctrl_pkg::MacroWriteBlankError; |
| end else begin |
| if (cnt_q == size_q) begin |
| cnt_clr = 1'b1; |
| state_d = WriteSt; |
| end else begin |
| state_d = WriteCheckSt; |
| end |
| end |
| end |
| end |
| // Now that the write check was successful, we can write all native words in one go. |
| WriteSt: begin |
| req = 1'b1; |
| wren = 1'b1; |
| cnt_en = 1'b1; |
| 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); |
| |
| prim_ram_1p_adv #( |
| .Depth (Depth), |
| .Width (Width), |
| .MemInitFile (MemInitFile), |
| .EnableECC (1'b1), |
| .EnableInputPipeline (1), |
| .EnableOutputPipeline (1) |
| ) i_prim_ram_1p_adv ( |
| .clk_i, |
| .rst_ni, |
| .req_i ( req ), |
| .write_i ( wren ), |
| .addr_i ( addr ), |
| .wdata_i ( wdata_q[cnt_q] ), |
| .wmask_i ( {Width{1'b1}} ), |
| .rdata_o ( rdata_d ), |
| .rvalid_o ( rvalid ), |
| .rerror_o ( rerror ), |
| .cfg_i ( '0 ) |
| ); |
| |
| // Currently it is assumed that no wrap arounds can occur. |
| `ASSERT(NoWrapArounds_A, 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 <= otp_ctrl_pkg::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 |
| |
| endmodule : prim_generic_otp |