| // Copyright lowRISC contributors. |
| // Licensed under the Apache License, Version 2.0, see LICENSE for details. |
| // SPDX-License-Identifier: Apache-2.0 |
| // |
| // AES cipher core control FSM |
| // |
| // This module contains the AES cipher core control FSM. |
| |
| `include "prim_assert.sv" |
| |
| module aes_cipher_control_fsm import aes_pkg::*; |
| #( |
| parameter bit SecMasking = 0, |
| parameter sbox_impl_e SecSBoxImpl = SBoxImplDom |
| ) ( |
| input logic clk_i, |
| input logic rst_ni, |
| |
| // Input handshake signals |
| input logic in_valid_i, // Sparsify using multi-rail. |
| output logic in_ready_o, // Sparsify using multi-rail. |
| |
| // Output handshake signals |
| output logic out_valid_o, // Sparsify using multi-rail. |
| input logic out_ready_i, // Sparsify using multi-rail. |
| |
| // Control and sync signals |
| input logic cfg_valid_i, // Used for SVAs only. |
| input ciph_op_e op_i, |
| input key_len_e key_len_i, |
| input logic crypt_i, // Sparsify using multi-rail. |
| input logic dec_key_gen_i, // Sparsify using multi-rail. |
| input logic prng_reseed_i, |
| input logic key_clear_i, |
| input logic data_out_clear_i, |
| input logic mux_sel_err_i, |
| input logic sp_enc_err_i, |
| input logic rnd_ctr_err_i, |
| input logic op_err_i, |
| input logic alert_fatal_i, |
| output logic alert_o, |
| |
| // Control signals for masking PRNG |
| output logic prng_update_o, |
| output logic prng_reseed_req_o, |
| input logic prng_reseed_ack_i, |
| |
| // Control and sync signals for cipher data path |
| output state_sel_e state_sel_o, |
| output logic state_we_o, // Sparsify using multi-rail. |
| output logic sub_bytes_en_o, // Sparsify using multi-rail. |
| input logic sub_bytes_out_req_i, // Sparsify using multi-rail. |
| output logic sub_bytes_out_ack_o, // Sparsify using multi-rail. |
| output add_rk_sel_e add_rk_sel_o, |
| |
| // Control and sync signals for key expand data path |
| output key_full_sel_e key_full_sel_o, |
| output logic key_full_we_o, // Sparsify using multi-rail. |
| output key_dec_sel_e key_dec_sel_o, |
| output logic key_dec_we_o, // Sparsify using multi-rail. |
| output logic key_expand_en_o, // Sparsify using multi-rail. |
| input logic key_expand_out_req_i, // Sparsify using multi-rail. |
| output logic key_expand_out_ack_o, // Sparsify using multi-rail. |
| output logic key_expand_clear_o, |
| output logic [3:0] rnd_ctr_o, |
| output key_words_sel_e key_words_sel_o, |
| output round_key_sel_e round_key_sel_o, |
| |
| // Register signals |
| input logic crypt_q_i, // Sparsify using multi-rail. |
| output logic crypt_d_o, // Sparsify using multi-rail. |
| input logic dec_key_gen_q_i, // Sparsify using multi-rail. |
| output logic dec_key_gen_d_o, // Sparsify using multi-rail. |
| input logic prng_reseed_q_i, |
| output logic prng_reseed_d_o, |
| input logic key_clear_q_i, |
| output logic key_clear_d_o, |
| input logic data_out_clear_q_i, |
| output logic data_out_clear_d_o |
| ); |
| |
| // Types |
| // Encoding generated with: |
| // $ ./util/design/sparse-fsm-encode.py -d 3 -m 8 -n 6 \ |
| // -s 31468618 --language=sv |
| // |
| // Hamming distance histogram: |
| // |
| // 0: -- |
| // 1: -- |
| // 2: -- |
| // 3: |||||||||||||||||||| (57.14%) |
| // 4: ||||||||||||||| (42.86%) |
| // 5: -- |
| // 6: -- |
| // |
| // Minimum Hamming distance: 3 |
| // Maximum Hamming distance: 4 |
| // Minimum Hamming weight: 1 |
| // Maximum Hamming weight: 5 |
| // |
| localparam int StateWidth = 6; |
| typedef enum logic [StateWidth-1:0] { |
| IDLE = 6'b001001, |
| INIT = 6'b100011, |
| ROUND = 6'b111101, |
| FINISH = 6'b010000, |
| PRNG_RESEED = 6'b100100, |
| CLEAR_S = 6'b111010, |
| CLEAR_KD = 6'b001110, |
| ERROR = 6'b010111 |
| } aes_cipher_ctrl_e; |
| |
| // cfg_valid_i is used for SVAs only. |
| logic unused_cfg_valid; |
| assign unused_cfg_valid = cfg_valid_i; |
| |
| // Tie off unused inputs. |
| if (!SecMasking) begin : gen_unused_prng_reseed |
| logic unused_prng_reseed; |
| assign unused_prng_reseed = prng_reseed_i; |
| end |
| |
| // Signals |
| aes_cipher_ctrl_e aes_cipher_ctrl_ns, aes_cipher_ctrl_cs; |
| logic advance; |
| logic [2:0] cyc_ctr_d, cyc_ctr_q; |
| logic cyc_ctr_expr; |
| logic prng_reseed_done_d, prng_reseed_done_q; |
| logic [3:0] rnd_ctr_d, rnd_ctr_q; |
| logic [3:0] num_rounds_d, num_rounds_q; |
| logic [3:0] num_rounds_regular; |
| |
| // Use separate signal for number of regular rounds. |
| assign num_rounds_regular = num_rounds_q - 4'd1; |
| |
| // FSM |
| always_comb begin : aes_cipher_ctrl_fsm |
| |
| // Handshake signals |
| in_ready_o = 1'b0; |
| out_valid_o = 1'b0; |
| |
| // Masking PRNG signals |
| prng_update_o = 1'b0; |
| prng_reseed_req_o = 1'b0; |
| |
| // Cipher data path |
| state_sel_o = STATE_ROUND; |
| state_we_o = 1'b0; |
| add_rk_sel_o = ADD_RK_ROUND; |
| sub_bytes_en_o = 1'b0; |
| sub_bytes_out_ack_o = 1'b0; |
| |
| // Key expand data path |
| key_full_sel_o = KEY_FULL_ROUND; |
| key_full_we_o = 1'b0; |
| key_dec_sel_o = KEY_DEC_EXPAND; |
| key_dec_we_o = 1'b0; |
| key_expand_en_o = 1'b0; |
| key_expand_out_ack_o = 1'b0; |
| key_expand_clear_o = 1'b0; |
| key_words_sel_o = KEY_WORDS_ZERO; |
| round_key_sel_o = ROUND_KEY_DIRECT; |
| |
| // FSM |
| aes_cipher_ctrl_ns = aes_cipher_ctrl_cs; |
| num_rounds_d = num_rounds_q; |
| rnd_ctr_d = rnd_ctr_q; |
| crypt_d_o = crypt_q_i; |
| dec_key_gen_d_o = dec_key_gen_q_i; |
| prng_reseed_d_o = prng_reseed_q_i; |
| key_clear_d_o = key_clear_q_i; |
| data_out_clear_d_o = data_out_clear_q_i; |
| prng_reseed_done_d = prng_reseed_done_q | prng_reseed_ack_i; |
| advance = 1'b0; |
| cyc_ctr_d = (SecSBoxImpl == SBoxImplDom) ? cyc_ctr_q + 3'd1 : 3'd0; |
| |
| // Alert |
| alert_o = 1'b0; |
| |
| unique case (aes_cipher_ctrl_cs) |
| |
| IDLE: begin |
| cyc_ctr_d = 3'd0; |
| |
| // Signal that we are ready, wait for handshake. |
| in_ready_o = 1'b1; |
| if (in_valid_i) begin |
| if (SecMasking && prng_reseed_i && !dec_key_gen_i && !crypt_i) begin |
| // Reseed the masking PRNG without starting encryption/decryption or generation of the |
| // start key for decryption. |
| prng_reseed_d_o = 1'b1; |
| prng_reseed_done_d = 1'b0; |
| aes_cipher_ctrl_ns = PRNG_RESEED; |
| |
| end else if (key_clear_i || data_out_clear_i) begin |
| // Clear internal key registers. The cipher core muxes are used to clear the data |
| // output registers. |
| key_clear_d_o = key_clear_i; |
| data_out_clear_d_o = data_out_clear_i; |
| |
| // To clear the data output registers, we must first clear the state. |
| aes_cipher_ctrl_ns = data_out_clear_i ? CLEAR_S : CLEAR_KD; |
| |
| end else if (dec_key_gen_i || crypt_i) begin |
| // Start encryption/decryption or generation of start key for decryption. |
| crypt_d_o = ~dec_key_gen_i & crypt_i; |
| dec_key_gen_d_o = dec_key_gen_i; |
| |
| // Latch whether we shall reseed the masking PRNG. |
| prng_reseed_d_o = SecMasking & prng_reseed_i; |
| |
| // Load input data to state |
| state_sel_o = dec_key_gen_i ? STATE_CLEAR : STATE_INIT; |
| state_we_o = 1'b1; |
| |
| // Make the masking PRNG advance. The current pseudo-random data is used to mask the |
| // input data. |
| prng_update_o = dec_key_gen_i ? 1'b0 : SecMasking; |
| |
| // Init key expand |
| key_expand_clear_o = 1'b1; |
| |
| // Load full key |
| key_full_sel_o = dec_key_gen_i ? KEY_FULL_ENC_INIT : |
| (op_i == CIPH_FWD) ? KEY_FULL_ENC_INIT : |
| (op_i == CIPH_INV) ? KEY_FULL_DEC_INIT : |
| KEY_FULL_ENC_INIT; |
| key_full_we_o = 1'b1; |
| |
| // Load num_rounds, initialize round counter. |
| num_rounds_d = (key_len_i == AES_128) ? 4'd10 : |
| (key_len_i == AES_192) ? 4'd12 : |
| 4'd14; |
| rnd_ctr_d = '0; |
| aes_cipher_ctrl_ns = INIT; |
| |
| end else begin |
| // Handshake without a valid command. We should never get here. If we do (e.g. via a |
| // malicious glitch), error out immediately. |
| aes_cipher_ctrl_ns = ERROR; |
| end |
| end |
| end |
| |
| INIT: begin |
| // Initial round: just add key to state |
| add_rk_sel_o = ADD_RK_INIT; |
| |
| // Select key words for initial add_round_key |
| key_words_sel_o = (dec_key_gen_q_i) ? KEY_WORDS_ZERO : |
| (key_len_i == AES_128) ? KEY_WORDS_0123 : |
| (key_len_i == AES_192 && op_i == CIPH_FWD) ? KEY_WORDS_0123 : |
| (key_len_i == AES_192 && op_i == CIPH_INV) ? KEY_WORDS_2345 : |
| (key_len_i == AES_256 && op_i == CIPH_FWD) ? KEY_WORDS_0123 : |
| (key_len_i == AES_256 && op_i == CIPH_INV) ? KEY_WORDS_4567 : KEY_WORDS_ZERO; |
| |
| // Clear masking PRNG reseed status. |
| prng_reseed_done_d = 1'b0; |
| |
| // AES-256 has two round keys available right from beginning. Pseudo-random data is |
| // required by KeyExpand only. |
| if (key_len_i != AES_256) begin |
| // Advance in sync with KeyExpand. Based on the S-Box implementation, it can take |
| // multiple cycles to finish. Wait for handshake. The DOM S-Boxes consume fresh PRD |
| // only in the first clock cycle. By requesting the PRNG update in any clock cycle |
| // other than the last one, the PRD fed into the DOM S-Boxes is guaranteed to be stable. |
| // This is better in terms of SCA resistance. Request the PRNG update in the first cycle. |
| advance = key_expand_out_req_i & cyc_ctr_expr; |
| prng_update_o = (SecSBoxImpl == SBoxImplDom) ? cyc_ctr_q == 3'd0 : SecMasking; |
| key_expand_en_o = 1'b1; |
| if (advance) begin |
| key_expand_out_ack_o = 1'b1; |
| state_we_o = ~dec_key_gen_q_i; |
| key_full_we_o = 1'b1; |
| rnd_ctr_d = rnd_ctr_q + 4'b0001; |
| cyc_ctr_d = 3'd0; |
| aes_cipher_ctrl_ns = ROUND; |
| end |
| end else begin |
| state_we_o = ~dec_key_gen_q_i; |
| rnd_ctr_d = rnd_ctr_q + 4'b0001; |
| cyc_ctr_d = 3'd0; |
| aes_cipher_ctrl_ns = ROUND; |
| end |
| end |
| |
| ROUND: begin |
| // Normal rounds |
| |
| // Select key words for add_round_key |
| key_words_sel_o = (dec_key_gen_q_i) ? KEY_WORDS_ZERO : |
| (key_len_i == AES_128) ? KEY_WORDS_0123 : |
| (key_len_i == AES_192 && op_i == CIPH_FWD) ? KEY_WORDS_2345 : |
| (key_len_i == AES_192 && op_i == CIPH_INV) ? KEY_WORDS_0123 : |
| (key_len_i == AES_256 && op_i == CIPH_FWD) ? KEY_WORDS_4567 : |
| (key_len_i == AES_256 && op_i == CIPH_INV) ? KEY_WORDS_0123 : KEY_WORDS_ZERO; |
| |
| // Keep requesting PRNG reseeding until it is acknowledged. |
| prng_reseed_req_o = SecMasking & prng_reseed_q_i & ~prng_reseed_done_q; |
| |
| // Select round key: direct or mixed (equivalent inverse cipher) |
| round_key_sel_o = (op_i == CIPH_FWD) ? ROUND_KEY_DIRECT : |
| (op_i == CIPH_INV) ? ROUND_KEY_MIXED : ROUND_KEY_DIRECT; |
| |
| // Advance in sync with SubBytes and KeyExpand. Based on the S-Box implementation, both can |
| // take multiple cycles to finish. Wait for handshake. The DOM S-Boxes consume fresh PRD |
| // only in the first clock cycle. By requesting the PRNG update in any clock cycle other |
| // than the last one, the PRD fed into the DOM S-Boxes is guaranteed to be stable. This is |
| // better in terms of SCA resistance. Request the PRNG update in the first cycle. Non-DOM |
| // S-Boxes need fresh PRD in every clock cycle. |
| advance = key_expand_out_req_i & cyc_ctr_expr & (dec_key_gen_q_i | sub_bytes_out_req_i); |
| prng_update_o = (SecSBoxImpl == SBoxImplDom) ? cyc_ctr_q == 3'd0 : SecMasking; |
| sub_bytes_en_o = ~dec_key_gen_q_i; |
| key_expand_en_o = 1'b1; |
| |
| if (advance) begin |
| sub_bytes_out_ack_o = ~dec_key_gen_q_i; |
| key_expand_out_ack_o = 1'b1; |
| |
| state_we_o = ~dec_key_gen_q_i; |
| key_full_we_o = 1'b1; |
| |
| // Update round |
| rnd_ctr_d = rnd_ctr_q + 4'b0001; |
| cyc_ctr_d = 3'd0; |
| |
| // Are we doing the last regular round? |
| if (rnd_ctr_q >= num_rounds_regular) begin |
| aes_cipher_ctrl_ns = FINISH; |
| |
| if (dec_key_gen_q_i) begin |
| // Write decryption key. |
| key_dec_we_o = 1'b1; |
| |
| // Indicate that we are done, try to perform the handshake. But we don't wait here. |
| // If we don't get the handshake now, we will wait in the finish state. When using |
| // masking, we only finish if the masking PRNG has been reseeded. |
| out_valid_o = SecMasking ? (prng_reseed_q_i ? prng_reseed_done_q : 1'b1) : 1'b1; |
| if (out_valid_o && out_ready_i) begin |
| // Go to idle state directly. |
| dec_key_gen_d_o = 1'b0; |
| prng_reseed_d_o = 1'b0; |
| aes_cipher_ctrl_ns = IDLE; |
| end |
| end |
| end // rnd_ctr_q |
| end // SubBytes/KeyExpand REQ/ACK |
| end |
| |
| FINISH: begin |
| // Final round |
| |
| // Select key words for add_round_key |
| key_words_sel_o = (dec_key_gen_q_i) ? KEY_WORDS_ZERO : |
| (key_len_i == AES_128) ? KEY_WORDS_0123 : |
| (key_len_i == AES_192 && op_i == CIPH_FWD) ? KEY_WORDS_2345 : |
| (key_len_i == AES_192 && op_i == CIPH_INV) ? KEY_WORDS_0123 : |
| (key_len_i == AES_256 && op_i == CIPH_FWD) ? KEY_WORDS_4567 : |
| (key_len_i == AES_256 && op_i == CIPH_INV) ? KEY_WORDS_0123 : KEY_WORDS_ZERO; |
| |
| // Skip mix_columns |
| add_rk_sel_o = ADD_RK_FINAL; |
| |
| // Keep requesting PRNG reseeding until it is acknowledged. |
| prng_reseed_req_o = SecMasking & prng_reseed_q_i & ~prng_reseed_done_q; |
| |
| // SEC_CM: DATA_REG.KEY.SCA |
| // Once we're done, we won't need the state anymore. We actually clear it when progressing |
| // to the next state. |
| state_sel_o = STATE_CLEAR; |
| |
| // SEC_CM: DATA_REG.LOCAL_ESC |
| // Advance in sync with SubBytes. Based on the S-Box implementation, it can take multiple |
| // cycles to finish. Only indicate that we are done if: |
| // - we have valid output (SubBytes finished), |
| // - the masking PRNG has been reseeded (if masking is used), |
| // - all mux selector signals are valid (don't release data in case of errors), and |
| // - all sparsely encoded signals are valid (don't release data in case of errors). |
| // Perform both handshakes simultaneously. |
| advance = (sub_bytes_out_req_i & cyc_ctr_expr) | dec_key_gen_q_i; |
| sub_bytes_en_o = ~dec_key_gen_q_i; |
| out_valid_o = (mux_sel_err_i || sp_enc_err_i || op_err_i) ? 1'b0 : |
| SecMasking ? (prng_reseed_q_i ? prng_reseed_done_q & advance : advance) : advance; |
| |
| // Stop updating the cycle counter once we have valid output. |
| cyc_ctr_d = |
| (SecSBoxImpl == SBoxImplDom) ? (!advance ? cyc_ctr_q + 3'd1 : cyc_ctr_q) : 3'd0; |
| |
| // The DOM S-Boxes consume fresh PRD only in the first clock cycle. By requesting the PRNG |
| // update in any clock cycle other than the last one, the PRD fed into the DOM S-Boxes is |
| // guaranteed to be stable. This is better in terms of SCA resistance. Request the PRNG |
| // update in the first cycle. We update it only once and in the last cycle for non-DOM |
| // S-Boxes where otherwise updating the PRNG while being stalled would cause the S-Boxes |
| // to be re-evaluated, thereby creating additional SCA leakage. |
| prng_update_o = |
| (SecSBoxImpl == SBoxImplDom) ? cyc_ctr_q == 3'd0 : out_valid_o & out_ready_i; |
| |
| if (out_valid_o && out_ready_i) begin |
| sub_bytes_out_ack_o = ~dec_key_gen_q_i; |
| |
| // Clear the state. |
| state_we_o = 1'b1; |
| crypt_d_o = 1'b0; |
| cyc_ctr_d = 3'd0; |
| // If we were generating the decryption key and didn't get the handshake in the last |
| // regular round, we should clear dec_key_gen now. |
| dec_key_gen_d_o = 1'b0; |
| prng_reseed_d_o = 1'b0; |
| aes_cipher_ctrl_ns = IDLE; |
| end |
| end |
| |
| PRNG_RESEED: begin |
| // Keep requesting PRNG reseeding until it is acknowledged. |
| prng_reseed_req_o = prng_reseed_q_i & ~prng_reseed_done_q; |
| |
| // Once we're done, wait for handshake. |
| out_valid_o = prng_reseed_done_q; |
| if (out_valid_o && out_ready_i) begin |
| prng_reseed_d_o = 1'b0; |
| aes_cipher_ctrl_ns = IDLE; |
| end |
| end |
| |
| CLEAR_S: begin |
| // Clear the state with pseudo-random data. |
| state_we_o = 1'b1; |
| state_sel_o = STATE_CLEAR; |
| aes_cipher_ctrl_ns = CLEAR_KD; |
| end |
| |
| CLEAR_KD: begin |
| // Clear internal key registers and/or external data output registers. |
| if (key_clear_q_i) begin |
| key_full_sel_o = KEY_FULL_CLEAR; |
| key_full_we_o = 1'b1; |
| key_dec_sel_o = KEY_DEC_CLEAR; |
| key_dec_we_o = 1'b1; |
| end |
| if (data_out_clear_q_i) begin |
| // Forward the state (previously cleared with psuedo-random data). |
| // SEC_CM: DATA_REG.SEC_WIPE |
| add_rk_sel_o = ADD_RK_INIT; |
| key_words_sel_o = KEY_WORDS_ZERO; |
| round_key_sel_o = ROUND_KEY_DIRECT; |
| end |
| // Indicate that we are done, wait for handshake. |
| out_valid_o = 1'b1; |
| if (out_ready_i) begin |
| key_clear_d_o = 1'b0; |
| data_out_clear_d_o = 1'b0; |
| aes_cipher_ctrl_ns = IDLE; |
| end |
| end |
| |
| ERROR: begin |
| // SEC_CM: CIPHER.FSM.LOCAL_ESC |
| // Terminal error state |
| alert_o = 1'b1; |
| end |
| |
| // We should never get here. If we do (e.g. via a malicious glitch), error out immediately. |
| default: begin |
| aes_cipher_ctrl_ns = ERROR; |
| alert_o = 1'b1; |
| end |
| endcase |
| |
| // Unconditionally jump into the terminal error state in case a mux selector or a sparsely |
| // encoded signal becomes invalid, in case we have detected a fault in the round counter, |
| // or if a fatal alert has been triggered. |
| if (mux_sel_err_i || sp_enc_err_i || rnd_ctr_err_i || op_err_i || alert_fatal_i) begin |
| aes_cipher_ctrl_ns = ERROR; |
| end |
| end |
| |
| // SEC_CM: CIPHER.FSM.SPARSE |
| `PRIM_FLOP_SPARSE_FSM(u_state_regs, aes_cipher_ctrl_ns, |
| aes_cipher_ctrl_cs, aes_cipher_ctrl_e, IDLE) |
| |
| always_ff @(posedge clk_i or negedge rst_ni) begin : reg_fsm |
| if (!rst_ni) begin |
| prng_reseed_done_q <= 1'b0; |
| rnd_ctr_q <= '0; |
| num_rounds_q <= '0; |
| end else begin |
| prng_reseed_done_q <= prng_reseed_done_d; |
| rnd_ctr_q <= rnd_ctr_d; |
| num_rounds_q <= num_rounds_d; |
| end |
| end |
| |
| assign rnd_ctr_o = rnd_ctr_q; |
| |
| if (SecSBoxImpl == SBoxImplDom) begin : gen_cyc_ctr |
| always_ff @(posedge clk_i or negedge rst_ni) begin : reg_cyc_ctr |
| if (!rst_ni) begin |
| cyc_ctr_q <= 3'd0; |
| end else begin |
| cyc_ctr_q <= cyc_ctr_d; |
| end |
| end |
| assign cyc_ctr_expr = cyc_ctr_q >= 3'd4; |
| end else begin : gen_no_cyc_ctr |
| logic [2:0] unused_cyc_ctr; |
| assign cyc_ctr_q = cyc_ctr_d; |
| assign unused_cyc_ctr = cyc_ctr_q; |
| assign cyc_ctr_expr = 1'b1; |
| end |
| |
| //////////////// |
| // Assertions // |
| //////////////// |
| |
| // Create a lint error to reduce the risk of accidentally disabling the masking. |
| `ASSERT_STATIC_LINT_ERROR(AesCipherControlFsmSecMaskingNonDefault, SecMasking == 1) |
| |
| // Create a lint error to reduce the risk of accidentally using a less secure SBox |
| // implementation. |
| `ASSERT_STATIC_LINT_ERROR(AesCipherControlFsmSecSBoxImplNonDefault, SecSBoxImpl == SBoxImplDom) |
| |
| // Selectors must be known/valid |
| `ASSERT(AesCiphOpValid, cfg_valid_i |-> op_i inside { |
| CIPH_FWD, |
| CIPH_INV |
| }) |
| `ASSERT(AesKeyLenValid, cfg_valid_i |-> key_len_i inside { |
| AES_128, |
| AES_192, |
| AES_256 |
| }) |
| `ASSERT(AesCipherControlStateValid, !alert_o |-> aes_cipher_ctrl_cs inside { |
| IDLE, |
| INIT, |
| ROUND, |
| FINISH, |
| PRNG_RESEED, |
| CLEAR_S, |
| CLEAR_KD |
| }) |
| |
| endmodule |