fpga: Add license checker module
Original-commit: 8ed8927b944e7ad8a5da040c7f1951aad3535e11
This commit is contained in:
@@ -13,4 +13,5 @@ sha256_core.v \
|
||||
sha256_k_constants.v \
|
||||
sha256_stream.v \
|
||||
sha256_w_mem.v \
|
||||
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
|
||||
@@ -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
|
||||
@@ -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()
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user