diff --git a/lib/sec/Makefile.srcs b/lib/sec/Makefile.srcs index afc8b02..4b330e9 100644 --- a/lib/sec/Makefile.srcs +++ b/lib/sec/Makefile.srcs @@ -13,4 +13,5 @@ sha256_core.v \ sha256_k_constants.v \ sha256_stream.v \ sha256_w_mem.v \ +license_check.sv \ )) diff --git a/lib/sec/license_check.sv b/lib/sec/license_check.sv new file mode 100644 index 0000000..7623804 --- /dev/null +++ b/lib/sec/license_check.sv @@ -0,0 +1,266 @@ +// +// Copyright 2023 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// + +// Module: license_check +// +// Description: +// +// License checker for RFNoC blocks, using a combination of private key, device +// serial, and feature flag. This module can be used to load a license key. +// If the key is valid, it will assert an output signal, which can be consumed +// by the module for which the license key is meant. +// +// The license key is a combination of a feature identifier (32-bit) and a +// SHA256 hash (256 bits). In total, the license key is thus 36 bytes long. +// +// The hash is calculated from: +// - A device serial +// - A private key +// - The 32-bit feature identifier +// +// Usage: +// - Upon instantiation, make sure that the serial input is connected to this +// device's serial number. +// - To unlock a feature, first write the feature ID to BASE_ADDR. This will +// calculate a SHA256 hash. +// - Then, write the rest of the user key in 32-bit words to BASE_ADDR+4. This +// module will compare the user key with the internally generated hash bit +// by bit. +// - After all 8 32-bit words have been written, the feature enable line will +// be asserted if the uploaded key matched the SHA256 hash. +// +// Note that in this implementation, PKEY_W + SERIAL_W must be less than +// 512-32-64-8 as we require 32 bits for the feature flag, and 8 bits padding +// (as well as the 64-bit message length). +// +// Registers (Relative to BASE_ADDR): +// - FID_ADDR (BASE_ADDR): Feature ID (w) +// - KEY_ADDR (BASE_ADDR+4): User key, one 32-bit word at a time (w) +// - RB_ADDR (BASE_ADDR+8): Readback, will provide info about the last feature +// ID that was written. Bit 31 tells us if the feature +// was unlocked. The rest tells us which feature index +// it is. +// +// Parameters: +// +// BASE_ADDR: Address for writing the feature flag. Key values are written to +// BASE_ADDR+4 (see above). +// PKEY_W: Width of private key (in bits). +// SERIAL_W: Width of serial (in bits). See note above on available lengths. +// NUM_FEATURES: Number of features that can be unlocked +// FEATURE_IDS: A list of 32-bit values that identify features +// PRIVATE_KEY: The private key +// DEVICE_TYPE: For now, can only be ULTRASCALE. +// + +`default_nettype none + +module license_check #( + parameter BASE_ADDR = 0, + parameter PKEY_W = 312, + parameter SERIAL_W = 96, + parameter NUM_FEATURES = 1, + parameter [(32*NUM_FEATURES)-1:0] FEATURE_IDS = {32'hC0DE}, + parameter [PKEY_W-1:0] PRIVATE_KEY = 0, + parameter DEVICE_TYPE = "ULTRASCALE" +)( + input wire clk, + input wire rst, + + input wire [SERIAL_W-1:0] serial, + + input wire s_ctrlport_req_wr, + input wire s_ctrlport_req_rd, + input wire [19:0] s_ctrlport_req_addr, + input wire [31:0] s_ctrlport_req_data, + output wire s_ctrlport_resp_ack, + output wire [ 1:0] s_ctrlport_resp_status, + output wire [31:0] s_ctrlport_resp_data, + + output reg [NUM_FEATURES-1:0] feature_enabled = 0 +); + + `include "../rfnoc/core/ctrlport.vh" + + + //! Number of bits in the SHA256 message that encode the message length + // (see SHA256 standard for details) + localparam MSGLEN_W = 64; + localparam FID_W = 32; // Number of bits in feature ID + // Number of bits that are being SHA-hashed + localparam [MSGLEN_W-1:0] MSGLEN = PKEY_W + SERIAL_W + FID_W; + // We have 512 bits in a SHA-input block. The remaining bits that are not part + // of the message are the padding and the message length. + localparam PADDING_W = 512 - MSGLEN - MSGLEN_W; // Number of padding bits + // We pad at least with a full byte. This wastes 7 bits of potential private + // key length, but it also means we can't accidentally load invalid private + // keys. + if (PADDING_W < 8) begin : gen_assertion + ERROR_max_key_len_exceeded(); + end + // Padding bits (10000...) + localparam [PADDING_W-1:0] PADDING = {1'b1, {(PADDING_W-1){1'b0}}}; + + // Length of the feature indexer reg + localparam FIDXREG_W = $clog2(NUM_FEATURES)+1; + // A value that is not a feature index + localparam NO_FID = {FIDXREG_W{1'b1}}; + + // Valid addresses on the CtrlPort bus + localparam FID_ADDR = BASE_ADDR; + localparam KEY_ADDR = BASE_ADDR + 4; + localparam RB_ADDR = BASE_ADDR + 8; + + // States + typedef enum logic [2:0] { + ST_IDLE, + ST_CALC, + ST_USERKEYDONE, + ST_ACK, + ST_CTRLPORT_ERROR + } state_t; + + // Registers & Wires + state_t state = ST_IDLE; + reg key_valid = 1'b0; // Tracks if current hash matches + reg fid_hashed = 1'b0; // Tracks if we have hashed a feature ID + reg [2:0] word_cnt = 3'd7; // Track which word we are currently comparing + // The index of the feature ID we're hashing/comparing + reg [FIDXREG_W-1:0] feature_idx = NO_FID; + + wire fid_wr_req = (s_ctrlport_req_wr && (s_ctrlport_req_addr == FID_ADDR)); + wire key_wr_req = (s_ctrlport_req_wr && (s_ctrlport_req_addr == KEY_ADDR)); + wire [255:0] sha_digest; + wire sha_digest_valid; + wire sha_tready; + wire sha_tvalid = fid_wr_req && (state == ST_IDLE); + + integer feat_i; + + wire [511:0] sha_input = { + serial, + PRIVATE_KEY, + s_ctrlport_req_data, + PADDING, + MSGLEN + }; + + // Actual SHA256 calculation module + sha256_stream sha256_inst ( + .clk (clk), + .rst (rst), + .mode (1'b1), // Always SHA256, not SHA224 + .s_tdata_i (sha_input), + .s_tlast_i (1'b1), // All SHA256 transactions in this module are single-block + .s_tvalid_i (sha_tvalid), + .s_tready_o (sha_tready), + .digest_o (sha_digest), + .digest_valid_o (sha_digest_valid) + ); + + + // State machine + always @(posedge clk) begin + if (rst) begin + state <= ST_IDLE; + word_cnt <= 3'd7; + key_valid <= 1'b0; + fid_hashed <= 1'b0; + // On reset, all features are disabled. Otherwise, they always stay on. + feature_enabled <= {NUM_FEATURES{1'b0}}; + feature_idx <= NO_FID; + end else case (state) + ST_IDLE : begin + //// Idle state: We're waiting on input via CtrlPort. + // - If we get a feature flag to hash, we store it and start hashin' + // Note that the sha256 module is connected straight to the input and + // will start calculating when this condition is met. + if (fid_wr_req) begin + // If we switched to ST_CALC before sha_tready is asserted, then we + // would read an invalid hash from the sha256 module. However, we wait + // with returning an ACK when the sha256 module is busy, so we can + // assume the hashing module is always ready in this scenario. + // synthesis translate_off + assert(sha_tready); + // synthesis translate_on + state <= ST_CALC; + word_cnt <= 3'd7; + key_valid <= 1'b0; + fid_hashed <= 1'b0; + // Reset the feature index to a non-feature-index value + feature_idx <= NO_FID; + + // We store which feature ID we've been comparing + for (feat_i = 0; feat_i < NUM_FEATURES; feat_i = feat_i + 1) begin + if (s_ctrlport_req_data == FEATURE_IDS[(32*feat_i) +: 32]) begin + feature_idx <= feat_i; + end + end + + // - If we get a key to compare, but we didn't previously hash a feature + // ID, then we return an error code, because we can't compare it with + // anything yet! + end else if (key_wr_req && !fid_hashed) begin + state <= ST_CTRLPORT_ERROR; + + // - If we get a key to compare, and we *did* previously hash a flag, we + // start comparing word-for-word. At any point, if the comparison fails, + // our key becomes and stays invalid. + end else if (key_wr_req) begin + key_valid <= (word_cnt == 7 ? (sha_digest[255:224] == s_ctrlport_req_data) : + word_cnt == 6 ? (sha_digest[223:192] == s_ctrlport_req_data) : + word_cnt == 5 ? (sha_digest[191:160] == s_ctrlport_req_data) : + word_cnt == 4 ? (sha_digest[159:128] == s_ctrlport_req_data) : + word_cnt == 3 ? (sha_digest[127: 96] == s_ctrlport_req_data) : + word_cnt == 2 ? (sha_digest[ 95: 64] == s_ctrlport_req_data) : + word_cnt == 1 ? (sha_digest[ 63: 32] == s_ctrlport_req_data) : + (sha_digest[ 31: 0] == s_ctrlport_req_data)) + && (word_cnt == 7 || key_valid); + word_cnt <= word_cnt - 1; + state <= word_cnt == 0 ? ST_USERKEYDONE : ST_ACK; + + // Handle readback + end else if (s_ctrlport_req_rd && (s_ctrlport_req_addr == RB_ADDR)) begin + state <= ST_ACK; + + // Any other read/write request is ignored + end else begin + state <= ST_IDLE; + end + end // ST_IDLE + ST_CALC : begin + //// Hash calculation state: + // We wait here until the SHA is calculated. We don't accept CtrlPort + // transactions until then, either. Once the feature flag has been hashed, + // we return to idle state and wait for a user key for comparison. + fid_hashed <= sha_digest_valid; + state <= sha_digest_valid ? ST_IDLE : ST_CALC; + end + ST_USERKEYDONE : begin + //// User-key-done state: We get here when 8 words have been provided for + // comparison. If an invalid key is uploaded for a feature after it was + // already unlocked, we leave the flag enabled. + feature_enabled[feature_idx] <= key_valid | feature_enabled[feature_idx]; + state <= ST_ACK; + end + ST_ACK, ST_CTRLPORT_ERROR : state <= ST_IDLE; + default : state <= ST_IDLE; + endcase + end // always @(posedge clk) + + assign s_ctrlport_resp_ack = (state == ST_CALC && sha_digest_valid) || + (state == ST_ACK) || + (state == ST_CTRLPORT_ERROR); + assign s_ctrlport_resp_status = + (state == ST_CTRLPORT_ERROR) ? CTRL_STS_CMDERR : CTRL_STS_OKAY; + + assign s_ctrlport_resp_data = (feature_idx == NO_FID) + ? 32'b0 + : {feature_enabled[feature_idx], {(31-FIDXREG_W){1'b0}}, feature_idx}; + +endmodule + +`default_nettype wire diff --git a/lib/sim/sec/license_check/Makefile b/lib/sim/sec/license_check/Makefile new file mode 100644 index 0000000..79faf65 --- /dev/null +++ b/lib/sim/sec/license_check/Makefile @@ -0,0 +1,45 @@ +# +# Copyright 2023 Ettus Research, a National Instruments Brand +# +# SPDX-License-Identifier: LGPL-3.0-or-later +# + +#------------------------------------------------- +# Top-of-Makefile +#------------------------------------------------- +# Define BASE_DIR to point to the "top" dir +BASE_DIR = $(abspath ../../../../top) +# Include viv_sim_preamble after defining BASE_DIR +include $(BASE_DIR)/../tools/make/viv_sim_preamble.mak + +#------------------------------------------------- +# Design Specific +#------------------------------------------------- +# Include makefiles and sources for the DUT and its dependencies + +DESIGN_SRCS += \ +$(abspath ../../../sec/license_check.sv) \ +$(abspath ../../../sec/sha256.v) \ +$(abspath ../../../sec/sha256_core.v) \ +$(abspath ../../../sec/sha256_k_constants.v) \ +$(abspath ../../../sec/sha256_stream.v) \ +$(abspath ../../../sec/sha256_w_mem.v) \ +$(VIVADO_PATH)/data/verilog/src/glbl.v \ + +MODELSIM_ARGS += glbl + +#------------------------------------------------- +# Testbench Specific +#------------------------------------------------- +SIM_TOP = license_check_tb + +SIM_SRCS = \ +$(abspath license_check_tb.sv) \ + +#------------------------------------------------- +# Bottom-of-Makefile +#------------------------------------------------- +# Include all simulator specific makefiles here +# Each should define a unique target to simulate +# e.g. xsim, vsim, etc and a common "clean" target +include $(BASE_DIR)/../tools/make/viv_simulator.mak diff --git a/lib/sim/sec/license_check/gen_sha256.py b/lib/sim/sec/license_check/gen_sha256.py new file mode 100644 index 0000000..8606839 --- /dev/null +++ b/lib/sim/sec/license_check/gen_sha256.py @@ -0,0 +1,38 @@ +# +# Copyright 2023 Ettus Research, a National Instruments Brand +# +# SPDX-License-Identifier: GPL-3.0-or-later +# +""" +Calculate hash values for license_check_tb +""" + +import hashlib + +serial = [0x01, 0x23, 0x45, 0x67, 0x8A, 0xBC, 0xDE, 0xF0, 0x0C, 0x0D, 0xE0, 0xBB] +pkey = [0x00] * 37 + [0x05, 0xEC] +feature0 = [0x00, 0x00, 0xC0, 0xDE] +feature1 = [0x00, 0x00, 0xF0, 0x0D] + +def main(): + """ + Calc and print hashes + """ + sha0 = hashlib.sha256() + sha0.update(bytes(serial + pkey + feature0)) + hash0 = sha0.hexdigest() + sha1 = hashlib.sha256() + sha1.update(bytes(serial + pkey + feature1)) + hash1 = sha1.hexdigest() + + print(f"// Feature 0 hash: 256'h{hash0}") + hash0_words = [f"hash0_{int(i/8)} = 32'h" + hash0[i:i+8] for i in range(0, 64, 8)] + print("// Individual words:") + print(";\n localparam ".join(hash0_words)) + print(f"// Feature 1 hash: 256'h{hash1}") + hash1_words = [f"hash1_{int(i/8)} = 32'h" + hash1[i:i+8] for i in range(0, 64, 8)] + print("// Individual words:") + print(";\n localparam ".join(hash1_words)) + +if __name__ == "__main__": + main() diff --git a/lib/sim/sec/license_check/license_check_tb.sv b/lib/sim/sec/license_check/license_check_tb.sv new file mode 100644 index 0000000..8bc7cbd --- /dev/null +++ b/lib/sim/sec/license_check/license_check_tb.sv @@ -0,0 +1,292 @@ +// +// Copyright 2023 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Description: +// +// Testbench for license_check and the sha256 module +// + +`default_nettype none + +module license_check_tb (); + + // Include macros and time declarations for use with PkgTestExec + `include "test_exec.svh" + import PkgTestExec::*; + + localparam real CLK_PERIOD = 10.0; // ns + localparam [95:0] SERIAL = 96'h012345678ABCDEF00C0DE0BB; + localparam int PKEY_W = 312; + localparam [PKEY_W-1:0] PRIVATE_KEY = 312'h5EC; + localparam int FEATURE0 = 32'hC0DE; + localparam int FEATURE1 = 32'hF00D; + + localparam FEATURE_ADDR = 0; + localparam KEY_ADDR = 4; + localparam RB_ADDR = 8; + + // Note: hashes are calculated with gen_sha256.py + // Feature 0 hash: 256'h80f122aad504a1ec7ae8f37b4eee414cf1cf32ecc543ee2e1d4e1d4ae59eb41b + // Individual words: + localparam hash0_0 = 32'h80f122aa; + localparam hash0_1 = 32'hd504a1ec; + localparam hash0_2 = 32'h7ae8f37b; + localparam hash0_3 = 32'h4eee414c; + localparam hash0_4 = 32'hf1cf32ec; + localparam hash0_5 = 32'hc543ee2e; + localparam hash0_6 = 32'h1d4e1d4a; + localparam hash0_7 = 32'he59eb41b; + // Feature 1 hash: 256'hefd81f78a5fdee0a16ed039cfa51db769a8da088328f8ed73d6af85ba1ce57d4 + // Individual words: + localparam hash1_0 = 32'hefd81f78; + localparam hash1_1 = 32'ha5fdee0a; + localparam hash1_2 = 32'h16ed039c; + localparam hash1_3 = 32'hfa51db76; + localparam hash1_4 = 32'h9a8da088; + localparam hash1_5 = 32'h328f8ed7; + localparam hash1_6 = 32'h3d6af85b; + localparam hash1_7 = 32'ha1ce57d4; + + + //--------------------------------------------------------------------------- + // Clocks and Resets + //--------------------------------------------------------------------------- + + bit clk; + bit rst; + bit rst_dut = 0; + + sim_clock_gen #(.PERIOD(CLK_PERIOD)) + clk_gen (.clk(clk), .rst(rst)); + + //--------------------------------------------------------------------------- + // Device Under Test (DUT) + //--------------------------------------------------------------------------- + + logic i_ctrlport_req_rd = 1'b0; + logic i_ctrlport_req_wr = 1'b0; + logic [19:0] i_ctrlport_req_addr = 20'h0; + logic [31:0] i_ctrlport_req_data = 32'h0; + logic o_ctrlport_resp_ack; + logic [1:0] o_ctrlport_resp_status; + logic [31:0] o_ctrlport_resp_data; + + logic [1:0] feature_enabled; + + license_check #( + .BASE_ADDR (0), + .PKEY_W (PKEY_W), + .SERIAL_W (96), + .NUM_FEATURES (2), + .FEATURE_IDS ({FEATURE1, FEATURE0}), + .PRIVATE_KEY (PRIVATE_KEY) + ) license_check_dut ( + .clk(clk), + .rst(rst | rst_dut), + + .serial(SERIAL), + + .s_ctrlport_req_wr (i_ctrlport_req_wr), + .s_ctrlport_req_rd (i_ctrlport_req_rd), + .s_ctrlport_req_addr (i_ctrlport_req_addr), + .s_ctrlport_req_data (i_ctrlport_req_data), + .s_ctrlport_resp_ack (o_ctrlport_resp_ack), + .s_ctrlport_resp_status (o_ctrlport_resp_status), + .s_ctrlport_resp_data (o_ctrlport_resp_data), + + .feature_enabled(feature_enabled) + ); + + + //-------------------------------- + // Tasks + //-------------------------------- + + // Write a data word via ctrlport transaction. Will run into timeout if no + // ACK is returned. + task automatic ctrlport_poke32(int addr, int data); + i_ctrlport_req_addr <= addr; + i_ctrlport_req_data <= data; + i_ctrlport_req_wr <= 1'b1; + @(posedge clk); + i_ctrlport_req_wr <= 1'b0; + while (!o_ctrlport_resp_ack) @(posedge clk); + test.assert_error( + !o_ctrlport_resp_status, + $sformatf("CtrlPort write response status not zero (%d)!", + o_ctrlport_resp_status)); + endtask + + task automatic ctrlport_poke32_status(int addr, int data, int exp_status); + i_ctrlport_req_addr <= addr; + i_ctrlport_req_data <= data; + i_ctrlport_req_wr <= 1'b1; + @(posedge clk); + i_ctrlport_req_wr <= 1'b0; + while (!o_ctrlport_resp_ack) @(posedge clk); + test.assert_error( + o_ctrlport_resp_status == exp_status, + $sformatf("CtrlPort write response status not %d (%d)!", + exp_status, o_ctrlport_resp_status)); + endtask + + task automatic ctrlport_peek32(int addr, int expected_data); + i_ctrlport_req_addr <= addr; + i_ctrlport_req_rd <= 1'b1; + @(posedge clk); + i_ctrlport_req_rd <= 1'b0; + while (!o_ctrlport_resp_ack) @(posedge clk); + test.assert_error( + !o_ctrlport_resp_status, + $sformatf("CtrlPort read response status not zero (%d)!", + o_ctrlport_resp_status)); + test.assert_error( + o_ctrlport_resp_data == expected_data, + $sformatf("Incorrect peek value: %x! Expected %x.", + o_ctrlport_resp_data, expected_data)); + endtask + + + + //--------------------------------------------------------------------------- + // Main Test Process + //--------------------------------------------------------------------------- + + initial begin : tb_main + string tb_name; + + tb_name = $sformatf("device_dna_ctrlport"); + test.start_tb(tb_name, 10ms); + + //------------------------------------------------------- + // Reset clock generator and wait for reset to complete + //------------------------------------------------------- + + test.start_test("Reset", 100us); + clk_gen.reset(); + if (rst) @rst; + test.end_test(); + + //-------------------------------- + // Test Sequences + //-------------------------------- + + // Verify that all features are disabled after reset + test.start_test("Checking reset state", 2us); + test.assert_error( + feature_enabled == 0, + $sformatf("Features enabled after reset: %x", feature_enabled)); + ctrlport_peek32(RB_ADDR, 32'b00000000000000000000000000000000); + test.end_test(); + + /// Regular feature unlock + test.start_test("Checking feature 0 unlock", 100us); + ctrlport_poke32(FEATURE_ADDR, FEATURE0); + ctrlport_poke32(KEY_ADDR, hash0_0); + ctrlport_poke32(KEY_ADDR, hash0_1); + ctrlport_poke32(KEY_ADDR, hash0_2); + ctrlport_poke32(KEY_ADDR, hash0_3); + ctrlport_poke32(KEY_ADDR, hash0_4); + ctrlport_poke32(KEY_ADDR, hash0_5); + ctrlport_poke32(KEY_ADDR, hash0_6); + ctrlport_poke32(KEY_ADDR, hash0_7); + test.assert_error(feature_enabled == 2'b1, + $sformatf("Feature enable is not 1 (%x)", feature_enabled)); + ctrlport_peek32(RB_ADDR, 32'b10000000000000000000000000000000); + test.end_test(); + + test.start_test("Checking feature 1 unlock", 100us); + ctrlport_poke32(FEATURE_ADDR, FEATURE1); + ctrlport_poke32(KEY_ADDR, hash1_0); + ctrlport_poke32(KEY_ADDR, hash1_1); + ctrlport_poke32(KEY_ADDR, hash1_2); + ctrlport_poke32(KEY_ADDR, hash1_3); + ctrlport_poke32(KEY_ADDR, hash1_4); + ctrlport_poke32(KEY_ADDR, hash1_5); + ctrlport_poke32(KEY_ADDR, hash1_6); + ctrlport_poke32(KEY_ADDR, hash1_7); + test.assert_error(feature_enabled == 2'b11, + $sformatf("Feature enable is not 0b11 (%x)", feature_enabled)); + ctrlport_peek32(RB_ADDR, 32'b10000000000000000000000000000001); + test.end_test(); + + /// Reset and make sure features are no longer enabled + test.start_test("Testing reset after unlock", 100us); + rst_dut <= 1; + @(posedge clk); + rst_dut <= 0; + @(posedge clk); + test.assert_error(feature_enabled == 2'b00, + $sformatf("Feature enable is not 0b00 (%x)", feature_enabled)); + ctrlport_peek32(RB_ADDR, 32'h0); + test.end_test(); + + /// Writing a hash value now is invalid + test.start_test("Checking error case: No FID loaded", 100us); + ctrlport_poke32_status(KEY_ADDR, 32'h1234ABCD, 2'b1); + test.end_test(); + + /// Try uploading invalid key + test.start_test("Checking feature 0 unlock with invalid key", 100us); + ctrlport_poke32(FEATURE_ADDR, FEATURE0); + ctrlport_poke32(KEY_ADDR, hash0_0); + ctrlport_poke32(KEY_ADDR, hash0_1); + ctrlport_poke32(KEY_ADDR, hash0_2); + ctrlport_poke32(KEY_ADDR, hash0_3); + ctrlport_poke32(KEY_ADDR, hash0_4); + ctrlport_poke32(KEY_ADDR, hash0_5); + ctrlport_poke32(KEY_ADDR, hash0_6); + ctrlport_poke32(KEY_ADDR, 32'h0); + test.assert_error(feature_enabled == 2'b0, + $sformatf("Feature enable is not 0 (%x)", feature_enabled)); + test.end_test(); + + /// Now again valid key after failure + test.start_test("Checking feature 0 unlock with valid key again after fail", 100us); + ctrlport_poke32(FEATURE_ADDR, FEATURE0); + ctrlport_poke32(KEY_ADDR, hash0_0); + ctrlport_poke32(KEY_ADDR, hash0_1); + ctrlport_poke32(KEY_ADDR, hash0_2); + ctrlport_poke32(KEY_ADDR, hash0_3); + ctrlport_poke32(KEY_ADDR, hash0_4); + ctrlport_poke32(KEY_ADDR, hash0_5); + ctrlport_poke32(KEY_ADDR, hash0_6); + ctrlport_poke32(KEY_ADDR, hash0_7); + test.assert_error(feature_enabled == 2'b1, + $sformatf("Feature enable is not 0b1 (%x)", feature_enabled)); + test.end_test(); + + /// Upload part of feature 0, interrupt, then feature 1 + test.start_test("Checking feature 1 unlock after reset", 100us); + ctrlport_poke32(FEATURE_ADDR, FEATURE0); + ctrlport_poke32(KEY_ADDR, hash0_0); + ctrlport_poke32(KEY_ADDR, hash0_1); + ctrlport_poke32(KEY_ADDR, hash0_2); + ctrlport_poke32(FEATURE_ADDR, FEATURE1); + ctrlport_poke32(KEY_ADDR, hash1_0); + ctrlport_poke32(KEY_ADDR, hash1_1); + ctrlport_poke32(KEY_ADDR, hash1_2); + ctrlport_poke32(KEY_ADDR, hash1_3); + ctrlport_poke32(KEY_ADDR, hash1_4); + ctrlport_poke32(KEY_ADDR, hash1_5); + ctrlport_poke32(KEY_ADDR, hash1_6); + ctrlport_poke32(KEY_ADDR, hash1_7); + test.assert_error(feature_enabled == 2'b11, + $sformatf("Feature enable is not 0b11 (%x)", feature_enabled)); + ctrlport_peek32(RB_ADDR, 32'b10000000000000000000000000000001); + test.end_test(); + + + //-------------------------------- + // Finish Up + //-------------------------------- + + test.end_tb(); + + end : tb_main + +endmodule : license_check_tb + +`default_nettype wire