diff --git a/hw/dv/sv/dv_utils/dv_utils_pkg.sv b/hw/dv/sv/dv_utils/dv_utils_pkg.sv index 5a36fe22fdf3d..fea93611117c5 100644 --- a/hw/dv/sv/dv_utils/dv_utils_pkg.sv +++ b/hw/dv/sv/dv_utils/dv_utils_pkg.sv @@ -92,11 +92,20 @@ package dv_utils_pkg; return val >= 0 ? val : -val; endfunction - // endian swap + // endian swaps a 32-bit data word function automatic logic [31:0] endian_swap(logic [31:0] data); return {<<8{data}}; endfunction + // endian swaps bytes at a word granularity, while preserving overall word ordering. + // + // e.g. if `arr[] = '{'h0, 'h1, 'h2, 'h3, 'h4, 'h5, 'h6, 'h7}`, this function will produce: + // `'{'h3, 'h2, 'h1, 'h0, 'h7, 'h6, 'h5, 'h4}` + function automatic void endian_swap_byte_arr(ref bit [7:0] arr[]); + arr = {<< byte {arr}}; + arr = {<< 32 {arr}}; + endfunction + `ifdef UVM // Simple function to set max errors before quitting sim function automatic void set_max_quit_count(int n); diff --git a/hw/ip/kmac/dv/env/kmac_env.core b/hw/ip/kmac/dv/env/kmac_env.core index 3b4d112fe54a4..0c17ffb2fac23 100644 --- a/hw/ip/kmac/dv/env/kmac_env.core +++ b/hw/ip/kmac/dv/env/kmac_env.core @@ -10,8 +10,10 @@ filesets: - lowrisc:dv:ralgen - lowrisc:dv:cip_lib - lowrisc:dv:push_pull_agent + - lowrisc:dv:str_utils - lowrisc:dv:test_vectors - lowrisc:dv:digestpp_dpi:0.1 + - lowrisc:ip:kmac files: - kmac_env_pkg.sv - kmac_sideload_if.sv diff --git a/hw/ip/kmac/dv/env/kmac_env_cfg.sv b/hw/ip/kmac/dv/env/kmac_env_cfg.sv index f7e852b55af61..2ccad7c1b980f 100644 --- a/hw/ip/kmac/dv/env/kmac_env_cfg.sv +++ b/hw/ip/kmac/dv/env/kmac_env_cfg.sv @@ -8,6 +8,9 @@ class kmac_env_cfg extends cip_base_env_cfg #(.RAL_T(kmac_reg_block)); idle_vif idle_vif; sideload_vif sideload_vif; + // Masked KMAC is the default configuration + bit enable_masking = 1; + `uvm_object_utils_begin(kmac_env_cfg) `uvm_object_utils_end diff --git a/hw/ip/kmac/dv/env/kmac_env_pkg.sv b/hw/ip/kmac/dv/env/kmac_env_pkg.sv index aad69f5afcfbf..cc96d3dfe51c2 100644 --- a/hw/ip/kmac/dv/env/kmac_env_pkg.sv +++ b/hw/ip/kmac/dv/env/kmac_env_pkg.sv @@ -9,12 +9,14 @@ package kmac_env_pkg; import dv_utils_pkg::*; import dv_lib_pkg::*; import tl_agent_pkg::*; + import str_utils_pkg::*; import test_vectors_pkg::*; import digestpp_dpi_pkg::*; import cip_base_pkg::*; import csr_utils_pkg::*; import push_pull_agent_pkg::*; import kmac_ral_pkg::*; + import kmac_pkg::*; import keymgr_pkg::*; // macro includes @@ -23,6 +25,27 @@ package kmac_env_pkg; // parameters + // max number of shares in design + parameter int KMAC_NUM_SHARES = 2; + + parameter int KMAC_NUM_KEYS_PER_SHARE = 16; + + // share1 of the 1600-bit KMAC state memory + parameter bit [TL_AW-1:0] KMAC_STATE_SHARE0_BASE = 32'h400; + parameter bit [TL_AW-1:0] KMAC_STATE_SHARE0_END = 32'h4C7; + + // share2 of the 1600-bit KMAC state memory + parameter bit [TL_AW-1:0] KMAC_STATE_SHARE1_BASE = 32'h500; + parameter bit [TL_AW-1:0] KMAC_STATE_SHARE1_END = 32'h5C7; + + // base and end addresses of the KAMC message FIFO. + parameter bit [TL_AW-1:0] KMAC_FIFO_BASE = 32'h800; + parameter bit [TL_AW-1:0] KMAC_FIFO_END = 32'hFFC; + + // Represents the max bit-width of some value to be encoded with either + // `right_encode()` or `left_encode()`. + parameter int MAX_ENCODE_WIDTH = 2040; + // Sideload data has 2*KeyWidth bits of key shares and 1 bit valid. parameter int SIDELOAD_KEY_SIZE = $bits(hw_key_req_t); // KDF request data has 1 bit for last, and the rest are for data/strb. @@ -33,10 +56,13 @@ package kmac_env_pkg; parameter int KDF_DIGEST_SIZE = $bits(kmac_data_rsp_t) - 1; // types - typedef enum { + + // interrupt types + typedef enum logic [1:0] { KmacDone, KmacFifoEmpty, - KmacErr + KmacErr, + KmacNumIntrs } kmac_intr_e; typedef virtual pins_if#(1) idle_vif; diff --git a/hw/ip/kmac/dv/env/kmac_scoreboard.sv b/hw/ip/kmac/dv/env/kmac_scoreboard.sv index d88f990b9e0a6..3059aaaa556b9 100644 --- a/hw/ip/kmac/dv/env/kmac_scoreboard.sv +++ b/hw/ip/kmac/dv/env/kmac_scoreboard.sv @@ -31,9 +31,10 @@ class kmac_scoreboard extends cip_base_scoreboard #( virtual task process_tl_access(tl_seq_item item, tl_channels_e channel = DataChannel); uvm_reg csr; - bit do_read_check = 1'b1; - bit write = item.is_write(); - uvm_reg_addr_t csr_addr = ral.get_word_aligned_addr(item.a_addr); + bit do_read_check = 1'b1; + bit write = item.is_write(); + uvm_reg_addr_t csr_addr = ral.get_word_aligned_addr(item.a_addr); + bit [TL_AW-1:0] csr_addr_mask = ral.get_addr_mask(); bit addr_phase_read = (!write && channel == AddrChannel); bit addr_phase_write = (write && channel == AddrChannel); @@ -44,8 +45,15 @@ class kmac_scoreboard extends cip_base_scoreboard #( if (csr_addr inside {cfg.csr_addrs}) begin csr = ral.default_map.get_reg_by_offset(csr_addr); `DV_CHECK_NE_FATAL(csr, null) - end - else begin + end else if ((csr_addr & csr_addr_mask) inside {[KMAC_FIFO_BASE:KMAC_FIFO_END]}) begin + // msgfifo window + return; + end else if ((csr_addr & csr_addr_mask) inside + {[KMAC_STATE_SHARE0_BASE:KMAC_STATE_SHARE0_END], + [KMAC_STATE_SHARE1_BASE:KMAC_STATE_SHARE1_END]}) begin + // state digest window + return; + end else begin `uvm_fatal(`gfn, $sformatf("Access unexpected addr 0x%0h", csr_addr)) end @@ -69,8 +77,13 @@ class kmac_scoreboard extends cip_base_scoreboard #( "intr_test": begin // FIXME end + "status": begin + // STATUS is read only + do_read_check = 1'b0; + end default: begin - `uvm_fatal(`gfn, $sformatf("invalid csr: %0s", csr.get_full_name())) + // TODO: uncomment after implementing scoreboard + //`uvm_fatal(`gfn, $sformatf("invalid csr: %0s", csr.get_full_name())) end endcase diff --git a/hw/ip/kmac/dv/env/seq_lib/kmac_base_vseq.sv b/hw/ip/kmac/dv/env/seq_lib/kmac_base_vseq.sv index ef045cc8d2b36..5b806071b1dfd 100644 --- a/hw/ip/kmac/dv/env/seq_lib/kmac_base_vseq.sv +++ b/hw/ip/kmac/dv/env/seq_lib/kmac_base_vseq.sv @@ -8,12 +8,182 @@ class kmac_base_vseq extends cip_base_vseq #( .COV_T (kmac_env_cov), .VIRTUAL_SEQUENCER_T (kmac_virtual_sequencer) ); + `uvm_object_utils(kmac_base_vseq) + `uvm_object_new + + // used to randomly enable interrupts + rand bit [TL_DW-1:0] enable_intr; + + // enable KMAC hashing + rand bit kmac_en; + + // This bit only applies to KMAC hashing operation. + // + // KMAC has two operation modes, one with known output length, + // and with an unknown output length (as an XOF). + // + // When output length is known we write `right_encode(len)` to msgfifo + // after the input message, but to enable the XOF mode we must write `right_encode(0)` + // to the msgfifo after the input message. + // + // This bit enables the XOF mode of operation, and causes the correct output length + // encoding to be written into the msgfifo. + rand bit xof_en; + + // bit-length of the incoming key + rand kmac_pkg::key_len_e key_len; + + // hashing mode (sha3/shake/cshake) + rand sha3_pkg::sha3_mode_e hash_mode; + + // security strength + rand sha3_pkg::keccak_strength_e strength; + + // endianness of message and secret key. + // 0: little endian + // 1: big endian + rand bit msg_endian; + + // endianness of output digest. + // 0: keep state endianness as is + // 1: convert state to big endian + rand bit state_endian; + + // set to provide the KMAC with a key through the sideload interface. + // does not control whether the cfg.sideload field is set. + rand bit provide_sideload_key; + + // Controls whether to actually enable sideloading functionality + rand bit en_sideload; + + // entropy mode + rand kmac_pkg::entropy_mode_e entropy_mode; + + // entropy fast process mode + rand bit entropy_fast_process; + + // entropy ready + rand bit entropy_ready; + + // error process + rand bit err_processed; + + // output length in bytes. + rand int unsigned output_len; + + // Keccak block size - used only for variable-length output functions. + // strength128 -> 168 + // strength256 -> 136 + rand int unsigned keccak_block_size; + + // Function name and customization string length in bytes (1 char = 1 byte). + // Since we cannot create random strings, we simplify by controlling the length of the fname. + rand int unsigned fname_len; + rand int unsigned custom_str_len; + + // Used to store N and S after encoding them + rand byte fname_arr[]; + rand byte custom_str_arr[]; + + // input msg - assume this is little endian + rand bit [7:0] msg[]; + + // 2 key shares - are XORed together to produce intended secret key + // + // max key length is 512 bits + rand bit [KMAC_NUM_SHARES-1:0][KMAC_NUM_KEYS_PER_SHARE-1:0][31:0] key_share; + + // address used when writing to message fifo + rand bit [TL_AW-1:0] fifo_addr; + + // data mask used when writing to message fifo + rand bit [TL_DBW-1:0] data_mask; + + // array to store `right_encode(output_len)` + bit [7:0] output_len_enc[]; - // various knobs to enable certain routines bit do_kmac_init = 1'b1; - `uvm_object_new + // constrain xof_en to 0 if not in kmac mode + constraint xof_en_c { + (!kmac_en) -> (xof_en == 1'b0); + } + + // KMAC HWIP only uses CSHAKE mode for KMAC hashing + constraint hash_mode_c { + if (kmac_en) { + hash_mode == sha3_pkg::CShake; + } else { + hash_mode != sha3_pkg::CShake; + } + } + + // output length constraints when using any SHA3 mode + constraint output_len_sha3_c { + if (hash_mode == sha3_pkg::Sha3) { + (strength == sha3_pkg::L224) -> (output_len == 28); + (strength == sha3_pkg::L256) -> (output_len == 32); + (strength == sha3_pkg::L384) -> (output_len == 48); + (strength == sha3_pkg::L512) -> (output_len == 64); + } + } + + constraint strength_c { + // only 128/256 bit strengths are supported for the XOFs + (hash_mode inside {sha3_pkg::Shake, sha3_pkg::CShake}) -> + (strength inside {sha3_pkg::L128, sha3_pkg::L256}); + + // SHA3-128 is not supported + (hash_mode == sha3_pkg::Sha3) -> (strength != sha3_pkg::L128); + } + + // Set the block size based on the random security strength. + // This is only relevant for XOF functions (L128 or L256), but will be + // set for all security strengths. + // + // The block size is calculated by: + // `(1600 - (2 * strength_in_bits)) / 8` + constraint keccak_block_size_c { + (strength == sha3_pkg::L128) -> (keccak_block_size == 168); + (strength == sha3_pkg::L224) -> (keccak_block_size == 144); + (strength == sha3_pkg::L256) -> (keccak_block_size == 136); + (strength == sha3_pkg::L384) -> (keccak_block_size == 104); + (strength == sha3_pkg::L512) -> (keccak_block_size == 72); + } + + // constrain the range of addresses we can access msg_fifo through + constraint fifo_addr_c { + fifo_addr inside {[KMAC_FIFO_BASE : KMAC_FIFO_END]}; + } + + // as per TL-UL spec, data mask must have contiguous 1s. + // + // we duplicate this constraint here as we manually write to the msgfifo window + // via the tl_access() method. + constraint data_mask_contiguous_c { + $countones(data_mask ^ {data_mask[TL_DBW-2:0], 1'b0}) <= 2; + } + + // as per spec, len(N+S) can be at most 288 bits (36 bytes). + constraint prefix_len_c { + fname_len + custom_str_len <= 36; + + fname_arr.size() == fname_len; + custom_str_arr.size() == custom_str_len; + } + + // constrains N and S to only be valid alphabet letters and space [A-Za-z] + constraint prefix_is_char_c { + foreach(fname_arr[i]) { + // space | A-Z | a-z + fname_arr[i] inside {32, [65:90], [97:122]}; + } + foreach(custom_str_arr[i]) { + // space | A-Z | a-z + custom_str_arr[i] inside {32, [65:90], [97:122]}; + } + } virtual task dut_init(string reset_kind = "HARD"); super.dut_init(); @@ -28,7 +198,566 @@ class kmac_base_vseq extends cip_base_vseq #( // setup basic kmac features virtual task kmac_init(); // Wait for KMAC to reach idle state - wait (cfg.idle_vif.sample_pin() == 1); + wait (cfg.idle_vif.pins == 1); + `uvm_info(`gfn, "reached idle state", UVM_HIGH) + + // set interrupts + cfg_interrupts(.interrupts(enable_intr)); + `uvm_info(`gfn, $sformatf("intr[KmacDone] = %0b", enable_intr[KmacDone]), UVM_HIGH) + `uvm_info(`gfn, $sformatf("intr[KmacFifoEmpty] = %0b", enable_intr[KmacFifoEmpty]), UVM_HIGH) + `uvm_info(`gfn, $sformatf("intr[KmacErr] = %0b", enable_intr[KmacErr]), UVM_HIGH) + + // setup CFG csr + ral.cfg.kmac_en.set(kmac_en); + ral.cfg.kstrength.set(strength); + ral.cfg.mode.set(hash_mode); + ral.cfg.msg_endianness.set(msg_endian); + ral.cfg.state_endianness.set(state_endian); + ral.cfg.sideload.set(en_sideload); + ral.cfg.entropy_mode.set(entropy_mode); + ral.cfg.entropy_fast_process.set(entropy_fast_process); + ral.cfg.entropy_ready.set(entropy_ready); + ral.cfg.err_processed.set(err_processed); + csr_update(.csr(ral.cfg)); + + // setup KEY_LEN csr + csr_wr(.csr(ral.key_len), .value(key_len)); + + // print debug info + `uvm_info(`gfn, $sformatf("KMAC INITIALIZATION INFO:\n%0s", convert2string()), UVM_HIGH) + endtask + + virtual function string convert2string(); + return {$sformatf("enable_intr: %0p\n", enable_intr), + $sformatf("kmac_en: %0b\n", kmac_en), + $sformatf("xof_en: %0b\n", xof_en), + $sformatf("hash_mode: %0s\n", hash_mode.name()), + $sformatf("strength: %0s\n", strength.name()), + $sformatf("keccak_block_size: %0d\n", keccak_block_size), + $sformatf("key_len: %0s\n", key_len.name()), + $sformatf("output_len %0d\n", output_len), + $sformatf("msg_endian: %0b\n", msg_endian), + $sformatf("state_endian: %0b\n", state_endian), + $sformatf("en_sideload: %0b\n", en_sideload), + $sformatf("provide_sideload_key: %0b\n", provide_sideload_key), + $sformatf("fname_arr: %0p\n", fname_arr), + $sformatf("fname: %0s\n", str_utils_pkg::bytes_to_str(fname_arr)), + $sformatf("custom_str_arr: %0p\n", custom_str_arr), + $sformatf("custom_str: %0s\n", str_utils_pkg::bytes_to_str(custom_str_arr)), + $sformatf("entropy_mode: %0s\n", entropy_mode.name()), + $sformatf("entropy_fast_process: %0b\n", entropy_fast_process), + $sformatf("entropy_ready: %0b\n", entropy_ready), + $sformatf("err_processed: %0b", err_processed)}; + endfunction + + // This function implements the bulk of integer encoding specified by NIST.SP.800-185 + // + // As per spec, this function is valid for any 0 >= x > 2**2040 + // + // Set `left_right` to 1'b1 for left encoding, and 1'b0 for right encoding + // + // Input `q` is assumed to be empty + function void encode(bit [MAX_ENCODE_WIDTH-1:0] x, bit left_right, ref bit [7:0] q[$]); + bit [7:0] b_str[$]; + byte b; + if (x == 0) begin + q.push_back(8'b0); + end else begin + // split the input number into a stream of bytes + while (x > 0) begin + b = x[7:0]; + x = x >> 8; + q.push_back(b); + end + // append the size of the queue at the front or back of the queue, + // depending whether we are left encoding or right encoding + end + // reverse bytes of q before prepending/appending q.size(). + {<< byte {q}} = q; + b = q.size(); + if (left_right) begin + q.push_front(b); + end else begin + q.push_back(b); + end + endfunction + + // This function performs the `left_encode(x)` function specified by NIST.SP.800-185 + // + // As per spec, this function is valid for any 0 >= x > 2**2040 + function void left_encode(bit [MAX_ENCODE_WIDTH-1:0] x, ref bit [7:0] arr[]); + bit [7:0] q[$]; + encode(x, 1'b1, q); + arr = q; + endfunction + + // This function performs the `right_encode(x)` specified by NIST.SP.800-185 + // + // As per spec, this function is valid for any 0 >= x > 2**2040 + function void right_encode(bit [MAX_ENCODE_WIDTH-1:0] x, ref bit [7:0] arr[]); + bit [7:0] q[$]; + encode(x, 1'b0, q); + arr = q; + endfunction + + // This function implements the `encode_string(S)` function specified by NIST.SP.800-185 + // + // As per spec, this function is valid for any 0 >= bytes.size() > 2**2040. + // Undefined behavior may result if this condition is broken. + function void encode_string(byte bytes[], ref bit [7:0] arr[]); + bit [7:0] encoded_len[]; + + // convert from type `byte` to type `bit [7:0]` + foreach (bytes[i]) begin + arr[i] = 8'(bytes[i]); + end + + left_encode(bytes.size() * 8, encoded_len); + `uvm_info(`gfn, $sformatf("encoded_str_len: %0p", encoded_len), UVM_HIGH) + arr = {encoded_len, arr}; + endfunction + + // helper function - takes in the key_len_e enum, and returns + // its size in bytes. + // e.g. if key is 128 bits long, this function will return 16. + function int get_key_size_bytes(kmac_pkg::key_len_e len); + case (len) + Key128: return 16; + Key192: return 24; + Key256: return 32; + Key384: return 48; + Key512: return 64; + default: `uvm_fatal(`gfn, $sformatf("%0s is an invalid key length!", len)) + endcase + endfunction + + // Returns the key size in 32-bit words. + function int get_key_size_words(kmac_pkg::key_len_e len); + return (get_key_size_bytes(len) / 4); + endfunction + + // Returns the key size in 64-bit blocks. + function int get_key_size_blocks(kmac_pkg::key_len_e len); + return (get_key_size_words(len) / 2); + endfunction + + // This task will encode the function name and customization string, + // and write them to the PREFIX csrs. + virtual task set_prefix(); + bit [7:0] encoded_fname[] = new[fname_arr.size()]; + bit [7:0] encoded_custom_str[] = new[custom_str_arr.size()]; + bit [7:0] prefix_bytes[] = new[fname_arr.size() + custom_str_arr.size()]; + + // PREFIX csr provides 288 bits for N+S, and 64 bits for the encoded lengths + bit [31:0] prefix_arr[11]; + + // encode function name and string + encode_string(fname_arr, encoded_fname); + encode_string(custom_str_arr, encoded_custom_str); + `uvm_info(`gfn, $sformatf("encoded_fname: %0p", encoded_fname), UVM_HIGH) + `uvm_info(`gfn, $sformatf("encoded_custom_str: %0p", encoded_custom_str), UVM_HIGH) + + // stream into chunks of 32-bits (size of the PREFIX csr) + prefix_bytes = {<< byte {encoded_fname, encoded_custom_str}}; + prefix_arr = {<< 32 {prefix_bytes}}; + `uvm_info(`gfn, $sformatf("prefix_arr: %0p", prefix_arr), UVM_HIGH) + + // write to PREFIX csrs + // + // We need to overwrite all 10 of them each time we configure the prefix, as the customization + // string is not guaranteed to be the full 256 bits every time, meaning that we might leave + // stale information from the previous iteration otherwise. + foreach (prefix_arr[i]) begin + string csr_name = $sformatf("prefix_%0d", i); + uvm_reg csr = ral.get_reg_by_name(csr_name); + csr_wr(.csr(csr), .value(prefix_arr[i])); + end + endtask + + // This task writes the given command to the CMD csr + virtual task issue_cmd(kmac_cmd_e cmd); + csr_wr(.csr(ral.cmd), .value(cmd)); + endtask + + // This task writes both key shares to the appropriate CSRs + virtual task write_key_shares(); + // write keys to both shares by default regardless of masking configuration + for (int i = 0; i < KMAC_NUM_SHARES; i++) begin + for (int j = 0; j < KMAC_NUM_KEYS_PER_SHARE; j++) begin + string csr_name = $sformatf("key_share%0d_%0d", i, j); + uvm_reg csr = ral.get_reg_by_name(csr_name); + csr_wr(.csr(csr), .value(key_share[i][j])); + end + end + + // debug - print out all keys + for (int i = 0; i < KMAC_NUM_SHARES; i++) begin + for (int j = 0; j < KMAC_NUM_KEYS_PER_SHARE; j++) begin + `uvm_info(`gfn, $sformatf("key_share[%0d][%0d] = 0x%0x", i, j, key_share[i][j]), UVM_HIGH) + end + end + endtask + + virtual task provide_sw_entropy(); + csr_wr(.csr(ral.entropy_seed_lower), .value($urandom())); + csr_wr(.csr(ral.entropy_seed_upper), .value($urandom())); + endtask + + // This task writes a generic byte array into the msg_fifo + // + // The general flow of this task is this: + // + // - Read STATUS csr to make sure there is enough room in the msgfifo + // - If not, csr_spinwait until there is room + // + // - Pick a random address in the FIFO window. + // + // - Pick a random write mask, making sure that if there are M<4 bytes in the message then there + // are at most M enabled bits in the write mask. + // This behavior only applies + // + // - Pick a random write mask: + // + // - If message is little endian, we can fully randomize the mask, making sure that + // if there are M<4 bytes left in the message then there are at most M enabled bits + // in the mask. This allows us to write <=4 bytes at a time which is ok since + // little endian ordering will be preserved. + // + // - If message is big endian, things get more complicated. + // Since KMAC only endian-swaps the bytes in each word, and leaves word order alone, + // we need to write a full word at a time to the msgfifo (or the remainder of the message + // if M<4 bytes remain). + // This is because writing partial words with the big-endian scheme used by KMAC will mess up + // byte ordering and cause incorrect message to be accumulated. + // + // - Pull N bytes from the message queue, where N is the number of enable bits in the data mask + // + // - Stitch these N bytes together into a data word based off of the write mask + // - Bytes from the message are only placed in locations corresponding to + // an enabled bit in the mask + // - All other bytes in the word are randomized + // + // - Perform a TLUL access to the msgfifo with the random data/addr/mask, relying on tl_agent + // to correctly align the final address and data size + virtual task write_msg(bit [7:0] msg_arr[]); + + bit [TL_DW-1:0] data_word; + bit [7:0] msg_q[$]; + + `uvm_info(`gfn, $sformatf("msg_arr: %0p", msg_arr), UVM_HIGH) + if (msg_endian) dv_utils_pkg::endian_swap_byte_arr(msg_arr); + `uvm_info(`gfn, $sformatf("msg_arr_swapped: %0p", msg_arr), UVM_HIGH) + + // Use a queue for the msgfifo writes to make life in the TB way easier. + msg_q = msg_arr; + + `uvm_info(`gfn, $sformatf("initial msg: %0p", msg_q), UVM_HIGH) + + // iterate through the message queue and pop off bytes to write to msgfifo + while (msg_q.size() > 0) begin + // check that there is actually room in the fifo before we start writing anything + wait_fifo_has_capacity(); + + `DV_CHECK_MEMBER_RANDOMIZE_FATAL(fifo_addr) + + // If message is written in little-endian format, we ensure there are at most the same number + // of enable bits in the data mask as there are bytes remaining in the message queue. + // This allows us to perform partial word writes to the msgfifo, and handles scenarios where + // less than 4 bytes of message remain. + // + // If message is written in big-endian format, we ensure that we write a full word at a time + // to the msgfifo (or the remainder of the message queue if <4 bytes remain). + // This is due to KMAC expecting that big-endian messages preserve word ordering and only + // endian-swap the bytes in each word. + `DV_CHECK_MEMBER_RANDOMIZE_WITH_FATAL(data_mask, $countones(data_mask) <= msg_q.size(); + if (msg_endian) { + if (msg_q.size() < TL_DBW) { + soft $countones(data_mask) == msg_q.size(); + } else { + soft $countones(data_mask) == TL_DBW; + } + }) + // Randomize the data word to write into the message fifo. + // Valid byte positions will be overwritten with bytes from the actual message. + `DV_CHECK_STD_RANDOMIZE_FATAL(data_word) + + // Replace valid bytes in the data_word with bytes from the message queue. + for (int i = 0; i < TL_DBW; i++) begin + if (data_mask[i]) begin + data_word[i*8 +: 8] = msg_q.pop_front(); + `uvm_info(`gfn, $sformatf("intermediate data_word: 0x%0x", data_word), UVM_HIGH) + end + end + + // print some debug info before performing the TLUL write + `uvm_info(`gfn, $sformatf("msg_size: %0d", msg_q.size()), UVM_HIGH) + `uvm_info(`gfn, $sformatf("fifo_addr = 0x%0x", fifo_addr), UVM_HIGH) + `uvm_info(`gfn, $sformatf("data_word = 0x%0x", data_word), UVM_HIGH) + `uvm_info(`gfn, $sformatf("data_mask = 0x%0x", data_mask), UVM_HIGH) + + // Write to the msgfifo + // + // TODO: randomize non/blocking? + tl_access(.addr(ral.get_addr_from_offset(fifo_addr)), + .write(1), + .data(data_word), + .mask(data_mask)); + end + + // wait for all msgfifo accesses to complete + // + // TODO: uncomment once nonblocking TL accesses are enabled. + // wait_no_outstanding_access(); + + // TODO: final csr checks might be needed + endtask + + // This task checks the fifo_empty interrupt (if enabled) and clears it, + // then waits for STATUS.fifo_full to be 0. + virtual task wait_fifo_has_capacity(); + bit [TL_DW-1:0] irq_data; + + // read out interrupt state and clear any set interrupts (fifo_empty). + // + // TODO: might need a way to determine when to check fifo-related state in the scoreboard + csr_rd(.ptr(ral.intr_state), .value(irq_data)); + csr_wr(.csr(ral.intr_state), .value(irq_data)); + + csr_spinwait(.ptr(ral.status.fifo_full), .exp_data(1'b0)); + endtask + + // This task waits for the kmac_done interrupt to trigger, + // or waits for ral.intr_state.kmac_done to be high if interrupts are disabled. + virtual task wait_for_kmac_done(); + bit [TL_DW-1:0] intr_state; + if (enable_intr[KmacDone]) begin + wait(cfg.intr_vif.pins[KmacDone] == 1'b1); + check_interrupts(.interrupts(1 << KmacDone), + .check_set(1'b1)); + end else begin + csr_spinwait(.ptr(ral.intr_state.kmac_done), .exp_data(1'b1)); + end + // read and clear intr_state + csr_rd(.ptr(ral.intr_state), .value(intr_state)); + csr_wr(.csr(ral.intr_state), .value(intr_state)); + endtask + + // This task reads the contents of the STATE window and populates the contents of `digest` with + // the output data. + // + // The general idea is: + // - Read 4-byte words from the STATE window + // - Add each byte to a queue, decrementing the total output_len each time + // + // TODO: Support squeezing output data for >keccak_block_size B output. + // Smoke test constrains output length to be below keccak_block_size. + virtual task read_digest(bit [TL_AW-1:0] state_addr, + int unsigned output_len, + ref bit [7:0] digest[]); + bit [TL_DW-1:0] digest_word; + bit [7:0] digest_byte; + + while (output_len > 0) begin + // TODO: randomize non/blocking? + tl_access(.addr(ral.get_addr_from_offset(state_addr)), + .write(1'b0), + .data(digest_word)); + + `uvm_info(`gfn, $sformatf("digest_word: 0x%0x", digest_word), UVM_HIGH) + + // If endianness is swapped for the state digest, byte-swap each word that is read. + if (state_endian) begin + digest_word = {<< byte {digest_word}}; + `uvm_info(`gfn, $sformatf("digest_word after endian swap: 0x%0x", digest_word), UVM_HIGH) + end + + for (int i = 0; i < TL_DW / 8; i++) begin + // safety check - if the output length has reached 0 due to being decremented, + // it indicates that we've finished reading the full digest and need to exit immediately. + if (output_len == 0) break; + + digest_byte = digest_word[i*8 +: 8]; + + digest = {digest, digest_byte}; + + // decrement the output_len, as we've just popped off a byte + output_len = output_len - 1; + end + + // Increment the address into the state window to read the next word + state_addr = state_addr + 4; + end + + endtask + + // This task reads the output digest and compares it to digest produced by reference model + virtual task check_digest(); + // Note that all three of these will be the same length + bit [7:0] digest_share0[]; + bit [7:0] digest_share1[]; + bit [7:0] unmasked_digest[] = new[output_len]; + + // # of 32-bit entries in the key + int key_word_len = get_key_size_words(key_len); + // # of bytes in the key + int key_byte_len = get_key_size_bytes(key_len); + + // if masking is enabled, need to XOR both key shares to get "actual" key + bit [31:0] unmasked_key[$]; + // DPI model needs to take in a byte-stream for the key + bit [7:0] dpi_key_arr[]; + // Intermediate array for streaming `unmasked_key` into `dpi_key_arr` + //bit [7:0] unmasked_key_bytes[] = new[get_key_size_bytes(key_len)]; + bit [7:0] unmasked_key_bytes[]; + + // output digest from DPI model + bit [7:0] dpi_digest[] = new[output_len]; + + `uvm_info(`gfn, $sformatf("key_word_len: %0d", key_word_len), UVM_HIGH) + `uvm_info(`gfn, $sformatf("key_byte_len: %0d", key_byte_len), UVM_HIGH) + + // Read out both digest shares + read_digest(KMAC_STATE_SHARE0_BASE, output_len, digest_share0); + `uvm_info(`gfn, $sformatf("digest_share0: %0p", digest_share0), UVM_HIGH) + // if masking disabled, read the full share1 digest so we can check it is 0. + read_digest(KMAC_STATE_SHARE1_BASE, (cfg.enable_masking) ? output_len : 200, digest_share1); + `uvm_info(`gfn, $sformatf("digest_share1: %0p", digest_share1), UVM_HIGH) + + `uvm_info(`gfn, $sformatf("msg input to DPI: %0p", msg), UVM_HIGH) + + //////////////////////////////////////////////////// + // Below logic should eventually go in scoreboard // + //////////////////////////////////////////////////// + + // If masking is enabled, we need to XOR both shares of the digest and key + // to get the "actual" unmasked versions. + // + // If masking is disabled, share1 of the digest must be 0, and + // we will only care about share0 of the key. + if (cfg.enable_masking) begin + // XOR the digest + for (int i = 0; i < output_len; i++) begin + unmasked_digest[i] = digest_share0[i] ^ digest_share1[i]; + end + // XOR the key + for (int i = 0; i < key_word_len; i++) begin + unmasked_key.push_back(key_share[0][i] ^ key_share[1][i]); + end + end else begin + // check that share1 of digest is 0 - 200 bytes in total. + for (int i = 0; i < 200; i++) begin + `DV_CHECK_EQ_FATAL(digest_share1[i], 0, + $sformatf("digest_share1[%0d] is not 0!", i)) + end + // the digest is just share0 + unmasked_digest = digest_share0; + // the "actual" key is just share0 of the key + for (int i = 0; i < key_word_len; i++) begin + unmasked_key.push_back(key_share[0][i]); + end + end + + // Based on random settings, determine which DPI function to call + case (hash_mode) + sha3_pkg::Sha3: begin + case (strength) + sha3_pkg::L224: begin + digestpp_dpi_pkg::c_dpi_sha3_224(msg, msg.size(), dpi_digest); + end + sha3_pkg::L256: begin + digestpp_dpi_pkg::c_dpi_sha3_256(msg, msg.size(), dpi_digest); + end + sha3_pkg::L384: begin + digestpp_dpi_pkg::c_dpi_sha3_384(msg, msg.size(), dpi_digest); + end sha3_pkg::L512: begin + digestpp_dpi_pkg::c_dpi_sha3_512(msg, msg.size(), dpi_digest); + end + default: begin + `uvm_fatal(`gfn, $sformatf("strength[%0s] is not allowed for sha3!", strength.name())) + end + endcase + end + sha3_pkg::Shake: begin + case (strength) + sha3_pkg::L128: begin + digestpp_dpi_pkg::c_dpi_shake128(msg, msg.size(), output_len, dpi_digest); + end + sha3_pkg::L256: begin + digestpp_dpi_pkg::c_dpi_shake256(msg, msg.size(), output_len, dpi_digest); + end + default: begin + `uvm_fatal(`gfn, $sformatf("strength[%0s] is not allowed for shake!", strength.name())) + end + endcase + end + sha3_pkg::CShake: begin + `uvm_info(`gfn, $sformatf("fname_str: %0s", str_utils_pkg::bytes_to_str(fname_arr)), UVM_HIGH) + `uvm_info(`gfn, $sformatf("custom_str: %0s", str_utils_pkg::bytes_to_str(custom_str_arr)), UVM_HIGH) + if (kmac_en) begin + // Convert the key array into a byte array for the DPI model + unmasked_key_bytes = {<< 32 {unmasked_key}}; + dpi_key_arr = {<< byte {unmasked_key_bytes}}; + `uvm_info(`gfn, $sformatf("dpi_key_arr.size(): %0d", dpi_key_arr.size()), UVM_HIGH) + `uvm_info(`gfn, $sformatf("dpi_key_arr: %0p", dpi_key_arr), UVM_HIGH) + case (strength) + sha3_pkg::L128: begin + if (xof_en) begin + digestpp_dpi_pkg::c_dpi_kmac128_xof(msg, msg.size(), + dpi_key_arr, dpi_key_arr.size(), + str_utils_pkg::bytes_to_str(custom_str_arr), + output_len, dpi_digest); + end else begin + digestpp_dpi_pkg::c_dpi_kmac128(msg, msg.size(), + dpi_key_arr, dpi_key_arr.size(), + str_utils_pkg::bytes_to_str(custom_str_arr), + output_len, dpi_digest); + end + end + sha3_pkg::L256: begin + if (xof_en) begin + digestpp_dpi_pkg::c_dpi_kmac256_xof(msg, msg.size(), + dpi_key_arr, dpi_key_arr.size(), + str_utils_pkg::bytes_to_str(custom_str_arr), + output_len, dpi_digest); + end else begin + digestpp_dpi_pkg::c_dpi_kmac256(msg, msg.size(), + dpi_key_arr, dpi_key_arr.size(), + str_utils_pkg::bytes_to_str(custom_str_arr), + output_len, dpi_digest); + end + end + default: begin + `uvm_fatal(`gfn, $sformatf("strength[%0s] is not allowed for kmac!", strength.name())) + end + endcase + end else begin + case (strength) + sha3_pkg::L128: begin + digestpp_dpi_pkg::c_dpi_cshake128(msg, str_utils_pkg::bytes_to_str(fname_arr), + str_utils_pkg::bytes_to_str(custom_str_arr), + msg.size(), output_len, dpi_digest); + end + sha3_pkg::L256: begin + digestpp_dpi_pkg::c_dpi_cshake256(msg, str_utils_pkg::bytes_to_str(fname_arr), + str_utils_pkg::bytes_to_str(custom_str_arr), + msg.size(), output_len, dpi_digest); + end + default: begin + `uvm_fatal(`gfn, $sformatf("strength[%0s] is not allowed for cshake!", strength.name())) + end + endcase + end + end + endcase + + `uvm_info(`gfn, $sformatf("unmasked_digest: %0p", unmasked_digest), UVM_HIGH) + `uvm_info(`gfn, $sformatf("dpi_digest: %0p", dpi_digest), UVM_HIGH) + + // Compare outputs from DPI model and DUT + for (int i = 0; i < unmasked_digest.size(); i++) begin + `DV_CHECK_EQ_FATAL(unmasked_digest[i], dpi_digest[i], + $sformatf("Mismatch between unmasked_digest[%0d] and dpi_digest[%0d]", i, i)) + end endtask endclass : kmac_base_vseq diff --git a/hw/ip/kmac/dv/env/seq_lib/kmac_common_vseq.sv b/hw/ip/kmac/dv/env/seq_lib/kmac_common_vseq.sv index 2f98f17bfba18..b0c0e4fcad386 100644 --- a/hw/ip/kmac/dv/env/seq_lib/kmac_common_vseq.sv +++ b/hw/ip/kmac/dv/env/seq_lib/kmac_common_vseq.sv @@ -11,6 +11,7 @@ class kmac_common_vseq extends kmac_base_vseq; `uvm_object_new virtual task body(); + do_kmac_init = 1'b0; run_common_vseq_wrapper(num_trans); endtask : body diff --git a/hw/ip/kmac/dv/env/seq_lib/kmac_smoke_vseq.sv b/hw/ip/kmac/dv/env/seq_lib/kmac_smoke_vseq.sv index 480f2f2011f4f..0a21b2964b758 100644 --- a/hw/ip/kmac/dv/env/seq_lib/kmac_smoke_vseq.sv +++ b/hw/ip/kmac/dv/env/seq_lib/kmac_smoke_vseq.sv @@ -4,11 +4,126 @@ // smoke test vseq class kmac_smoke_vseq extends kmac_base_vseq; - `uvm_object_utils(kmac_smoke_vseq) + `uvm_object_utils(kmac_smoke_vseq) `uvm_object_new + // TODO: 200 is chosen as upper bound due to large configuration space for KMAC. + // If this large range causes noticeable simulation slowdown, reduce it. + constraint num_trans_c { + num_trans inside {[1:200]}; + } + + // If in KMAC mode, the function name has to be "KMAC" string + constraint legal_fname_c { + if (kmac_en) { + fname_len == 4; + fname_arr[0] == 75; // "K" + fname_arr[1] == 77; // "M" + fname_arr[2] == 65; // "A" + fname_arr[3] == 67; // "C" + } else { + fname_len == 0; + } + } + + constraint custom_str_len_c { + custom_str_len == 0; + } + + constraint en_sideload_c { + en_sideload == 0; + } + + constraint entropy_mode_c { + entropy_mode == EntropyModeSw; + } + + constraint entropy_fast_process_c { + entropy_fast_process == 0; + } + + constraint entropy_ready_c { + entropy_ready == 1; + } + + constraint err_processed_c { + err_processed == 0; + } + + // Constraint output byte length to be at most the keccak block size (168/136). + // This way we can read the entire digest without having to manually squeeze data. + constraint output_len_c { + output_len inside {[1:keccak_block_size]}; + } + + // for smoke test keep message below 32 bytes + constraint msg_c { + msg.size() dist { + 0 :/ 1, + [1:32] :/ 9 + }; + } + + // We want to disable do_kmac_init here because we wil re-initialize the KMAC each time we do + // a message hash. + virtual task pre_start(); + do_kmac_init = 0; + super.pre_start(); + endtask + + // Do a full message hash, repeated num_trans times task body(); + `uvm_info(`gfn, $sformatf("Starting %0d message hashes", num_trans), UVM_LOW) + for (int i = 0; i < num_trans; i++) begin + `uvm_info(`gfn, $sformatf("iteration: %0d", i), UVM_HIGH) + + `DV_CHECK_RANDOMIZE_FATAL(this) + + kmac_init(); + `uvm_info(`gfn, "kmac_init done", UVM_HIGH) + + set_prefix(); + + // TODO: drive a sideload key + + // write the keys + if (kmac_en) write_key_shares(); + + if (cfg.enable_masking && entropy_mode == EntropyModeSw) begin + provide_sw_entropy(); + end + + // issue Start cmd + issue_cmd(CmdStart); + + // write the message into msgfifo + `uvm_info(`gfn, $sformatf("msg: %0p", msg), UVM_HIGH) + write_msg(msg); + + // if using KMAC, need to write either encoded output length or 0 to msgfifo + if (kmac_en) begin + right_encode(xof_en ? 0 : output_len * 8, output_len_enc); + `uvm_info(`gfn, $sformatf("output_len_enc: %0p", output_len_enc), UVM_HIGH) + write_msg(output_len_enc); + end + + // issue Process cmd + issue_cmd(CmdProcess); + + // wait for kmac_done to be set + wait_for_kmac_done(); + + // compare the output digest against C++ reference model + check_digest(); + + // issue the Done cmd to tell KMAC to clear internal state + issue_cmd(CmdDone); + `uvm_info(`gfn, "done", UVM_HIGH) + + // TODO: randomly read out the digest after issuing Done command, expect both shares = 0 + end + endtask : body endclass : kmac_smoke_vseq diff --git a/hw/ip/kmac/dv/kmac_base_sim_cfg.hjson b/hw/ip/kmac/dv/kmac_base_sim_cfg.hjson index 854d0b2b4df41..b56459b355b38 100644 --- a/hw/ip/kmac/dv/kmac_base_sim_cfg.hjson +++ b/hw/ip/kmac/dv/kmac_base_sim_cfg.hjson @@ -50,12 +50,12 @@ { name: enable_mask_mode build_opts: ["+define+EN_MASKING=1", "+define+REUSE_SHARE=0"] - run_opts: ["+enable_masking=1"] + run_opts: ["+enable_masking=1", "+test_timeout_ns=2000000000"] } { name: disable_mask_mode build_opts: ["+define+EN_MASKING=0", "+define+REUSE_SHARE=0"] - run_opts: ["+enable_masking=0"] + run_opts: ["+enable_masking=0", "+test_timeout_ns=2000000000"] } ] diff --git a/hw/ip/kmac/dv/tb.sv b/hw/ip/kmac/dv/tb.sv index 358fae24e804a..2a62b871a0ba4 100644 --- a/hw/ip/kmac/dv/tb.sv +++ b/hw/ip/kmac/dv/tb.sv @@ -65,16 +65,15 @@ module tb; // Idle interface .idle_o (idle ), - // TODO: hook up interfaces for: - // - // 1) KDF - // 2) csrng/edn - - // edn + // EDN interface .clk_edn_i (edn_clk ), .rst_edn_ni (edn_rst_n ), .entropy_o (edn_if.req ), .entropy_i ({edn_if.ack, edn_if.d_data}) + + // TODO: hook up interfaces for: + // + // 1) KDF ); // Interface assignments diff --git a/hw/ip/kmac/dv/tests/kmac_base_test.sv b/hw/ip/kmac/dv/tests/kmac_base_test.sv index 9cf144b847f74..e45d045d4d12e 100644 --- a/hw/ip/kmac/dv/tests/kmac_base_test.sv +++ b/hw/ip/kmac/dv/tests/kmac_base_test.sv @@ -17,4 +17,9 @@ class kmac_base_test extends cip_base_test #( // the base class also looks up UVM_TEST_SEQ plusarg to create and run that seq in // the run_phase; as such, nothing more needs to be done + virtual function void build_phase(uvm_phase phase); + super.build_phase(phase); + void'($value$plusargs("enable_masking=%0b", cfg.enable_masking)); + endfunction + endclass : kmac_base_test