| // Copyright lowRISC contributors. |
| // Licensed under the Apache License, Version 2.0, see LICENSE for details. |
| // SPDX-License-Identifier: Apache-2.0 |
| // |
| // Direct access interface for OTP controller. |
| // |
| |
| `include "prim_assert.sv" |
| |
| module otp_ctrl_dai |
| import otp_ctrl_pkg::*; |
| import otp_ctrl_reg_pkg::*; |
| ( |
| input clk_i, |
| input rst_ni, |
| // Init reqest from power manager |
| input init_req_i, |
| output logic init_done_o, |
| // Init request going to partitions |
| output logic part_init_req_o, |
| input [NumPart-1:0] part_init_done_i, |
| // Escalation input. This moves the FSM into a terminal state and locks down |
| // the DAI. |
| input lc_tx_t escalate_en_i, |
| // Output error state of DAI, to be consumed by OTP error/alert logic. |
| // Note that most errors are not recoverable and move the DAI FSM into |
| // a terminal error state. |
| output otp_err_e error_o, |
| // Access/lock status from partitions |
| input part_access_t [NumPart-1:0] part_access_i, |
| // CSR interface |
| input [OtpByteAddrWidth-1:0] dai_addr_i, |
| input dai_cmd_e dai_cmd_i, |
| input logic dai_req_i, |
| input [NumDaiWords-1:0][31:0] dai_wdata_i, |
| output logic dai_idle_o, // wired to the status CSRs |
| output logic dai_cmd_done_o, // this is used to raise an IRQ |
| output logic [NumDaiWords-1:0][31:0] dai_rdata_o, |
| // OTP interface |
| output logic otp_req_o, |
| output prim_otp_cmd_e otp_cmd_o, |
| output logic [OtpSizeWidth-1:0] otp_size_o, |
| output logic [OtpIfWidth-1:0] otp_wdata_o, |
| output logic [OtpAddrWidth-1:0] otp_addr_o, |
| input otp_gnt_i, |
| input otp_rvalid_i, |
| input [ScrmblBlockWidth-1:0] otp_rdata_i, |
| input otp_err_e otp_err_i, |
| // Scrambling mutex request |
| output logic scrmbl_mtx_req_o, |
| input scrmbl_mtx_gnt_i, |
| // Scrambling datapath interface |
| output otp_scrmbl_cmd_e scrmbl_cmd_o, |
| output logic [ConstSelWidth-1:0] scrmbl_sel_o, |
| output logic [ScrmblBlockWidth-1:0] scrmbl_data_o, |
| output logic scrmbl_valid_o, |
| input logic scrmbl_ready_i, |
| input logic scrmbl_valid_i, |
| input logic [ScrmblBlockWidth-1:0] scrmbl_data_i |
| ); |
| |
| //////////////////////// |
| // Integration Checks // |
| //////////////////////// |
| |
| import prim_util_pkg::vbits; |
| |
| localparam int CntWidth = OtpByteAddrWidth - $clog2(ScrmblBlockWidth/8); |
| |
| // Integration checks for parameters. |
| `ASSERT_INIT(CheckNativeOtpWidth0_A, (ScrmblBlockWidth % OtpWidth) == 0) |
| `ASSERT_INIT(CheckNativeOtpWidth1_A, (32 % OtpWidth) == 0) |
| |
| ///////////////////// |
| // DAI Control FSM // |
| ///////////////////// |
| |
| // Encoding generated with ./sparse-fsm-encode.py -d 5 -m 20 -n 12 -s 3011551511 |
| // Hamming distance histogram: |
| // |
| // 0: -- |
| // 1: -- |
| // 2: -- |
| // 3: -- |
| // 4: -- |
| // 5: |||||||||||||||||| (32.11%) |
| // 6: |||||||||||||||||||| (35.26%) |
| // 7: |||||||| (15.79%) |
| // 8: |||||| (11.58%) |
| // 9: | (2.11%) |
| // 10: (1.05%) |
| // 11: | (2.11%) |
| // 12: -- |
| // |
| // Minimum Hamming distance: 5 |
| // Maximum Hamming distance: 11 |
| // |
| parameter int StateWidth = 12; |
| typedef enum logic [StateWidth-1:0] { |
| ResetSt = 12'b001000011011, |
| InitOtpSt = 12'b101111001001, |
| InitPartSt = 12'b101010100111, |
| IdleSt = 12'b110100110101, |
| ErrorSt = 12'b100011010000, |
| ReadSt = 12'b111001010110, |
| ReadWaitSt = 12'b000101100111, |
| DescrSt = 12'b110001001101, |
| DescrWaitSt = 12'b010000110010, |
| WriteSt = 12'b101101111100, |
| WriteWaitSt = 12'b100100101010, |
| ScrSt = 12'b111110010011, |
| ScrWaitSt = 12'b010110011000, |
| DigClrSt = 12'b011100001110, |
| DigReadSt = 12'b011001101000, |
| DigReadWaitSt = 12'b000011111110, |
| DigSt = 12'b000010101001, |
| DigPadSt = 12'b000000000100, |
| DigFinSt = 12'b010011000011, |
| DigWaitSt = 12'b011011110101 |
| } state_e; |
| |
| typedef enum logic [1:0] { |
| OtpData = 2'b00, |
| DaiData = 2'b01, |
| ScrmblData = 2'b10 |
| } data_sel_e; |
| |
| |
| typedef enum logic { |
| PartOffset = 1'b0, |
| DaiOffset = 1'b1 |
| } addr_sel_e; |
| |
| state_e state_d, state_q; |
| logic [CntWidth-1:0] cnt_d, cnt_q; |
| logic cnt_en, cnt_clr; |
| otp_err_e error_d, error_q; |
| logic data_en, data_clr; |
| data_sel_e data_sel; |
| addr_sel_e base_sel_d, base_sel_q; |
| logic [ScrmblBlockWidth-1:0] data_q; |
| logic [NumPartWidth-1:0] part_idx; |
| logic [NumPart-1:0][OtpAddrWidth-1:0] digest_addr_lut; |
| |
| // Output partition error state. |
| assign error_o = error_q; |
| // Working register is connected to data outputs. |
| assign dai_rdata_o = data_q; |
| assign otp_wdata_o = data_q; |
| assign scrmbl_data_o = data_q; |
| |
| always_comb begin : p_fsm |
| state_d = state_q; |
| |
| // Init signals |
| init_done_o = 1'b1; |
| part_init_req_o = 1'b0; |
| |
| // DAI signals |
| dai_idle_o = 1'b0; |
| dai_cmd_done_o = 1'b0; |
| |
| // OTP signals |
| otp_req_o = 1'b0; |
| otp_cmd_o = OtpInit; |
| |
| // Scrambling mutex |
| scrmbl_mtx_req_o = 1'b0; |
| |
| // Scrambling datapath |
| scrmbl_cmd_o = LoadShadow; |
| scrmbl_sel_o = CnstyDigest; |
| scrmbl_valid_o = 1'b0; |
| |
| // Counter |
| cnt_en = 1'b0; |
| cnt_clr = 1'b0; |
| base_sel_d = base_sel_q; |
| |
| // Temporary data register |
| data_en = 1'b0; |
| data_clr = 1'b0; |
| data_sel = OtpData; |
| |
| // Error Register |
| error_d = error_q; |
| |
| unique case (state_q) |
| /////////////////////////////////////////////////////////////////// |
| // We get here after reset and wait until the power manager |
| // requests OTP initialization. If initialization is requested, |
| // an init command is written to the OTP macro, and we move on |
| // to the InitOtpSt waiting state. |
| ResetSt: begin |
| init_done_o = 1'b0; |
| data_clr = 1'b1; |
| if (init_req_i) begin |
| otp_req_o = 1'b1; |
| if (otp_gnt_i) begin |
| state_d = InitOtpSt; |
| end |
| end |
| end |
| /////////////////////////////////////////////////////////////////// |
| // We wait here unitl the OTP macro has initialized without |
| // error. If an error occurred during this stage, we latch that |
| // error and move into a terminal error state. |
| InitOtpSt: begin |
| init_done_o = 1'b0; |
| if (otp_rvalid_i) begin |
| if ((!(otp_err_i inside {NoErr, OtpReadCorrErr}))) begin |
| state_d = ErrorSt; |
| error_d = otp_err_i; |
| end else begin |
| state_d = InitPartSt; |
| end |
| end |
| end |
| /////////////////////////////////////////////////////////////////// |
| // Since the OTP macro is now functional, we can send out an |
| // initialization request to all partitions and wait until they |
| // all have initialized. |
| InitPartSt: begin |
| init_done_o = 1'b0; |
| part_init_req_o = 1'b1; |
| if (part_init_done_i == {NumPart{1'b1}}) begin |
| state_d = IdleSt; |
| end |
| end |
| /////////////////////////////////////////////////////////////////// |
| // Idle state where we wait for incoming commands. |
| // Invalid commands trigger a CmdInvErr, which is recoverable. |
| IdleSt: begin |
| dai_idle_o = 1'b1; |
| if (dai_req_i) begin |
| // This clears previous (recoverable) errors. |
| error_d = NoErr; |
| unique case (dai_cmd_i) |
| DaiRead: begin |
| state_d = ReadSt; |
| // Clear the temporary data register. |
| data_clr = 1'b1; |
| base_sel_d = DaiOffset; |
| end |
| DaiWrite: begin |
| data_sel = DaiData; |
| // Fetch data block. |
| data_en = 1'b1; |
| base_sel_d = DaiOffset; |
| // If this partition is scrambled, directly go to write scrambling first. |
| if (PartInfo[part_idx].scrambled) begin |
| state_d = ScrSt; |
| end else begin |
| state_d = WriteSt; |
| end |
| end |
| DaiDigest: begin |
| state_d = DigClrSt; |
| scrmbl_mtx_req_o = 1'b1; |
| base_sel_d = PartOffset; |
| end |
| default: begin |
| // Invalid commands get caught here. This is a recoverable error. |
| error_d = CmdInvErr; |
| end |
| endcase // dai_cmd_i |
| end // dai_req_i |
| end |
| /////////////////////////////////////////////////////////////////// |
| // Each time we request a block of data from OTP, we re-check |
| // whether read access has been locked for this partition. If |
| // that is the case, we immediately bail out. Otherwise, we |
| // request a block of data from OTP. |
| ReadSt: begin |
| if (part_access_i[part_idx].read_lock == Unlocked) begin |
| otp_req_o = 1'b1; |
| otp_cmd_o = OtpRead; |
| if (otp_gnt_i) begin |
| state_d = ReadWaitSt; |
| end |
| end else begin |
| state_d = IdleSt; |
| error_d = AccessErr; // Signal this error, but do not go into terminal error state. |
| dai_cmd_done_o = 1'b1; |
| end |
| end |
| /////////////////////////////////////////////////////////////////// |
| // Wait for OTP response and write to readout register. Check |
| // whether descrambling is required or not. In case an OTP |
| // transaction fails, latch the OTP error code, and jump to |
| // terminal error state. |
| ReadWaitSt: begin |
| if (otp_rvalid_i) begin |
| // Check OTP return code. |
| if ((!(otp_err_i inside {NoErr, OtpReadCorrErr}))) begin |
| state_d = ErrorSt; |
| error_d = otp_err_i; |
| end else begin |
| data_en = 1'b1; |
| if (PartInfo[part_idx].scrambled) begin |
| state_d = DescrSt; |
| end else begin |
| state_d = IdleSt; |
| dai_cmd_done_o = 1'b1; |
| end |
| // Signal soft ECC errors, but do not go into terminal error state. |
| if (otp_err_i == OtpReadCorrErr) begin |
| error_d = otp_err_i; |
| end |
| end |
| end |
| end |
| /////////////////////////////////////////////////////////////////// |
| // Descrambling state. This first acquires the scrambling |
| // datapath mutex. Note that once the mutex is acquired, we have |
| // exclusive access to the scrambling datapath until we release |
| // the mutex by deasserting scrmbl_mtx_req_o. |
| DescrSt: begin |
| scrmbl_mtx_req_o = 1'b1; |
| scrmbl_valid_o = 1'b1; |
| scrmbl_cmd_o = Decrypt; |
| scrmbl_sel_o = PartInfo[part_idx].key_sel; |
| if (scrmbl_mtx_gnt_i && scrmbl_ready_i) begin |
| state_d = DescrWaitSt; |
| end |
| end |
| /////////////////////////////////////////////////////////////////// |
| // Wait for the descrambled data to return. Note that we release |
| // the mutex lock upon leaving this state. |
| DescrWaitSt: begin |
| scrmbl_mtx_req_o = 1'b1; |
| scrmbl_sel_o = PartInfo[part_idx].key_sel; |
| data_sel = ScrmblData; |
| if (scrmbl_valid_i) begin |
| state_d = IdleSt; |
| data_en = 1'b1; |
| dai_cmd_done_o = 1'b1; |
| end |
| end |
| /////////////////////////////////////////////////////////////////// |
| // First, check whether write accesses are allowed to this |
| // partition, and error out otherwise. Note that for buffered |
| // partitions, we do not allow DAI writes to the digest offset. |
| // Unbuffered partitions have SW managed digests, hence that |
| // check is not needed in that case. The LC partition is |
| // permanently write locked and can hence not be written via the DAI. |
| WriteSt: begin |
| if (part_access_i[part_idx].write_lock == Unlocked && |
| // If this is a HW digest write to a buffered partition. |
| ((PartInfo[part_idx].variant == Buffered && PartInfo[part_idx].hw_digest && |
| base_sel_q == PartOffset && otp_addr_o == digest_addr_lut[part_idx]) || |
| // If this is a non HW digest write to a buffered partition. |
| (PartInfo[part_idx].variant == Buffered && PartInfo[part_idx].hw_digest && |
| base_sel_q == DaiOffset && otp_addr_o < digest_addr_lut[part_idx]) || |
| // If this is a write to an unbuffered partition |
| (PartInfo[part_idx].variant != Buffered && base_sel_q == DaiOffset))) begin |
| otp_req_o = 1'b1; |
| otp_cmd_o = OtpWrite; |
| if (otp_gnt_i) begin |
| state_d = WriteWaitSt; |
| end |
| end else begin |
| state_d = IdleSt; |
| error_d = AccessErr; // Signal this error, but do not go into terminal error state. |
| dai_cmd_done_o = 1'b1; |
| end |
| end |
| /////////////////////////////////////////////////////////////////// |
| // Wait for OTP response, and then go back to idle. In case an |
| // OTP transaction fails, latch the OTP error code, and jump to |
| // terminal error state. |
| WriteWaitSt: begin |
| if (otp_rvalid_i) begin |
| // Check OTP return code. Note that non-blank errors are recoverable. |
| if ((!(otp_err_i inside {NoErr, OtpWriteBlankErr}))) begin |
| state_d = ErrorSt; |
| error_d = otp_err_i; |
| end else begin |
| state_d = IdleSt; |
| dai_cmd_done_o = 1'b1; |
| // Signal non-blank state, but do not go to terminal error state. |
| if (otp_err_i == OtpWriteBlankErr) begin |
| error_d = otp_err_i; |
| end |
| end |
| end |
| end |
| /////////////////////////////////////////////////////////////////// |
| // Scrambling state. This first acquires the scrambling |
| // datapath mutex. Note that once the mutex is acquired, we have |
| // exclusive access to the scrambling datapath until we release |
| // the mutex by deasserting scrmbl_mtx_req_o. |
| ScrSt: begin |
| scrmbl_mtx_req_o = 1'b1; |
| scrmbl_valid_o = 1'b1; |
| scrmbl_cmd_o = Encrypt; |
| scrmbl_sel_o = PartInfo[part_idx].key_sel; |
| if (scrmbl_mtx_gnt_i && scrmbl_ready_i) begin |
| state_d = ScrWaitSt; |
| end |
| end |
| /////////////////////////////////////////////////////////////////// |
| // Wait for the scrambled data to return. Note that we release |
| // the mutex lock upon leaving this state. |
| ScrWaitSt: begin |
| scrmbl_mtx_req_o = 1'b1; |
| data_sel = ScrmblData; |
| if (scrmbl_valid_i) begin |
| state_d = WriteSt; |
| data_en = 1'b1; |
| end |
| end |
| /////////////////////////////////////////////////////////////////// |
| // First, acquire the mutex for the digest and clear the digest state. |
| DigClrSt: begin |
| scrmbl_mtx_req_o = 1'b1; |
| scrmbl_valid_o = 1'b1; |
| // Need to reset the digest state and set digest mode to "standard". |
| scrmbl_sel_o = StandardMode; |
| scrmbl_cmd_o = DigestInit; |
| if (scrmbl_mtx_gnt_i && scrmbl_ready_i) begin |
| state_d = DigReadSt; |
| end |
| end |
| /////////////////////////////////////////////////////////////////// |
| // This requests a 64bit block to be pushed into the digest datapath. |
| // We also check here whether the partition has been write locked. |
| DigReadSt: begin |
| scrmbl_mtx_req_o = 1'b1; |
| if (part_access_i[part_idx].read_lock == Unlocked && |
| part_access_i[part_idx].write_lock == Unlocked) begin |
| otp_req_o = 1'b1; |
| otp_cmd_o = OtpRead; |
| if (otp_gnt_i) begin |
| state_d = DigReadWaitSt; |
| end |
| end else begin |
| state_d = IdleSt; |
| error_d = AccessErr; // Signal this error, but do not go into terminal error state. |
| dai_cmd_done_o = 1'b1; |
| end |
| end |
| /////////////////////////////////////////////////////////////////// |
| // Wait for OTP response and write to readout register. Check |
| // whether descrambling is required or not. In case an OTP |
| // transaction fails, latch the OTP error code, and jump to |
| // terminal error state. |
| DigReadWaitSt: begin |
| scrmbl_mtx_req_o = 1'b1; |
| if (otp_rvalid_i) begin |
| cnt_en = 1'b1; |
| // Check OTP return code. |
| if ((!(otp_err_i inside {NoErr, OtpReadCorrErr}))) begin |
| state_d = ErrorSt; |
| error_d = otp_err_i; |
| end else begin |
| data_en = 1'b1; |
| state_d = DigSt; |
| // Signal soft ECC errors, but do not go into terminal error state. |
| if (otp_err_i == OtpReadCorrErr) begin |
| error_d = otp_err_i; |
| end |
| end |
| end |
| end |
| /////////////////////////////////////////////////////////////////// |
| // Push the word read into the scrambling datapath. The last |
| // block is repeated in case the number blocks in this partition |
| // is odd. |
| DigSt: begin |
| scrmbl_mtx_req_o = 1'b1; |
| scrmbl_valid_o = 1'b1; |
| // No need to digest the digest value itself |
| if (otp_addr_o == digest_addr_lut[part_idx]) begin |
| // Trigger digest round in case this is the second block in a row. |
| if (cnt_q[0]) begin |
| scrmbl_cmd_o = Digest; |
| if (scrmbl_ready_i) begin |
| state_d = DigFinSt; |
| end |
| // Otherwise, just load low word and go to padding state. |
| end else if (scrmbl_ready_i) begin |
| state_d = DigPadSt; |
| end |
| end else begin |
| // Trigger digest round in case this is the second block in a row. |
| if (cnt_q[0]) begin |
| scrmbl_cmd_o = Digest; |
| end |
| // Go back and fetch more data blocks. |
| if (scrmbl_ready_i) begin |
| state_d = DigReadSt; |
| end |
| end |
| end |
| /////////////////////////////////////////////////////////////////// |
| // Padding state, just repeat the last block and go to digest |
| // finalization. |
| DigPadSt: begin |
| scrmbl_mtx_req_o = 1'b1; |
| scrmbl_valid_o = 1'b1; |
| scrmbl_cmd_o = Digest; |
| if (scrmbl_ready_i) begin |
| state_d = DigFinSt; |
| end |
| end |
| /////////////////////////////////////////////////////////////////// |
| // Trigger digest finalization and go wait for the result. |
| DigFinSt: begin |
| scrmbl_mtx_req_o = 1'b1; |
| scrmbl_valid_o = 1'b1; |
| scrmbl_cmd_o = DigestFinalize; |
| if (scrmbl_ready_i) begin |
| state_d = DigWaitSt; |
| end |
| end |
| /////////////////////////////////////////////////////////////////// |
| // Wait for the digest to return, and write the result to OTP. |
| // Note that the write address will be correct in this state, |
| // since the counter has been stepped to the correct address as |
| // part of the readout sequence, and the correct size for this |
| // access has been loaded before. |
| DigWaitSt: begin |
| scrmbl_mtx_req_o = 1'b1; |
| data_sel = ScrmblData; |
| if (scrmbl_valid_i) begin |
| state_d = WriteSt; |
| data_en = 1'b1; |
| end |
| end |
| /////////////////////////////////////////////////////////////////// |
| // Terminal Error State. This locks access to the DAI. Make sure |
| // an FsmErr error code is assigned here, in case no error code has |
| // been assigned yet. |
| ErrorSt: begin |
| if (!error_q) begin |
| error_d = FsmErr; |
| end |
| end |
| /////////////////////////////////////////////////////////////////// |
| // We should never get here. If we do (e.g. via a malicious |
| // glitch), error out immediately. |
| default: begin |
| state_d = ErrorSt; |
| end |
| /////////////////////////////////////////////////////////////////// |
| endcase // state_q |
| |
| if (state_q != ErrorSt) begin |
| // Unconditionally jump into the terminal error state in case of |
| // escalation, and lock access to the DAI down. |
| if (escalate_en_i != Off) begin |
| state_d = ErrorSt; |
| error_d = EscErr; |
| end |
| end |
| end |
| |
| //////////////////////////// |
| // Partition Select Logic // |
| //////////////////////////// |
| |
| // This checks which partition the address belongs to by comparing |
| // the incoming address to the partition address ranges. The onehot |
| // bitvector generated by the parallel comparisons is fed into a |
| // binary tree that determines the partition index with O(log(N)) delay. |
| |
| logic [NumPart-1:0] part_sel_oh; |
| for (genvar k = 0; k < NumPart; k++) begin : gen_part_sel |
| localparam logic [OtpByteAddrWidth:0] PartEnd = (OtpByteAddrWidth+1)'(PartInfo[k].offset) + |
| (OtpByteAddrWidth+1)'(PartInfo[k].size); |
| assign part_sel_oh[k] = (dai_addr_i >= PartInfo[k].offset) & ({1'b0, dai_addr_i} < PartEnd); |
| // Needed for other address and access checks. |
| localparam logic [OtpByteAddrWidth-1:0] DigestOffset = OtpByteAddrWidth'(PartEnd - |
| ScrmblBlockWidth/8); |
| assign digest_addr_lut[k] = DigestOffset >> OtpAddrShift; |
| end |
| |
| `ASSERT(PartSelMustBeOnehot_A, $onehot0(part_sel_oh)) |
| |
| prim_arbiter_fixed #( |
| .N(NumPart), |
| .EnDataPort(0) |
| ) i_prim_arbiter_fixed ( |
| .clk_i, |
| .rst_ni, |
| .req_i ( part_sel_oh ), |
| .data_i ( '{default: '0} ), |
| .gnt_o ( ), // unused |
| .idx_o ( part_idx ), |
| .valid_o ( ), // unused |
| .data_o ( ), // unused |
| .ready_i ( 1'b0 ) |
| ); |
| |
| ///////////////////////////////////// |
| // Address Calculations for Digest // |
| ///////////////////////////////////// |
| |
| // Depending on whether this is a 32bit or 64bit partition, we cut off the lower address bits. |
| logic [OtpByteAddrWidth-1:0] addr_base; |
| assign addr_base = (base_sel_q == PartOffset) ? PartInfo[part_idx].offset : |
| (PartInfo[part_idx].scrambled) ? {dai_addr_i[OtpByteAddrWidth-1:3], 3'h0} : |
| {dai_addr_i[OtpByteAddrWidth-1:2], 2'h0}; |
| |
| // OTP transaction sizes are 64bit for scrambled partitions, and when digesting. |
| // Otherwise, they are 32bit. |
| assign otp_size_o = (PartInfo[part_idx].scrambled || base_sel_q == PartOffset) ? |
| OtpSizeWidth'(unsigned'(ScrmblBlockWidth / OtpWidth - 1)) : |
| OtpSizeWidth'(unsigned'(32 / OtpWidth - 1)); |
| |
| // Address counter - this is only used for computing a digest, hence the increment is |
| // fixed to 8 byte. |
| assign cnt_d = (cnt_clr) ? '0 : |
| (cnt_en) ? cnt_q + 1'b1 : cnt_q; |
| |
| // Note that OTP works on halfword (16bit) addresses, hence need to |
| // shift the addresses appropriately. |
| logic [OtpByteAddrWidth-1:0] addr_calc; |
| assign addr_calc = {cnt_q, {$clog2(ScrmblBlockWidth/8){1'b0}}} + addr_base; |
| assign otp_addr_o = addr_calc >> OtpAddrShift; |
| |
| /////////////// |
| // Registers // |
| /////////////// |
| |
| // This primitive is used to place a size-only constraint on the |
| // flops in order to prevent FSM state encoding optimizations. |
| prim_flop #( |
| .Width(StateWidth), |
| .ResetValue(StateWidth'(ResetSt)) |
| ) u_state_regs ( |
| .clk_i, |
| .rst_ni, |
| .d_i ( state_d ), |
| .q_o ( state_q ) |
| ); |
| |
| always_ff @(posedge clk_i or negedge rst_ni) begin : p_regs |
| if (!rst_ni) begin |
| error_q <= NoErr; |
| cnt_q <= '0; |
| data_q <= '0; |
| base_sel_q <= DaiOffset; |
| end else begin |
| error_q <= error_d; |
| cnt_q <= cnt_d; |
| base_sel_q <= base_sel_d; |
| |
| // Working register |
| if (data_clr) begin |
| data_q <= '0; |
| end else if (data_en) begin |
| if (data_sel == ScrmblData) begin |
| data_q <= scrmbl_data_i; |
| end else if (data_sel == DaiData) begin |
| data_q <= dai_wdata_i; |
| end else begin |
| data_q <= otp_rdata_i; |
| end |
| end |
| end |
| end |
| |
| //////////////// |
| // Assertions // |
| //////////////// |
| |
| // Known assertions |
| `ASSERT_KNOWN(InitDoneKnown_A, init_done_o) |
| `ASSERT_KNOWN(PartInitReqKnown_A, part_init_req_o) |
| `ASSERT_KNOWN(ErrorKnown_A, error_o) |
| `ASSERT_KNOWN(DaiIdleKnown_A, dai_idle_o) |
| `ASSERT_KNOWN(DaiRdataKnown_A, dai_rdata_o) |
| `ASSERT_KNOWN(OtpReqKnown_A, otp_req_o) |
| `ASSERT_KNOWN(OtpCmdKnown_A, otp_cmd_o) |
| `ASSERT_KNOWN(OtpSizeKnown_A, otp_size_o) |
| `ASSERT_KNOWN(OtpWdataKnown_A, otp_wdata_o) |
| `ASSERT_KNOWN(OtpAddrKnown_A, otp_addr_o) |
| `ASSERT_KNOWN(ScrmblMtxReqKnown_A, scrmbl_mtx_req_o) |
| `ASSERT_KNOWN(ScrmblCmdKnown_A, scrmbl_cmd_o) |
| `ASSERT_KNOWN(ScrmblSelKnown_A, scrmbl_sel_o) |
| `ASSERT_KNOWN(ScrmblDataKnown_A, scrmbl_data_o) |
| `ASSERT_KNOWN(ScrmblValidKnown_A, scrmbl_valid_o) |
| |
| // OTP error response |
| `ASSERT(OtpErrorState_A, |
| state_q inside {InitOtpSt, ReadWaitSt, WriteWaitSt, DigReadWaitSt} && otp_rvalid_i && |
| !(otp_err_i inside {NoErr, OtpReadCorrErr}) |
| |=> |
| state_q == ErrorSt && error_o == $past(otp_err_i)) |
| |
| endmodule : otp_ctrl_dai |