293 lines
12 KiB
Systemverilog
293 lines
12 KiB
Systemverilog
//
|
|
// 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.
|
|
// - RB_FID_ADDR (BASE_ADDR+12): Readback, will return a feature flag this
|
|
// module was compiled with. On the next read,
|
|
// it will return the next feature ID. To
|
|
// learn all feature IDs that this module knows,
|
|
// keep reading this register until values
|
|
// repeat.
|
|
//
|
|
// 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
|
|
)(
|
|
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;
|
|
localparam RB_FID_ADDR = BASE_ADDR + 12;
|
|
|
|
// States
|
|
typedef enum logic [2:0] {
|
|
ST_IDLE,
|
|
ST_CALC,
|
|
ST_USERKEYDONE,
|
|
ST_ACK,
|
|
ST_CTRLPORT_ERROR
|
|
} state_t;
|
|
|
|
typedef enum logic {
|
|
ST_RB_ENABLED,
|
|
ST_RB_FIDS
|
|
} rb_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;
|
|
// The index of the last feature ID we returned on RB_FID_ADDR
|
|
reg [FIDXREG_W-1:0] rb_feature_idx = (NUM_FEATURES - 1);
|
|
reg rb_last_req = ST_RB_ENABLED;
|
|
|
|
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;
|
|
// Reset the readback feature index
|
|
rb_feature_idx <= (NUM_FEATURES - 1);
|
|
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;
|
|
rb_last_req <= ST_RB_ENABLED;
|
|
|
|
end else if (s_ctrlport_req_rd && (s_ctrlport_req_addr == RB_FID_ADDR)) begin
|
|
state <= ST_ACK;
|
|
rb_last_req <= ST_RB_FIDS;
|
|
rb_feature_idx <= rb_feature_idx == (NUM_FEATURES-1)
|
|
? {FIDXREG_W{1'b0}}
|
|
: (rb_feature_idx + 1);
|
|
|
|
// 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 = (rb_last_req == ST_RB_ENABLED) ?
|
|
((feature_idx == NO_FID)
|
|
? 32'b0
|
|
: {feature_enabled[feature_idx], {(31-FIDXREG_W){1'b0}}, feature_idx})
|
|
: (FEATURE_IDS[(32*rb_feature_idx) +: 32]);
|
|
|
|
endmodule
|
|
|
|
`default_nettype wire
|