[otbn] Implement RND/URND CSR/WSR and RND_PREFETCH CSR This adds logic to read RND values from the EDN, removing the test RND dummy value. A small cache to hold a single 256-bit random number from an EDN request is provided. A RND_PREFETCH CSR is introduced which prefetches to the cache when any value is written to it. Any RND read will take the value from the cache, emptying it. If a value isn't available in the cache the RND read will stall until one is available, starting a new EDN request to fetch one if required. An LFSR is added to supply values to URND. This is seeded via a request to the EDN that occurs when OTBN starts. Execution cannot proceed until the LFSR has been seeded. Two dummy EDNs are provided in `otbn_top_sim.sv`. These provides the existing test RND dummy value on every request after a fixed delay. The ISS has been altered to support the new stalling behaviour for RND reads. In a testbench the EDN interface is monitored and the ISS is provided with RND values as they appear on the interface. ISS support for URND has not yet been implemented, though it does implement the stall at the beginning of execution whilst OTBN awaits a URND reseed. Signed-off-by: Greg Chadwick <gac@lowrisc.org>
diff --git a/hw/ip/otbn/dv/model/iss_wrapper.cc b/hw/ip/otbn/dv/model/iss_wrapper.cc index f5dc918..5357c77 100644 --- a/hw/ip/otbn/dv/model/iss_wrapper.cc +++ b/hw/ip/otbn/dv/model/iss_wrapper.cc
@@ -8,6 +8,7 @@ #include <cstring> #include <fcntl.h> #include <ftw.h> +#include <iomanip> #include <iostream> #include <memory> #include <regex> @@ -218,6 +219,17 @@ return val; } +static std::string wlen_val_to_hex_str(uint32_t val[8]) { + std::ostringstream oss; + + oss << std::hex << "0x"; + for (int i = 7; i >= 0; --i) { + oss << std::setfill('0') << std::setw(8) << val[i]; + } + + return oss.str(); +} + ISSWrapper::ISSWrapper() : tmpdir(new TmpDir()) { std::string model_path(find_otbn_model()); @@ -322,6 +334,16 @@ run_command(oss.str(), nullptr); } +void ISSWrapper::edn_rnd_data(uint32_t edn_rnd_data[8]) { + std::ostringstream oss; + oss << "edn_rnd_data " << wlen_val_to_hex_str(edn_rnd_data) << "\n"; + run_command(oss.str(), nullptr); +} + +void ISSWrapper::edn_urnd_reseed_complete() { + run_command("edn_urnd_reseed_complete\n", nullptr); +} + std::pair<int, uint32_t> ISSWrapper::step(bool gen_trace) { std::vector<std::string> lines; bool mismatch = false;
diff --git a/hw/ip/otbn/dv/model/iss_wrapper.h b/hw/ip/otbn/dv/model/iss_wrapper.h index a5383f4..9143c23 100644 --- a/hw/ip/otbn/dv/model/iss_wrapper.h +++ b/hw/ip/otbn/dv/model/iss_wrapper.h
@@ -36,6 +36,13 @@ // Jump to a new address and start running void start(uint32_t addr); + // Provide data for RND. ISS will stall when RND is read and RND data isn't + // available + void edn_rnd_data(uint32_t edn_rnd_data[8]); + + // Signal URND reseed at beginning of execution is complete + void edn_urnd_reseed_complete(); + // Run simulation for a single cycle. Returns a pair (ret_code, err_bits). // // If gen_trace is true, pass trace data to the (singleton)
diff --git a/hw/ip/otbn/dv/model/otbn_core_model.sv b/hw/ip/otbn/dv/model/otbn_core_model.sv index e5bda2c..5e27730 100644 --- a/hw/ip/otbn/dv/model/otbn_core_model.sv +++ b/hw/ip/otbn/dv/model/otbn_core_model.sv
@@ -39,7 +39,11 @@ input logic [ImemAddrWidth-1:0] start_addr_i, // start byte address in IMEM - output bit err_o // something went wrong + output bit err_o, // something went wrong + + input logic edn_rnd_data_valid_i, // provide RND data from EDN + input logic [WLEN-1:0] edn_rnd_data_i, + input logic edn_urnd_data_valid_i // URND reseed from EDN is valid ); import "DPI-C" context function chandle otbn_model_init(string mem_scope, @@ -48,11 +52,15 @@ int unsigned dmem_words); import "DPI-C" function void otbn_model_destroy(chandle handle); import "DPI-C" context function - int unsigned otbn_model_step(chandle model, - logic start_i, - int unsigned start_addr, - int unsigned status, - inout bit [31:0] err_code); + int unsigned otbn_model_step(chandle model, + logic start, + int unsigned start_addr, + int unsigned status, + logic edn_rnd_data_valid, + logic [WLEN-1:0] edn_rnd_data, + logic edn_urnd_data_valid, + inout bit [31:0] err_code); + localparam int ImemSizeWords = ImemSizeByte / 4; localparam int DmemSizeWords = DmemSizeByte / (WLEN / 8); @@ -107,7 +115,10 @@ if (start_i | running | check_due) begin status <= otbn_model_step(model_handle, start_i, start_addr_32, - status, raw_err_bits_d); + status, + edn_rnd_data_valid_i, edn_rnd_data_i, + edn_urnd_data_valid_i, + raw_err_bits_d); raw_err_bits_q <= raw_err_bits_d; end else begin // If we're not running and we're not being told to start, there's nothing to do.
diff --git a/hw/ip/otbn/dv/model/otbn_model.cc b/hw/ip/otbn/dv/model/otbn_model.cc index 08061f9..dd45504 100644 --- a/hw/ip/otbn/dv/model/otbn_model.cc +++ b/hw/ip/otbn/dv/model/otbn_model.cc
@@ -104,28 +104,28 @@ // Bit 3: failed_cmp Consistency check at end of run failed // // The otbn_model_step function should only be called when either the model is -// running (bit 0 of status), has a check due (bit 1 of status), or when -// start_i is asserted. At other times, it will return immediately (but wastes -// a DPI call). +// running (bit 0 of status), has a check due (bit 1 of status), or when start +// is asserted. At other times, it will return immediately (but wastes a DPI +// call). // -// If the model is running and start_i is false, otbn_model_step steps the ISS -// by a single cycle. If something goes wrong, it will set failed_step to true -// and running to false. +// If the model is running and start is false, otbn_model_step steps the ISS by +// a single cycle. If something goes wrong, it will set failed_step to true and +// running to false. // -// If nothing goes wrong, but the ISS finishes its run, we set running to -// false, write out err_bits and do the post-run task. If the model's -// design_scope is non-empty, it should be the scope of an RTL implementation. -// In that case, we compare register and memory contents with that -// implementation, printing to stderr and setting the failed_cmp bit if there -// are any mismatches. If the model's design_scope is the empty string, we grab -// the contents of DMEM from the ISS and inject them into the simulation -// memory. +// If nothing goes wrong, but the ISS finishes its run, we set running to false, +// write out err_bits and do the post-run task. If the model's design_scope is +// non-empty, it should be the scope of an RTL implementation. In that case, we +// compare register and memory contents with that implementation, printing to +// stderr and setting the failed_cmp bit if there are any mismatches. If the +// model's design_scope is the empty string, we grab the contents of DMEM from +// the ISS and inject them into the simulation memory. // -// If start_i is true, we start the model at start_addr and then step once (as +// If start is true, we start the model at start_addr and then step once (as // described above). -extern "C" unsigned otbn_model_step(OtbnModel *model, svLogic start_i, - unsigned start_addr, unsigned status, - svBitVecVal *err_bits /* bit [31:0] */); +extern "C" unsigned otbn_model_step( + OtbnModel *model, svLogic start, unsigned start_addr, unsigned status, + svLogic edn_rnd_data_valid, svLogicVecVal *edn_rnd_data, /* logic [255:0] */ + svLogic edn_urnd_data_valid, svBitVecVal *err_bits /* bit [31:0] */); static std::vector<uint8_t> read_vector_from_file(const std::string &path, size_t num_bytes) { @@ -216,17 +216,54 @@ return 0; } +// Extract 256-bit RND EDN data from a 4 state logic value. RND data is placed +// into 8 element uint32_t array dst. +static void set_rnd_data(uint32_t dst[8], const svLogicVecVal src[8]) { + for (int i = 0; i < 8; ++i) { + // All bits should be known + assert(src[i].bval == 0); + dst[i] = src[i].aval; + } +} + +// Pack uint32_t-based error bitfield into a SystemVerilog bit vector that +// represents a "bit [31:0]" (as in the SV prototype of otbn_model_step) +static void set_err_bits(svBitVecVal *dst, uint32_t src) { + for (int i = 0; i < 32; ++i) { + svBit bit = (src >> i) & 1; + svPutBitselBit(dst, i, bit); + } +} + +static bool is_xz(svLogic l) { return l == sv_x || l == sv_z; } + // Step once in the model. Returns 1 if the model has finished, 0 if not and -1 // on failure. If gen_trace is true, pass trace entries to the trace checker. // If the model has finished, writes otbn.ERR_BITS to *err_bits. -static int step_model(OtbnModel &model, bool gen_trace, uint32_t *err_bits) { +static int step_model(OtbnModel &model, svLogic edn_rnd_data_valid, + svLogicVecVal *edn_rnd_data, /* logic [255:0] */ + svLogic edn_urnd_data_valid, bool gen_trace, + svBitVecVal *err_bits /* bit [31:0] */) { assert(err_bits); ISSWrapper *iss = model.get_wrapper(true); if (!iss) return -1; + assert(!is_xz(edn_rnd_data_valid)); + assert(!is_xz(edn_urnd_data_valid)); + try { + if (edn_rnd_data_valid) { + uint32_t int_edn_rnd_data[8]; + set_rnd_data(int_edn_rnd_data, edn_rnd_data); + iss->edn_rnd_data(int_edn_rnd_data); + } + + if (edn_urnd_data_valid) { + iss->edn_urnd_reseed_complete(); + } + std::pair<int, uint32_t> ret = iss->step(gen_trace); switch (ret.first) { case -1: @@ -236,7 +273,7 @@ case 1: // The simulation has stopped. Extract err_bits - *err_bits = ret.second; + set_err_bits(err_bits, ret.second); return 1; case 0: @@ -532,19 +569,11 @@ return good ? 1 : 0; } -// Pack uint32_t-based error bitfield (as generated by step_model) into a -// SystemVerilog bit vector that represents a "bit [31:0]" (as in the SV -// prototype of otbn_model_step) -static void set_err_bits(svBitVecVal *dst, uint32_t src) { - for (int i = 0; i < 32; ++i) { - svBit bit = (src >> i) & 1; - svPutBitselBit(dst, i, bit); - } -} - -extern "C" unsigned otbn_model_step(OtbnModel *model, svLogic start_i, - unsigned start_addr, unsigned status, - svBitVecVal *err_bits /* bit [31:0] */) { +extern "C" unsigned otbn_model_step( + OtbnModel *model, svLogic start, unsigned start_addr, unsigned status, + svLogic edn_rnd_data_valid, svLogicVecVal *edn_rnd_data, /* logic [255:0] */ + svLogic edn_urnd_data_valid, svBitVecVal *err_bits /* bit [31:0] */ +) { assert(model && err_bits); // Run model checks if needed. This usually happens just after an operation @@ -568,8 +597,10 @@ status &= ~CHECK_DUE_BIT; } + assert(!is_xz(start)); + // Start the model if requested - if (start_i) { + if (start) { switch (start_model(*model, start_addr)) { case 0: // All good @@ -587,8 +618,8 @@ return status; // Step the model once - uint32_t int_err_bits; - switch (step_model(*model, check_rtl, &int_err_bits)) { + switch (step_model(*model, edn_rnd_data_valid, edn_rnd_data, + edn_urnd_data_valid, check_rtl, err_bits)) { case 0: // Still running: no change break; @@ -596,7 +627,6 @@ case 1: // Finished status = (status & ~RUNNING_BIT) | CHECK_DUE_BIT; - set_err_bits(err_bits, int_err_bits); break; default:
diff --git a/hw/ip/otbn/dv/otbnsim/sim/csr.py b/hw/ip/otbn/dv/otbnsim/sim/csr.py index 7be2891..a1a7352 100644 --- a/hw/ip/otbn/dv/otbnsim/sim/csr.py +++ b/hw/ip/otbn/dv/otbnsim/sim/csr.py
@@ -43,6 +43,10 @@ # RND register return wsrs.RND.read_u32() + if idx == 0xfc1: + # RND_PREFETCH register + return 0 + raise RuntimeError('Unknown CSR index: {:#x}'.format(idx)) def write_unsigned(self, wsrs: WSRFile, idx: int, value: int) -> None: @@ -67,8 +71,8 @@ wsrs.MOD.write_unsigned(self._set_field(mod_n, 32, value, old)) return - if idx == 0xfc0: - # RND register (writes are ignored) + if idx == 0xfc0 or idx == 0xfc1: + # RND/RND_PREFETCH register (writes are ignored) return raise RuntimeError('Unknown CSR index: {:#x}'.format(idx))
diff --git a/hw/ip/otbn/dv/otbnsim/sim/insn.py b/hw/ip/otbn/dv/otbnsim/sim/insn.py index 2d88482..d09cb33 100644 --- a/hw/ip/otbn/dv/otbnsim/sim/insn.py +++ b/hw/ip/otbn/dv/otbnsim/sim/insn.py
@@ -280,6 +280,14 @@ self.csr = op_vals['csr'] self.grs1 = op_vals['grs1'] + def pre_execute(self, state: OTBNState) -> bool: + if self.csr == 0xfc0: + # Will return False if RND value not available, causing instruction + # to stall + return state.wsrs.RND.request_value() + + return True + def execute(self, state: OTBNState) -> None: old_val = state.read_csr(self.csr) bits_to_set = state.gprs.get_reg(self.grs1).read_unsigned() @@ -298,6 +306,14 @@ self.csr = op_vals['csr'] self.grs1 = op_vals['grs1'] + def pre_execute(self, state: OTBNState) -> bool: + if self.csr == 0xfc0 and self.grd != 0: + # Will return False if RND value not available, causing instruction + # to stall + return state.wsrs.RND.request_value() + + return True + def execute(self, state: OTBNState) -> None: new_val = state.gprs.get_reg(self.grs1).read_unsigned() @@ -977,6 +993,14 @@ self.wrd = op_vals['wrd'] self.wsr = op_vals['wsr'] + def pre_execute(self, state: OTBNState) -> bool: + if self.wsr == 0x1: + # Will return False if RND value not available, causing instruction + # to stall + return state.wsrs.RND.request_value() + + return True + def execute(self, state: OTBNState) -> None: val = state.wsrs.read_at_idx(self.wsr) state.wdrs.get_reg(self.wrd).write_unsigned(val)
diff --git a/hw/ip/otbn/dv/otbnsim/sim/sim.py b/hw/ip/otbn/dv/otbnsim/sim/sim.py index 325f639..783eb76 100644 --- a/hw/ip/otbn/dv/otbnsim/sim/sim.py +++ b/hw/ip/otbn/dv/otbnsim/sim/sim.py
@@ -8,6 +8,9 @@ from .state import OTBNState from .trace import Trace +_TEST_RND_DATA = \ + 0x99999999_99999999_99999999_99999999_99999999_99999999_99999999_99999999 + class OTBNSim: def __init__(self) -> None: @@ -27,10 +30,18 @@ ''' insn_count = 0 + # ISS will stall at start until URND data is valid, immediately set it + # valid when in free running mode as nothing else will. + self.state.set_urnd_reseed_complete() while self.state.running: self.step(verbose) insn_count += 1 + if self.state.wsrs.RND.pending_request: + # If an instruction requests RND data, make it available + # immediately. + self.state.wsrs.RND.set_unsigned(_TEST_RND_DATA) + return insn_count def step(self, verbose: bool) -> Tuple[Optional[OTBNInsn], List[Trace]]:
diff --git a/hw/ip/otbn/dv/otbnsim/sim/state.py b/hw/ip/otbn/dv/otbnsim/sim/state.py index 303979f..8033a23 100644 --- a/hw/ip/otbn/dv/otbnsim/sim/state.py +++ b/hw/ip/otbn/dv/otbnsim/sim/state.py
@@ -38,11 +38,13 @@ # returning false from OTBNInsn.pre_execute. For non instruction related # stalls setting self.non_insn_stall will produce a stall. # - # As a special case, we stall for one cycle before fetching the first - # instruction (to match the behaviour of the RTL). This is modelled by - # setting self._start_stall and self.non_insn_stall + # As a special case, we stall until the URND reseed is completed then + # stall for one more cycle before fetching the first instruction (to + # match the behaviour of the RTL). This is modelled by setting + # self._start_stall, self._urnd_stall and self.non_insn_stall self.non_insn_stall = False self._start_stall = False + self._urnd_stall = False self.loop_stack = LoopStack() self.ext_regs = OTBNExtRegs() @@ -51,6 +53,15 @@ self._err_bits = 0 self.pending_halt = False + self._new_rnd_data = None # type: Optional[int] + self._urnd_reseed_complete = False + + def set_rnd_data(self, rnd_data: int) -> None: + self._new_rnd_data = rnd_data + + def set_urnd_reseed_complete(self) -> None: + self._urnd_reseed_complete = True + def loop_start(self, iterations: int, bodysize: int) -> None: next_pc = int(self.pc) + 4 self.loop_stack.start_loop(next_pc, iterations, bodysize) @@ -79,6 +90,16 @@ assert not self.pending_halt assert self._err_bits == 0 + if self._new_rnd_data: + self.wsrs.RND.set_unsigned(self._new_rnd_data) + self._new_rnd_data = None + + if self._urnd_stall: + if self._urnd_reseed_complete: + self._urnd_stall = False + + return + # If self._start_stall, this is the end of the stall cycle at the start # of a run. Clear self.non_insn_stall and self._start_stall # and commit self.ext_regs (so the start flag becomes visible). @@ -119,9 +140,11 @@ self.ext_regs.set_bits('STATUS', 1 << 0) self.running = True self._start_stall = True + self._urnd_stall = True self.non_insn_stall = True self.pending_halt = False self._err_bits = 0 + self._urnd_reseed_complete = False self.pc = addr
diff --git a/hw/ip/otbn/dv/otbnsim/sim/wsr.py b/hw/ip/otbn/dv/otbnsim/sim/wsr.py index 2157aae..0fc2aca 100644 --- a/hw/ip/otbn/dv/otbnsim/sim/wsr.py +++ b/hw/ip/otbn/dv/otbnsim/sim/wsr.py
@@ -86,33 +86,68 @@ class RandWSR(WSR): - '''The magic RND WSR''' + '''The magic RND WSR + + RND is special as OTBN can stall on reads to it. A read from RND either + immediately returns data from a cache of a previous EDN request (triggered + by writing to the RND_PREFETCH CSR) or waits for data from the EDN. To model + this anything reading from RND must first call `request_value` which returns + True if the value is available. + ''' def __init__(self, name: str): super().__init__(name) - # For now, the RTL doesn't have a real "random number generator". - # Eventually, it will have an LFSR of some sort, seeded by the - # CSRNG/EDN. We'll model that properly when we've specced it out. Until - # then, random numbers are constant. This constant must match the one - # in the RTL (the `rnd` signal in the `otbn_core` module found in - # rtl/otbn_core.sv). If changed here it must be changed there to match. - # Constant for RND is the binary bit pattern 1001 (0x9 hex) repeated to - # fill a 256-bit word. - u32 = 0x99999999 - u64 = (u32 << 32) | u32 - u128 = (u64 << 64) | u64 - self._random_value = (u128 << 128) | u128 + self._random_value = None # type: Optional[int] + self._random_value_read = False + self.pending_request = False def read_unsigned(self) -> int: + assert self._random_value is not None + + self._random_value_read = True + return self._random_value def read_u32(self) -> int: '''Read a 32-bit unsigned result''' - return self._random_value & ((1 << 32) - 1) + return self.read_unsigned() & ((1 << 32) - 1) def write_unsigned(self, value: int) -> None: + '''Writes to RND are ignored + + Note this is different to `set_unsigned`. This is used by executing + instruction, see `set_unsigned` docstring for more details + ''' return + def commit(self) -> None: + if self._random_value_read: + self._random_value = None + self.pending_request = False + + self._random_value_read = False + + def request_value(self) -> bool: + '''Signals intent to read RND, returns True if a value is available''' + if self._random_value: + return True + + self.pending_request = True + return False + + def set_unsigned(self, value: int) -> None: + '''Sets a random value that can be read by a future `read_unsigned` + + This is different to `write_unsigned`, that is used by an executing + instruction to write to RND. This is used by the simulation environment + to provide a value that is later read by `read_unsigned` and doesn't + relate to instruction execution (e.g. in an RTL simulation it monitors + the EDN bus and supplies the simulator with an RND value when a fresh + one is seen on the EDN bus). + ''' + assert 0 <= value < (1 << 256) + self._random_value = value + class WSRFile: '''A model of the WSR file'''
diff --git a/hw/ip/otbn/dv/otbnsim/stepped.py b/hw/ip/otbn/dv/otbnsim/stepped.py index 57ba293..44edeb3 100755 --- a/hw/ip/otbn/dv/otbnsim/stepped.py +++ b/hw/ip/otbn/dv/otbnsim/stepped.py
@@ -44,17 +44,17 @@ from sim.sim import OTBNSim -def read_word(arg_name: str, word_data: str) -> int: - '''Try to read a 32-bit unsigned word''' +def read_word(arg_name: str, word_data: str, bits: int) -> int: + '''Try to read an unsigned word of the specified bit length''' try: value = int(word_data, 0) except ValueError: raise ValueError('Failed to read {!r} as an integer for <{}> argument.' .format(word_data, arg_name)) from None - if value < 0 or value >> 32: - raise ValueError('<{}> argument is {!r}: not representable as a u32.' - .format(arg_name, word_data)) + if value < 0 or value >> bits: + raise ValueError('<{}> argument is {!r}: not representable in {!r} bits.' + .format(arg_name, word_data, bits)) return value @@ -71,7 +71,7 @@ raise ValueError('start expects exactly 1 argument. Got {}.' .format(args)) - addr = read_word('addr', args[0]) + addr = read_word('addr', args[0], 32) if addr & 3: raise ValueError('start address must be word-aligned. Got {:#08x}.' .format(addr)) @@ -183,6 +183,23 @@ print('0x{:08x}'.format(value)) +def on_edn_rnd_data(sim: OTBNSim, args: List[str]) -> None: + if len(args) != 1: + raise ValueError('edn_rnd_data expects exactly 1 argument. Got {}.' + .format(args)) + + edn_rnd_data = read_word('edn_rnd_data', args[0], 256) + sim.state.set_rnd_data(edn_rnd_data) + + +def on_edn_urnd_reseed_complete(sim: OTBNSim, args: List[str]) -> None: + if args: + raise ValueError('edn_urnd_reseed_complete expects zero arguments. Got {}.' + .format(args)) + + sim.state.set_urnd_reseed_complete() + + _HANDLERS = { 'start': on_start, 'step': on_step, @@ -192,7 +209,9 @@ 'load_i': on_load_i, 'dump_d': on_dump_d, 'print_regs': on_print_regs, - 'print_call_stack': on_print_call_stack + 'print_call_stack': on_print_call_stack, + 'edn_rnd_data': on_edn_rnd_data, + 'edn_urnd_reseed_complete': on_edn_urnd_reseed_complete }
diff --git a/hw/ip/otbn/dv/tracer/rtl/otbn_trace_if.sv b/hw/ip/otbn/dv/tracer/rtl/otbn_trace_if.sv index a5162a5..de0ed74 100644 --- a/hw/ip/otbn/dv/tracer/rtl/otbn_trace_if.sv +++ b/hw/ip/otbn/dv/tracer/rtl/otbn_trace_if.sv
@@ -70,7 +70,11 @@ input otbn_pkg::alu_bignum_operation_t alu_bignum_operation, input logic mac_bignum_en, - input logic [otbn_pkg::WLEN-1:0] rnd + input logic [otbn_pkg::WLEN-1:0] rnd_data, + input logic rnd_req, + input logic rnd_valid, + + input logic [otbn_pkg::WLEN-1:0] urnd_data ); import otbn_pkg::*; @@ -196,9 +200,14 @@ assign ispr_write[IsprRnd] = 1'b0; assign ispr_write_data[IsprRnd] = '0; + assign ispr_write[IsprUrnd] = 1'b0; + assign ispr_write_data[IsprUrnd] = '0; - assign ispr_read[IsprRnd] = any_ispr_read & (ispr_addr == IsprRnd); - assign ispr_read_data[IsprRnd] = rnd; + assign ispr_read[IsprRnd] = any_ispr_read & (ispr_addr == IsprRnd) & rnd_req & rnd_valid; + assign ispr_read_data[IsprRnd] = rnd_data; + + assign ispr_read[IsprUrnd] = any_ispr_read & (ispr_addr == IsprUrnd); + assign ispr_read_data[IsprUrnd] = urnd_data; // Seperate per flag group tracking using the flags_t struct so tracer can cleanly present flag // accesses.
diff --git a/hw/ip/otbn/dv/tracer/rtl/otbn_tracer.sv b/hw/ip/otbn/dv/tracer/rtl/otbn_tracer.sv index 094dbaf..ed753d1 100644 --- a/hw/ip/otbn/dv/tracer/rtl/otbn_tracer.sv +++ b/hw/ip/otbn/dv/tracer/rtl/otbn_tracer.sv
@@ -89,6 +89,7 @@ IsprAcc: return "ACC"; IsprRnd: return "RND"; IsprFlags: return "FLAGS"; + IsprUrnd: return "URND"; default: return "UNKNOWN_ISPR"; endcase endfunction
diff --git a/hw/ip/otbn/dv/uvm/otbn_sim.core b/hw/ip/otbn/dv/uvm/otbn_sim.core index da09dec..5cd960e 100644 --- a/hw/ip/otbn/dv/uvm/otbn_sim.core +++ b/hw/ip/otbn/dv/uvm/otbn_sim.core
@@ -14,6 +14,7 @@ depend: - lowrisc:dv:otbn_test - lowrisc:dv:otbn_sva + - lowrisc:ip:edn_pkg files: - tb.sv file_type: systemVerilogSource
diff --git a/hw/ip/otbn/dv/uvm/tb.sv b/hw/ip/otbn/dv/uvm/tb.sv index 46af9f3..db72f9d 100644 --- a/hw/ip/otbn/dv/uvm/tb.sv +++ b/hw/ip/otbn/dv/uvm/tb.sv
@@ -11,6 +11,7 @@ // dep packages (rtl) import otbn_reg_pkg::*; + import edn_pkg::*; // macro includes `include "uvm_macros.svh" @@ -36,6 +37,22 @@ `DV_ALERT_IF_CONNECT + // Mock up EDN that just instantly returns fixed data when requested + // TODO: Provide a proper EDN agent + edn_req_t edn_rnd_req; + edn_rsp_t edn_rnd_rsp; + + edn_req_t edn_urnd_req; + edn_rsp_t edn_urnd_rsp; + + assign edn_rnd_rsp.edn_ack = edn_rnd_req.edn_req; + assign edn_rnd_rsp.edn_fips = 1'b0; + assign edn_rnd_rsp.edn_bus = 32'h99999999; + + assign edn_urnd_rsp.edn_ack = edn_urnd_req.edn_req; + assign edn_urnd_rsp.edn_fips = 1'b0; + assign edn_urnd_rsp.edn_bus = 32'h99999999; + // dut otbn dut ( .clk_i (clk), @@ -49,8 +66,15 @@ .intr_done_o (intr_done), .alert_rx_i (alert_rx), - .alert_tx_o (alert_tx) + .alert_tx_o (alert_tx), + .clk_edn_i (clk), + .rst_edn_ni (rst_n), + .edn_rnd_o ( edn_rnd_req ), + .edn_rnd_i ( edn_rnd_rsp ), + + .edn_urnd_o ( edn_urnd_req ), + .edn_urnd_i ( edn_urnd_rsp ) ); bind otbn_core otbn_trace_if #( @@ -60,6 +84,7 @@ bind otbn_core otbn_tracer u_otbn_tracer(.*, .otbn_trace(i_otbn_trace_if)); + // OTBN model, wrapping an ISS. // // Note that we pull the "start" signal out of the DUT. This is because it's much more difficult @@ -68,6 +93,15 @@ // decoding errors). assign model_if.start = dut.start; + // Internally otbn_core uses a 256-bit width interface for EDN data. This maps to muliple EDN + // requests at this level (via a packing FIFO internal to otbn). The model works with the internal + // otbn_core interface so probe into it here to provide the relevant signals to the model. + logic edn_rnd_data_valid; + logic edn_urnd_data_valid; + + assign edn_rnd_data_valid = dut.edn_rnd_req & dut.edn_rnd_ack; + assign edn_urnd_data_valid = dut.edn_urnd_req & dut.edn_urnd_ack; + otbn_core_model #( .DmemSizeByte (otbn_reg_pkg::OTBN_DMEM_SIZE), .ImemSizeByte (otbn_reg_pkg::OTBN_IMEM_SIZE), @@ -80,7 +114,11 @@ .start_i (model_if.start), .done_o (model_if.done), .start_addr_i (model_if.start_addr), - .err_o (model_if.err) + .err_o (model_if.err), + + .edn_rnd_data_valid_i (edn_rnd_data_valid), + .edn_rnd_data_i (dut.edn_rnd_data), + .edn_urnd_data_valid_i (edn_urnd_data_valid) ); initial begin
diff --git a/hw/ip/otbn/dv/verilator/otbn_mock_edn.sv b/hw/ip/otbn/dv/verilator/otbn_mock_edn.sv new file mode 100644 index 0000000..a9ecd53 --- /dev/null +++ b/hw/ip/otbn/dv/verilator/otbn_mock_edn.sv
@@ -0,0 +1,50 @@ +// Copyright lowRISC contributors. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +/** + * Mock EDN end point that returns a fixed value after a fixed delay used for + * OTBN simulation purposes. + */ +module otbn_mock_edn #( + parameter int Width = 256, + parameter logic [Width-1:0] FixedEdnVal = '0, + parameter int Delay = 16, + + localparam int DelayWidth = $clog2(Delay) +) ( + input clk_i, + input rst_ni, + + input edn_req_i, + output edn_ack_o, + output [Width-1:0] edn_data_o +); + parameter int MaxDelay = Delay - 1; + logic [DelayWidth-1:0] edn_req_counter; + logic edn_req_active; + logic edn_req_complete; + + assign edn_req_complete = edn_req_counter == MaxDelay[DelayWidth-1:0]; + assign edn_ack_o = edn_req_complete; + assign edn_data_o = edn_req_complete ? FixedEdnVal : '0; + + always @(posedge clk_i or negedge rst_ni) begin + if (!rst_ni) begin + edn_req_counter <= 0; + edn_req_active <= 0; + end else begin + if (edn_req_active) begin + edn_req_counter <= edn_req_counter + 4'b1; + end + + if (edn_req_i & ~edn_req_active) begin + edn_req_active <= 1; + end + + if (edn_req_complete) begin + edn_req_active <= 0; + end + end + end +endmodule
diff --git a/hw/ip/otbn/dv/verilator/otbn_top_sim.core b/hw/ip/otbn/dv/verilator/otbn_top_sim.core index c498257..39a96e0 100644 --- a/hw/ip/otbn/dv/verilator/otbn_top_sim.core +++ b/hw/ip/otbn/dv/verilator/otbn_top_sim.core
@@ -19,6 +19,7 @@ files: - otbn_top_sim.cc: { file_type: cppSource } - otbn_top_sim.sv: { file_type: systemVerilogSource } + - otbn_mock_edn.sv: { file_type: systemVerilogSource } files_verilator_waiver: files: - otbn_top_sim_waivers.vlt
diff --git a/hw/ip/otbn/dv/verilator/otbn_top_sim.sv b/hw/ip/otbn/dv/verilator/otbn_top_sim.sv index 8600de3..d057beb 100644 --- a/hw/ip/otbn/dv/verilator/otbn_top_sim.sv +++ b/hw/ip/otbn/dv/verilator/otbn_top_sim.sv
@@ -52,6 +52,8 @@ logic edn_rnd_req, edn_urnd_req; logic edn_rnd_ack, edn_urnd_ack; logic [EdnDataWidth-1:0] edn_rnd_data, edn_urnd_data; + logic edn_rnd_data_valid; + logic edn_urnd_data_valid; otbn_core #( .ImemSizeByte ( ImemSizeByte ), @@ -107,14 +109,35 @@ logic unused_imem_top_rdata; assign unused_imem_top_rdata = &{1'b0, imem_rdata[38:32]}; - // Tie-off EDN signals, eventually simulation will provide something here for testing RND - logic unused_edn_rnd_req, unused_edn_urnd_req; - assign unused_edn_rnd_req = edn_rnd_req; - assign unused_edn_urnd_req = edn_urnd_req; - assign edn_rnd_ack = 1'b0; - assign edn_rnd_data = '0; - assign edn_urnd_ack = 1'b0; - assign edn_urnd_data = '0; + localparam logic [WLEN-1:0] FixedEdnVal = {{(WLEN / 4){4'h9}}}; + + otbn_mock_edn #( + .Width ( WLEN ), + .FixedEdnVal ( FixedEdnVal ) + ) u_mock_rnd_edn( + .clk_i ( IO_CLK ), + .rst_ni ( IO_RST_N ), + + .edn_req_i ( edn_rnd_req ), + .edn_ack_o ( edn_rnd_ack ), + .edn_data_o ( edn_rnd_data ) + ); + + assign edn_rnd_data_valid = edn_rnd_req & edn_rnd_ack; + + otbn_mock_edn #( + .Width ( WLEN ), + .FixedEdnVal ( FixedEdnVal ) + ) u_mock_urnd_edn( + .clk_i ( IO_CLK ), + .rst_ni ( IO_RST_N ), + + .edn_req_i ( edn_urnd_req ), + .edn_ack_o ( edn_urnd_ack ), + .edn_data_o ( edn_urnd_data ) + ); + + assign edn_urnd_data_valid = edn_urnd_req & edn_urnd_ack; bind otbn_core otbn_trace_if #(.ImemAddrWidth, .DmemAddrWidth) i_otbn_trace_if (.*); bind otbn_core otbn_tracer u_otbn_tracer(.*, .otbn_trace(i_otbn_trace_if)); @@ -237,17 +260,21 @@ .MemScope ( ".." ), .DesignScope ( DesignScope ) ) u_otbn_core_model ( - .clk_i ( IO_CLK ), - .rst_ni ( IO_RST_N ), + .clk_i ( IO_CLK ), + .rst_ni ( IO_RST_N ), - .start_i ( otbn_start ), - .done_o ( otbn_model_done ), + .start_i ( otbn_start ), + .done_o ( otbn_model_done ), - .start_addr_i ( ImemStartAddr ), + .start_addr_i ( ImemStartAddr ), - .err_bits_o ( otbn_model_err_bits ), + .err_bits_o ( otbn_model_err_bits ), - .err_o ( otbn_model_err ) + .err_o ( otbn_model_err ), + + .edn_rnd_data_valid_i ( edn_rnd_data_valid ), + .edn_rnd_data_i ( edn_rnd_data ), + .edn_urnd_data_valid_i ( edn_urnd_data_valid ) ); bit done_mismatch_latched, err_bits_mismatch_latched;