fpga: Add license checker module
Original-commit: 8ed8927b944e7ad8a5da040c7f1951aad3535e11
This commit is contained in:
@@ -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