[otbn,dv] Connect status output to otbn_scoreboard

This ensures that the scoreboard knows when the ISS becomes locked.
There's a bit more wiring still to do (passing out things like
FATAL_ALERT_CAUSE), but this is hopefully a reasonable start.

Signed-off-by: Rupert Swarbrick <rswarbrick@lowrisc.org>
diff --git a/hw/ip/otbn/dv/uvm/env/otbn_scoreboard.sv b/hw/ip/otbn/dv/uvm/env/otbn_scoreboard.sv
index 169d8b0..efe0056 100644
--- a/hw/ip/otbn/dv/uvm/env/otbn_scoreboard.sv
+++ b/hw/ip/otbn/dv/uvm/env/otbn_scoreboard.sv
@@ -45,10 +45,15 @@
   bit saw_start_tl_trans = 1'b0;
   bit waiting_for_model = 1'b0;
 
-  // The "running" field uses the OtbnModelStart and OtbnModelDone items on the model FIFO to track
-  // whether we think OTBN is running at the moment. We know that this is in sync with the RTL
-  // because of the MatchingDone_A assertion at testbench level.
-  bit running = 1'b0;
+  // The mirrored STATUS register from the ISS.
+  bit [7:0] model_status = 1'b0;
+
+  // The "locked" field is used to track whether OTBN is "locked". For most operational state
+  // tracking, we go through the ISS, but OTBN can become locked without actually starting an
+  // operation (for example, there might be a malformed TL transaction). We spot that sort of thing
+  // here, updating the "locked" flag if either the ISS says to do so OR if we see something 'out of
+  // band'.
+  bit locked = 1'b0;
 
   `uvm_component_new
 
@@ -87,6 +92,9 @@
     // Clear saw_start_tl_trans. Any "start" TL transaction will have been discarded by the reset,
     // so we shouldn't still be tracking it.
     saw_start_tl_trans = 1'b0;
+
+    // Clear the locked bit (this is modelling RTL state that should be cleared on reset)
+    locked = 1'b0;
   endfunction
 
   task process_tl_access(tl_seq_item item, tl_channels_e channel, string ral_name);
@@ -152,8 +160,12 @@
       end
 
       "status": begin
-        // Status register
-        exp_read_data = '{upd: 1'b1, chk: 1'b1, val: {31'd0, running}};
+        // Status register. If we're locked, this should read 0xff. Otherwise, fall back to the
+        // expected operational state (IDLE = 0; BUSY_EXECUTE = 1; BUSY_SEC_WIPE_DMEM = 2;
+        // BUSY_SEC_WIPE_IMEM = 3).
+        //
+        // TODO: Track states other than IDLE and BUSY_EXECUTE.
+        exp_read_data = '{upd: 1'b1, chk: 1'b1, val: locked ? 32'hff : model_status};
       end
 
       "err_bits": begin
@@ -198,10 +210,13 @@
     uvm_reg_addr_t       csr_addr;
     otbn_exp_read_data_t exp_read_data;
 
-    // We're only interested in reads. Ignore any acks for memory or register writes that came in on
-    // the A channel.
-    if (item.is_write())
+    // The data-channel response to a write is just an ack, which isn't particularly interesting.
+    // Check for integrity errors (which should lock the block), but otherwise there's nothing to
+    // do.
+    if (item.is_write()) begin
+      if (item.d_error) locked = 1'b1;
       return;
+    end
 
     // We're also only interested in registers; the scoreboard doesn't explicitly model memories in
     // the RAL. Look to see if this is a valid register address. If not, it was to a memory and we
@@ -247,18 +262,20 @@
           // previous cycle.
           `DV_CHECK_FATAL(waiting_for_model, "model started unbidden!")
           waiting_for_model = 1'b0;
-          running = 1'b1;
         end
 
-        OtbnModelDone: begin
-          `DV_CHECK_FATAL(running, "Got done signal when we didn't think model was running.")
-          running = 1'b0;
+        OtbnModelStatus: begin
+          model_status = item.status;
         end
 
         OtbnModelInsn: begin
           // The model agent's monitor should be configured to only emit OtbnModelInsn items if
           // coverage is enabled.
           `DV_CHECK_FATAL(cfg.en_cov)
+          // We don't expect any instructions unless we're currently running something.
+          `DV_CHECK_EQ_FATAL(model_status, 1 /* BUSY_EXECUTE */,
+                             "Saw instruction when not in BUSY_EXECUTE operational state.")
+
           iss_trace_queue.push_back(item);
           pop_trace_queues();
         end
diff --git a/hw/ip/otbn/dv/uvm/otbn_model_agent/otbn_model_agent_pkg.sv b/hw/ip/otbn/dv/uvm/otbn_model_agent/otbn_model_agent_pkg.sv
index 035afda..90c1271 100644
--- a/hw/ip/otbn/dv/uvm/otbn_model_agent/otbn_model_agent_pkg.sv
+++ b/hw/ip/otbn/dv/uvm/otbn_model_agent/otbn_model_agent_pkg.sv
@@ -13,7 +13,7 @@
 
   typedef enum {
     OtbnModelStart,
-    OtbnModelDone,
+    OtbnModelStatus,
     OtbnModelInsn
   } otbn_model_item_type_e;
 
diff --git a/hw/ip/otbn/dv/uvm/otbn_model_agent/otbn_model_if.sv b/hw/ip/otbn/dv/uvm/otbn_model_agent/otbn_model_if.sv
index 8896856..29bdf32 100644
--- a/hw/ip/otbn/dv/uvm/otbn_model_agent/otbn_model_if.sv
+++ b/hw/ip/otbn/dv/uvm/otbn_model_agent/otbn_model_if.sv
@@ -19,18 +19,16 @@
   bit                       done;         // Operation done
   bit                       err;          // Something went wrong
   bit [31:0]                stop_pc;      // PC at end of operation
+
+  // Mirrored registers
+  bit [7:0]                 status;       // STATUS register
+
   chandle                   handle;       // Handle for DPI calls to C model
 
-  // Wait until done goes high. Stops early on reset
-  task automatic wait_done();
-    while (rst_ni === 1'b1 && !done) begin
-      @(posedge clk_i or negedge rst_ni);
-    end
-  endtask
-
-  // Wait until done goes low. Stops early on reset
-  task automatic wait_not_done();
-    while (rst_ni === 1'b1 && done) begin
+  // Wait until reset or change of status.
+  task automatic wait_status();
+    bit old_status = status;
+    while (rst_ni === 1'b1 && status == old_status) begin
       @(posedge clk_i or negedge rst_ni);
     end
   endtask
diff --git a/hw/ip/otbn/dv/uvm/otbn_model_agent/otbn_model_item.sv b/hw/ip/otbn/dv/uvm/otbn_model_agent/otbn_model_item.sv
index 0a0cc86..7ea57f0 100644
--- a/hw/ip/otbn/dv/uvm/otbn_model_agent/otbn_model_item.sv
+++ b/hw/ip/otbn/dv/uvm/otbn_model_agent/otbn_model_item.sv
@@ -7,7 +7,8 @@
   // What sort of transaction is this?
   otbn_model_item_type_e item_type;
 
-  // Only valid when item_type == OtbnModelDone.
+  // Only valid when item_type == OtbnModelStatus.
+  bit [7:0]              status;
   bit                    err;
 
   // Only valid when item_type == OtbnModelInsn
@@ -16,6 +17,7 @@
 
   `uvm_object_utils_begin(otbn_model_item)
     `uvm_field_enum   (otbn_model_item_type_e, item_type, UVM_DEFAULT)
+    `uvm_field_int    (status, UVM_DEFAULT)
     `uvm_field_int    (err, UVM_DEFAULT)
     `uvm_field_int    (insn_addr, UVM_DEFAULT | UVM_HEX)
     `uvm_field_string (mnemonic, UVM_DEFAULT)
diff --git a/hw/ip/otbn/dv/uvm/otbn_model_agent/otbn_model_monitor.sv b/hw/ip/otbn/dv/uvm/otbn_model_agent/otbn_model_monitor.sv
index 8ada50e..c302d1b 100644
--- a/hw/ip/otbn/dv/uvm/otbn_model_agent/otbn_model_monitor.sv
+++ b/hw/ip/otbn/dv/uvm/otbn_model_agent/otbn_model_monitor.sv
@@ -22,7 +22,7 @@
   protected task collect_trans(uvm_phase phase);
     fork
       collect_start();
-      collect_done();
+      collect_status();
       // TODO: Only run when coverage is enabled.
       collect_insns();
     join
@@ -38,6 +38,7 @@
         if (cfg.vif.start) begin
           trans = otbn_model_item::type_id::create("trans");
           trans.item_type = OtbnModelStart;
+          trans.status    = 0;
           trans.err       = 0;
           trans.mnemonic  = "";
           analysis_port.write(trans);
@@ -46,23 +47,23 @@
     end
   endtask
 
-  protected task collect_done();
+  protected task collect_status();
     otbn_model_item trans;
 
     forever begin
-      // wait until vif signals done (or we are in reset)
-      cfg.vif.wait_done();
+      // Wait until vif signals a change in status (or we are in reset)
+      cfg.vif.wait_status();
 
       if (cfg.vif.rst_ni === 1'b1) begin
         // We aren't in reset, so we've just seen the done signal go high.
         trans = otbn_model_item::type_id::create("trans");
-        trans.item_type = OtbnModelDone;
+        trans.item_type = OtbnModelStatus;
+        trans.status    = cfg.vif.status;
         trans.err       = cfg.vif.err;
         trans.mnemonic  = "";
         analysis_port.write(trans);
-        cfg.vif.wait_not_done();
       end else begin
-        // We are in reset. Wait until we aren't (we need to do this because wait_done() returns
+        // We are in reset. Wait until we aren't (we need to do this because wait_status() returns
         // immediately in reset)
         wait(cfg.vif.rst_ni);
       end
@@ -89,6 +90,7 @@
         if (otbn_trace_checker_pop_iss_insn(insn_addr, insn_mnemonic)) begin
           trans = otbn_model_item::type_id::create("trans");
           trans.item_type = OtbnModelInsn;
+          trans.status    = 0;
           trans.err       = 0;
           trans.insn_addr = insn_addr;
           trans.mnemonic  = insn_mnemonic;
diff --git a/hw/ip/otbn/dv/uvm/tb.sv b/hw/ip/otbn/dv/uvm/tb.sv
index 3ba5021..9534b9e 100644
--- a/hw/ip/otbn/dv/uvm/tb.sv
+++ b/hw/ip/otbn/dv/uvm/tb.sv
@@ -191,7 +191,7 @@
     .edn_rnd_data_i        (dut.edn_rnd_data),
     .edn_urnd_data_valid_i (edn_urnd_data_valid),
 
-    .status_o     (),
+    .status_o     (model_if.status),
     .insn_cnt_o   (model_insn_cnt),
 
     .err_o        (model_if.err)