diff --git a/hw/dv/sv/spi_agent/spi_agent_cfg.sv b/hw/dv/sv/spi_agent/spi_agent_cfg.sv index 5adc8135b5010..e84eff699cc92 100644 --- a/hw/dv/sv/spi_agent/spi_agent_cfg.sv +++ b/hw/dv/sv/spi_agent/spi_agent_cfg.sv @@ -19,7 +19,7 @@ class spi_agent_cfg extends dv_base_agent_cfg; bit partial_byte; // Transfering less than byte bit [3:0] bits_to_transfer; // Bits to transfer if using less than byte bit decode_commands; // Used in monitor if decoding of commands needed - bit [2:0] cmd_addr_size = 4; //Address size for command + bit [2:0] cmd_addr_size = 4; // Address size for command //------------------------- // spi_host regs diff --git a/hw/dv/sv/spi_agent/spi_host_driver.sv b/hw/dv/sv/spi_agent/spi_host_driver.sv index 8eb403e6f958a..a4daaa11c4a43 100644 --- a/hw/dv/sv/spi_agent/spi_host_driver.sv +++ b/hw/dv/sv/spi_agent/spi_host_driver.sv @@ -198,10 +198,14 @@ class spi_host_driver extends spi_driver; if (req.write_command) begin issue_data(req.payload_q, dummy_return_q); end else begin + int iter = 0; repeat (req.read_size) begin logic [7:0] data; cfg.read_byte(.num_lanes(req.num_lanes), .is_device_rsp(1), .csb_id(active_csb), .data(data)); + `uvm_info(`gfn, $sformatf("HOST_DRV: total_size=%0d - iter=%0d - read_byte=0x%0x", + req.read_size, iter, data), UVM_DEBUG) + iter++; rsp.payload_q.push_back(data); end `uvm_info(`gfn, $sformatf("collect read data for flash: 0x%p", rsp.payload_q), UVM_HIGH) diff --git a/hw/dv/sv/spi_agent/spi_item.sv b/hw/dv/sv/spi_agent/spi_item.sv index 2f02965cb83cb..6f4be9d71c24d 100644 --- a/hw/dv/sv/spi_agent/spi_item.sv +++ b/hw/dv/sv/spi_agent/spi_item.sv @@ -32,6 +32,9 @@ class spi_item extends uvm_sequence_item; // of wait until the entire item is collected. This indicates item has collected all data. bit mon_item_complete; + // Triggered for each byte sampled and when CSB becomes inactive + event byte_sampled_ev, item_finished_ev; + // constrain size of data sent / received to be at most 64kB constraint data_size_c { data.size() inside {[0:65536]}; } diff --git a/hw/dv/sv/spi_agent/spi_monitor.sv b/hw/dv/sv/spi_agent/spi_monitor.sv index 55172d8e10899..387493796bc00 100644 --- a/hw/dv/sv/spi_agent/spi_monitor.sv +++ b/hw/dv/sv/spi_agent/spi_monitor.sv @@ -23,13 +23,16 @@ class spi_monitor extends dv_base_monitor#( // Analysis port for the collected transfer. uvm_analysis_port #(spi_item) host_analysis_port; uvm_analysis_port #(spi_item) device_analysis_port; + // Sends txn on CSB deassertion (CSB becoming active) + uvm_analysis_port #(spi_item) csb_active_analysis_port; `uvm_component_new function void build_phase(uvm_phase phase); super.build_phase(phase); - host_analysis_port = new("host_analysis_port", this); - device_analysis_port = new("device_analysis_port", this); + host_analysis_port = new("host_analysis_port", this); + device_analysis_port = new("device_analysis_port", this); + csb_active_analysis_port = new("csb_active_analysis_port", this); endfunction virtual task run_phase(uvm_phase phase); @@ -46,9 +49,9 @@ class spi_monitor extends dv_base_monitor#( cfg.vif.sck_polarity = cfg.sck_polarity[active_csb]; cfg.vif.sck_phase = cfg.sck_phase[active_csb]; - host_item = spi_item::type_id::create("host_item", this); device_item = spi_item::type_id::create("device_item", this); + csb_active_analysis_port.write(host_item); fork begin : isolation_thread fork @@ -75,6 +78,11 @@ class spi_monitor extends dv_base_monitor#( disable fork; end : isolation_thread join + + -> host_item.item_finished_ev; + `uvm_info(`gfn, "Triggering 'host_item.item_finished' since CSB just became inactive", + UVM_DEBUG) + // write to host_analysis_port case (cfg.spi_func_mode) SpiModeFlash: begin @@ -90,7 +98,7 @@ class spi_monitor extends dv_base_monitor#( end end default: ; // do nothing, in SpiModeGeneric, it writes to fifo for each byte - endcase + endcase // case (cfg.spi_func_mode) endtask : collect_trans virtual protected task collect_curr_trans(); @@ -238,17 +246,26 @@ class spi_monitor extends dv_base_monitor#( num_addr_bytes, item.write_command, item.num_lanes, item.dummy_cycles); `uvm_info(`gfn, $sformatf("sampled flash opcode: 0x%0h", item.opcode), UVM_HIGH) + `uvm_info(`gfn, "Triggering 'host_item.byte_sampled' after sampling opcode", UVM_DEBUG) + -> host_item.byte_sampled_ev; sample_address(num_addr_bytes, item.address_q); - + if(item.address_q.size > 0) begin + `uvm_info(`gfn, "Triggering 'host_item.byte_sampled' after sampling address", UVM_DEBUG) + -> host_item.byte_sampled_ev; + end repeat (item.dummy_cycles) begin cfg.wait_sck_edge(SamplingEdge, active_csb); end + `uvm_info(`gfn, $sformatf("Sending {opcode=0x%0x,address=%p} on the 'req_analysis_port'", + item.opcode, item.address_q), UVM_DEBUG) req_analysis_port.write(item); forever begin logic[7:0] byte_data; sample_and_check_byte(item.num_lanes, !item.write_command, byte_data); item.payload_q.push_back(byte_data); + `uvm_info(`gfn, "Triggering 'host_item.byte_sampled' after sampling a data byte", UVM_DEBUG) + -> host_item.byte_sampled_ev; end endtask : collect_flash_trans @@ -262,6 +279,9 @@ class spi_monitor extends dv_base_monitor#( decode_tpm_cmd(cmd, item.write_command, size); if (!item.write_command) item.read_size = size; `uvm_info(`gfn, $sformatf("Received TPM command: 0x%0x", cmd), UVM_MEDIUM) + `uvm_info(`gfn, "Triggering 'host_item.byte_sampled' after sampling opcode", UVM_DEBUG) + -> host_item.byte_sampled_ev; + // read 3 bytes address fork begin : tpm_sio_0 @@ -279,6 +299,12 @@ class spi_monitor extends dv_base_monitor#( end join `uvm_info(`gfn, $sformatf("Received TPM addr: %p", item.address_q), UVM_MEDIUM) + + if(item.address_q.size > 0) begin + `uvm_info(`gfn, "Triggering 'host_item.byte_sampled' after sampling address", UVM_DEBUG) + -> host_item.byte_sampled_ev; + end + req_analysis_port.write(item); // poll TPM_START @@ -297,6 +323,9 @@ class spi_monitor extends dv_base_monitor#( .data(item.data[i]), .check_data_not_z(1)); `uvm_info(`gfn, $sformatf("collect %s data for TPM, idx %0d: 0x%0x", item.write_command ? "write" : "read", i, item.data[i]), UVM_MEDIUM) + `uvm_info(`gfn, "Triggering 'host_item.byte_sampled' after sampling a data byte", UVM_DEBUG) + -> host_item.byte_sampled_ev; + end endtask diff --git a/hw/ip/spi_device/dv/env/seq_lib/spi_device_intercept_vseq.sv b/hw/ip/spi_device/dv/env/seq_lib/spi_device_intercept_vseq.sv index 30204dfddf1a7..6151d379cddfb 100644 --- a/hw/ip/spi_device/dv/env/seq_lib/spi_device_intercept_vseq.sv +++ b/hw/ip/spi_device/dv/env/seq_lib/spi_device_intercept_vseq.sv @@ -34,10 +34,132 @@ class spi_device_intercept_vseq extends spi_device_pass_cmd_filtering_vseq; super.pre_start(); endtask + virtual task random_cycle_delay(); + int unsigned cycle_delay; + if(!std::randomize(cycle_delay) with { cycle_delay dist {[1:10] := 80, + [11:20] := 15, + [21:50] := 4, + [51:90] := 1 + }; }) + `uvm_fatal(`gfn, "Randomization Failure") + `uvm_info(`gfn, $sformatf("Inserting %0d TL-UL cycle delays",cycle_delay), UVM_DEBUG) + cfg.clk_rst_vif.wait_clks(cycle_delay); + endtask + // randomly set flash_status for every spi transaction virtual task spi_host_xfer_flash_item(bit [7:0] op, uint payload_size, bit [31:0] addr, bit wait_on_busy = 1); - random_access_flash_status(); - super.spi_host_xfer_flash_item(op, payload_size, addr, wait_on_busy); + typedef enum {sequential_access, concurrent_access, two_writes, wrong} access_option_t; + + bit delay_free; + bit spi_command_finished; + access_option_t access_option; + // Note: there shouldn't be two flash_status writes in a row without SPI command in between + // The RTL could absord a second write, but it wouldn't be correct operation in terms of SW + // behaviour. In theory, one could write the upper-bits of flash_status, followed by another + // write to clear the busy bit (done in least probable case within the randcase below). + // + // In practice, SW writes to flash_status should only occur as a response to a command which + // sets the busy bit. + // When the RTL processes a read_status command internally, it commits a whole byte of status + // bits to return to the host. So if SW were to write flash_status whilst the host was sending + // a READ_STATUS command, and the host left the CSB line low "fow a while" expecting for + // instance to read the busy bit unset, the host would see complete bytes written to + // flash_status and not a byte made of two different writes + + access_option = wrong; + randcase + 25: access_option = sequential_access; + 70: access_option = concurrent_access; + 5 : access_option = two_writes; + endcase // randcase + + case (access_option) + sequential_access: begin // Sequential accesses + random_access_flash_status(); + super.spi_host_xfer_flash_item(op, payload_size, addr, wait_on_busy); + end + concurrent_access: begin // Concurrent accesses + fork + begin + bit send_write, write_sent; + + while (spi_command_finished==0) begin + randcase + 9: delay_free = 0; + 1: delay_free = 1; + endcase + + if (!delay_free) + random_cycle_delay(); + + if (!write_sent) begin + // Only randomize until we send the first write + if(!std::randomize(send_write) with { send_write dist {1 := 15, 0 := 85}; }) + `uvm_fatal(`gfn, "Randomization Failure") + end + // Sending 1-write only + if (!write_sent && send_write) begin + random_access_flash_status(.write(send_write)); + write_sent = 1; + end + else begin + random_access_flash_status(.write(0)); + end + end // while (spi_command_finished==0) + end + begin + // Thread gets killed on the SPI command below completing + super.spi_host_xfer_flash_item(op, payload_size, addr, wait_on_busy); + spi_command_finished = 1; + end + join + + end // case: concurrent_access + two_writes: begin + // keep reading flash_status, and if the busy bit was set, then we send two writes + // to flash_status, the first setting the upper bits, and the second clearing the + // WEL/BUSY bits + fork + begin + bit [TL_DW-1:0] read_status; + bit [21:0] other_status; + + while (spi_command_finished==0) begin + randcase + 9: delay_free = 0; + 1: delay_free = 1; + endcase + + if (!delay_free) + random_cycle_delay(); + + // Keep reading until the busy bit is set + if (read_status[0] == 0) + csr_rd(ral.flash_status, read_status); + else begin + // Busy bit is set set - setting first the upper bits of flash_status: + other_status = $urandom(); + random_access_flash_status(.write(1), .busy(1), .wel(1), + .other_status(other_status)); + cfg.clk_rst_vif.wait_clks($urandom_range(1,5)); + + // Clearing busy and wel bits + random_access_flash_status(.write(1), .busy(0), .wel(0), + .other_status(other_status)); + read_status[0] = 0; // Busy bit has been cleared + end + end // forever begin + end + begin + // Thread gets killed on the SPI command below completing + super.spi_host_xfer_flash_item(op, payload_size, addr, wait_on_busy); + spi_command_finished = 1; + end + join + end // case: two_writes + default: `uvm_fatal(`gfn, "Wrong case statement") + endcase endtask + endclass : spi_device_intercept_vseq diff --git a/hw/ip/spi_device/dv/env/seq_lib/spi_device_pass_base_vseq.sv b/hw/ip/spi_device/dv/env/seq_lib/spi_device_pass_base_vseq.sv index a2ac4322050ca..86588804f4d98 100644 --- a/hw/ip/spi_device/dv/env/seq_lib/spi_device_pass_base_vseq.sv +++ b/hw/ip/spi_device/dv/env/seq_lib/spi_device_pass_base_vseq.sv @@ -793,12 +793,22 @@ class spi_device_pass_base_vseq extends spi_device_base_vseq; bit is_watermark; // use zero_delay write, otherwise, spi may read faster than SW write bit zero_delay_write = 1; + int unsigned delay = $urandom_range(10, 200); - cfg.clk_rst_vif.wait_clks($urandom_range(10, 200)); + // We read 'intr_state' through the backdoor every cycle to see if a flip/watermark + // event occurs. Just because otherwise, if we apply a "random delay" it's possible the + // new value will be written at a time the RTL has already "latched" the older value and + // there will be a mem comparison mismatch + // Once we read a readbufflip/watermark interrupt via backdoor, then do a front-door read + // to keep the predictions intact + cfg.clk_rst_vif.wait_clks(1); + csr_rd(.ptr(ral.intr_state), .value(intr_state_val), .backdoor(1)); - csr_rd(ral.intr_state, intr_state_val); is_flip = get_field_val(ral.intr_state.readbuf_flip, intr_state_val); is_watermark = get_field_val(ral.intr_state.readbuf_watermark, intr_state_val); + if (is_flip || is_watermark) // Front-door read to keep the SCB predictions intact + csr_rd(ral.intr_state, intr_state_val); + // clear flip and watermark event before updating mem // if clear too late, scb is hard to handle as the interrupt may happen again @@ -807,10 +817,8 @@ class spi_device_pass_base_vseq extends spi_device_base_vseq; if (is_watermark) intr_state_val[ReadbufWatermark] = 1; if (!is_flip && !is_watermark) continue; - // Wait for register access to complete - while(ral.intr_state.is_busy()) begin - cfg.clk_rst_vif.wait_clks(1); - end + + cfg.clk_rst_vif.wait_clks(1); csr_wr(ral.intr_state, intr_state_val); if (is_flip) begin diff --git a/hw/ip/spi_device/dv/env/spi_device_env.sv b/hw/ip/spi_device/dv/env/spi_device_env.sv index 3a6fb80dbbe36..7a9a95bc711f0 100644 --- a/hw/ip/spi_device/dv/env/spi_device_env.sv +++ b/hw/ip/spi_device/dv/env/spi_device_env.sv @@ -35,6 +35,9 @@ class spi_device_env extends cip_base_env #( scoreboard.upstream_spi_device_fifo.analysis_export); spi_host_agent.monitor.req_analysis_port.connect( scoreboard.upstream_spi_req_fifo.analysis_export); + spi_host_agent.monitor.csb_active_analysis_port.connect( + scoreboard.upstream_csb_active_fifo.analysis_export); + spi_device_agent.monitor.host_analysis_port.connect( scoreboard.downstream_spi_host_fifo.analysis_export); end diff --git a/hw/ip/spi_device/dv/env/spi_device_scoreboard.sv b/hw/ip/spi_device/dv/env/spi_device_scoreboard.sv index c911cd1513eca..35fec7013bdca 100644 --- a/hw/ip/spi_device/dv/env/spi_device_scoreboard.sv +++ b/hw/ip/spi_device/dv/env/spi_device_scoreboard.sv @@ -26,6 +26,9 @@ class spi_device_scoreboard extends cip_base_scoreboard #(.CFG_T (spi_device_env uvm_tlm_analysis_fifo #(spi_item) upstream_spi_req_fifo; uvm_tlm_analysis_fifo #(spi_item) downstream_spi_host_fifo; + // RX a SPI-txn the when CSB is active + uvm_tlm_analysis_fifo #(spi_item) upstream_csb_active_fifo; + // mem model to save expected value local mem_model tx_mem; local mem_model rx_mem; @@ -52,12 +55,28 @@ class spi_device_scoreboard extends cip_base_scoreboard #(.CFG_T (spi_device_env // once triggered, it won't be triggered again until it flips bit read_buffer_watermark_triggered; - flash_status_t flash_status_q[$]; - flash_status_t flash_status_settle_q[$]; - // this queue contains the previous value of flash_status - // it's ok the readback value matches to either this one or the mirror value - // after a flash_status access, delete this queue as the uncertain window is only a few cycles - flash_status_t flash_status_tl_pre_val_q[$]; + // Predicted spi-side flash status + flash_status_t spi_side_flash_status; + // flash_status value written on TL-UL. There can be several (RTL supports up to 2) writes on the + // TL-UL CDC side before it's seen on the SPI CDC side. + flash_status_t tl_ul_side_flash_status_q[$]; + // Needed for when TL-UL read occurs before CDC transition from SPI->UL after CSB is inactive. + // It'll have up to 2 items max. That's because once CSB goes high, if SW reads flash_status we + // may get the "old old" flash_status, or the new "old" value depending on when the read arrives + flash_status_t tl_ul_old_flash_status_q[$]; + // This queue will have 1 entry at most, and it'll be populated whenever SW writes to flash_status + // Used to store values written not known if yet committed to the TL-UL-side + flash_status_t tl_ul_fuzzy_flash_status_q[$]; + // This queue will have 1 entry at most, and it'll be populated if there's a SW write used to + // store values written not known if yet committed to the SPI-side + flash_status_t spi_side_fuzzy_flash_status_q[$]; + + // Triggered after TB updates spi-side status bits from TL-UL to compare READ_STATUS command bits + event check_spi_status_bits_ev; + + // Triggered after CSB is asserted + event CSB_not_active_ev; + // for TPM mode local spi_item tpm_read_sw_q[$]; @@ -74,6 +93,7 @@ class spi_device_scoreboard extends cip_base_scoreboard #(.CFG_T (spi_device_env upstream_spi_device_fifo = new("upstream_spi_device_fifo", this); upstream_spi_req_fifo = new("upstream_spi_req_fifo", this); downstream_spi_host_fifo = new("downstream_spi_host_fifo", this); + upstream_csb_active_fifo = new("upstream_csb_active_fifo",this); tx_mem = mem_model#()::type_id::create("tx_mem", this); rx_mem = mem_model#()::type_id::create("rx_mem", this); @@ -87,8 +107,8 @@ class spi_device_scoreboard extends cip_base_scoreboard #(.CFG_T (spi_device_env process_upstream_spi_host_fifo(); process_upstream_spi_device_fifo(); process_downstream_spi_fifo(); - process_read_buffer_cmd(); - forever_latch_flash_status(); + upstream_req_fifo_distributor(); + process_flash_tl2spi_updates(); process_clear_mems(); join_none endtask @@ -127,65 +147,70 @@ class spi_device_scoreboard extends cip_base_scoreboard #(.CFG_T (spi_device_env // downstream item should be in the queue at the same time, add small delay #1ps; cmd_type = triage_flash_cmd(item.opcode, set_busy); - `uvm_info(`gfn, $sformatf("Triage flash cmd: %s, set_busy: %0d", cmd_type, set_busy), + `uvm_info(`gfn, + $sformatf("Triage flash cmd: %s, set_busy: %0d", cmd_type.name, set_busy), UVM_MEDIUM) + if (set_busy) + spi_side_flash_status.busy = 1; is_intercepted = 1; - case (cmd_type) - NoInternalProcess: begin - is_intercepted = 0; - end - InternalProcessStatus: begin - check_internal_processed_read_status(item); - end - InternalProcessJedec: begin - check_internal_processed_read_jedec(item); - end - InternalProcessSfdp: begin - check_internal_processed_read_sfdp(item); - end - InternalProcessReadCmd: begin - spi_item downstream_item; - if (is_opcode_passthrough(item.opcode)) begin - `DV_CHECK_EQ_FATAL(spi_passthrough_downstream_q.size, 1) - downstream_item = spi_passthrough_downstream_q[0]; + // InternalProcessStatus is handled by its own process + if (cmd_type != InternalProcessStatus) begin + case (cmd_type) + NoInternalProcess: begin + is_intercepted = 0; end - check_read_cmd_data_for_non_read_buffer(item, downstream_item); - end - InternalProcessCfgCmd: begin - bit prev_wel = `gmv(ral.flash_status.wel); - if (`GET_OPCODE_VALID_AND_MATCH(cmd_info_en4b, item.opcode)) begin - if (cfg.en_cov) begin - cov.spi_device_addr_4b_enter_exit_command_cg.sample( - .addr_4b_en(1), .prev_addr_4b_en(`gmv(ral.addr_mode.addr_4b_en))); + InternalProcessJedec: begin + check_internal_processed_read_jedec(item); + end + InternalProcessSfdp: begin + check_internal_processed_read_sfdp(item); + end + InternalProcessReadCmd: begin + spi_item downstream_item; + if (is_opcode_passthrough(item.opcode)) begin + `DV_CHECK_EQ_FATAL(spi_passthrough_downstream_q.size, 1) + downstream_item = spi_passthrough_downstream_q[0]; end - void'(ral.addr_mode.addr_4b_en.predict(.value(1), .kind(UVM_PREDICT_WRITE))); - `uvm_info(`gfn, "Enable 4b addr due to cmd EN4B", UVM_MEDIUM) - end else if (`GET_OPCODE_VALID_AND_MATCH(cmd_info_ex4b, item.opcode)) begin - if (cfg.en_cov) begin - cov.spi_device_addr_4b_enter_exit_command_cg.sample( - .addr_4b_en(0), .prev_addr_4b_en(`gmv(ral.addr_mode.addr_4b_en))); + check_read_cmd_data_for_non_read_buffer(item, downstream_item); + end + InternalProcessCfgCmd: begin + bit prev_wel = `gmv(ral.flash_status.wel); + if (`GET_OPCODE_VALID_AND_MATCH(cmd_info_en4b, item.opcode)) begin + if (cfg.en_cov) begin + cov.spi_device_addr_4b_enter_exit_command_cg.sample( + .addr_4b_en(1), .prev_addr_4b_en(`gmv(ral.addr_mode.addr_4b_en))); + end + void'(ral.addr_mode.addr_4b_en.predict(.value(1), .kind(UVM_PREDICT_WRITE))); + `uvm_info(`gfn, "Enable 4b addr due to cmd EN4B", UVM_MEDIUM) + end else if (`GET_OPCODE_VALID_AND_MATCH(cmd_info_ex4b, item.opcode)) begin + if (cfg.en_cov) begin + cov.spi_device_addr_4b_enter_exit_command_cg.sample( + .addr_4b_en(0), .prev_addr_4b_en(`gmv(ral.addr_mode.addr_4b_en))); + end + void'(ral.addr_mode.addr_4b_en.predict(.value(0), .kind(UVM_PREDICT_WRITE))); + `uvm_info(`gfn, "Disable 4b addr due to cmd EX4B", UVM_MEDIUM) + end else if (`GET_OPCODE_VALID_AND_MATCH(cmd_info_wren, item.opcode)) begin + update_wel = 1; + wel_val = 1; + spi_side_flash_status.wel = 1; + end else if (`GET_OPCODE_VALID_AND_MATCH(cmd_info_wrdi, item.opcode)) begin + update_wel = 1; + wel_val = 0; + spi_side_flash_status.wel = 0; + end else begin + `uvm_fatal(`gfn, $sformatf("shouldn't enter here, opcode 0x%0x", item.opcode)) + end + if (cfg.en_cov && update_wel) begin + cov.spi_device_write_enable_disable_cg.sample(wel_val, prev_wel); end - void'(ral.addr_mode.addr_4b_en.predict(.value(0), .kind(UVM_PREDICT_WRITE))); - `uvm_info(`gfn, "Disable 4b addr due to cmd EX4B", UVM_MEDIUM) - end else if (`GET_OPCODE_VALID_AND_MATCH(cmd_info_wren, item.opcode)) begin - update_wel = 1; - wel_val = 1; - end else if (`GET_OPCODE_VALID_AND_MATCH(cmd_info_wrdi, item.opcode)) begin - update_wel = 1; - wel_val = 0; - end else begin - `uvm_fatal(`gfn, $sformatf("shouldn't enter here, opcode 0x%0x", item.opcode)) end - if (cfg.en_cov && update_wel) begin - cov.spi_device_write_enable_disable_cg.sample(wel_val, prev_wel); + UploadCmd: begin + process_upload_cmd(item); end - end - UploadCmd: begin - process_upload_cmd(item); - end - default: `uvm_fatal(`gfn, "can't get here") - endcase + default: `uvm_fatal(`gfn, "can't get here") + endcase + end // if (cmd_type == InternalProcessStatus) // if busy, passthrough is blocked if (is_opcode_passthrough(item.opcode) && !`gmv(ral.flash_status.busy)) begin @@ -232,7 +257,6 @@ class spi_device_scoreboard extends cip_base_scoreboard #(.CFG_T (spi_device_env end end : handle_fcov - latch_flash_status(set_busy, update_wel, wel_val); end SpiModeTpm: begin bit [TPM_ADDR_WIDTH-1:0] addr = convert_addr_from_byte_queue(item.address_q); @@ -433,102 +457,6 @@ class spi_device_scoreboard extends cip_base_scoreboard #(.CFG_T (spi_device_env end endfunction : compare_tpm_hw_reg - // update flash_status to the value of the last item - virtual function void latch_flash_status(bit set_busy, bit update_wel, bit wel_val); - fork begin - bit[TL_DW-1:0] rdata; - flash_status_t pre_flash_status_val = `gmv(ral.flash_status); - flash_status_t cur_flash_status_val; - bit match; - - // it takes 3-4 cycles to update the status after spi item completes - // since we may have another thread to keep polling this csr, it's easier to predict it - // and compare with the backdoor value, to make sure both align - cfg.clk_rst_vif.wait_n_clks(FLASH_STATUS_UPDATE_DLY_AFTER_CSB_DEASSERT + 1); - flash_status_tl_pre_val_q.delete(); - - if (flash_status_settle_q.size != 0) begin - `DV_CHECK_LE(flash_status_settle_q.size, 1) - void'(ral.flash_status.predict(.value(flash_status_settle_q[0]), - .kind(UVM_PREDICT_WRITE))); - `uvm_info(`gfn, $sformatf("flash_status updated to: 0x%0h", `gmv(ral.flash_status)), - UVM_MEDIUM) - flash_status_settle_q.delete(); - end - if (set_busy) begin - void'(ral.flash_status.busy.predict(.value(1), .kind(UVM_PREDICT_READ))); - `uvm_info(`gfn, "busy bit is set due to upload command", UVM_MEDIUM) - end - - cur_flash_status_val = `gmv(ral.flash_status); - if (update_wel && wel_val != cur_flash_status_val.wel) begin - cur_flash_status_val.wel = wel_val; - void'(ral.flash_status.predict(.value(cur_flash_status_val), .kind(UVM_PREDICT_READ))); - `uvm_info(`gfn, $sformatf("update wel to %0d due to wren/wrdi command", wel_val), - UVM_MEDIUM) - end - // if sw updates this csr around the end of the spi item, it's hard to predict if the value - // is accepted or not. So do a backdoor check, it's ok if the rdata matchs to predict value - // or any value in the flash_status_q - csr_rd(.ptr(ral.flash_status), .value(rdata), .backdoor(1)); - - if (rdata == `gmv(ral.flash_status)) match = 1; - - if (!match) begin - `DV_CHECK_LE(flash_status_q.size, 1) - while (flash_status_q.size > 0) begin - flash_status_t predict_val = flash_status_q.pop_front(); - if (predict_val == rdata) begin - match = 1; - void'(ral.flash_status.predict(.value(predict_val), .kind(UVM_PREDICT_READ))); - `uvm_info(`gfn, $sformatf("found match, flash_status updated to: 0x%0h", predict_val), - UVM_MEDIUM) - break; - end - end - end - - if (!match) begin - `uvm_error(`gfn, - $sformatf("flash_status mismatch, backdoor value: 0x%0x, exp: 0x%0x", - rdata, `gmv(ral.flash_status))) - end - - // when this is just updated, TL interface may read back old value and it's ok, it will get - // the new one in the next read - if (pre_flash_status_val != `gmv(ral.flash_status)) begin - flash_status_tl_pre_val_q.push_back(pre_flash_status_val); - end - end join_none - endfunction - - // flash status can be set by SW, then it's updated when a spi transaction completes - // If a flash status write occurs during spi transaction, not sure if it will be taken or not - // latch the flash_status at the beginning of the transaction. If some write happens during - // transaction, save to flash_status_q. it's ok that HW update status to any value in both Q. - virtual task forever_latch_flash_status(); - forever begin - @(negedge cfg.spi_host_agent_cfg.vif.csb[FW_FLASH_CSB_ID]); - if (flash_status_q.size > 0) begin - flash_status_settle_q.delete(); - flash_status_settle_q.push_back(flash_status_q[$]); - flash_status_q.delete(); - end - // if this is the end of a normal flash transaction, flash_status is handled after receiving - // the item from spi_monitor. - // But if it's a dummy transaction, the rising CSB also updates flash_status, but spi_monitor - // doesn't send any item for it, so need to update flash_status here - @(posedge cfg.spi_host_agent_cfg.vif.csb[FW_FLASH_CSB_ID]); - // this small delay allows the other thread to process flash_status when it's not a dummy - // transaction. The non-dummy item thread already adds 1ps for downstream to add an item. - #2ps; - // this flash_status_settle_q isn't empty, then this is a dummy transaction - if (flash_status_q.size || flash_status_settle_q.size) begin - latch_flash_status(.set_busy(0), .update_wel(0), .wel_val(0)); - `uvm_info(`gfn, "latch flash_status due to a dummy item", UVM_MEDIUM) - end - end - endtask // this only triages post-process cmd. read buffer cmd isn't handled here as it needs to be // processed during transaction. @@ -580,33 +508,6 @@ class spi_device_scoreboard extends cip_base_scoreboard #(.CFG_T (spi_device_env return !filter && valid_cmd; endfunction - virtual function void check_internal_processed_read_status(spi_item item); - int start_addr; - bit [23:0] status = `gmv(ral.flash_status); // 3 bytes - case (item.opcode) - READ_STATUS_1: start_addr = 0; - READ_STATUS_2: start_addr = 1; - READ_STATUS_3: start_addr = 2; - default: `uvm_error(`gfn, $sformatf("unexpected status opcode: 0x%0h", item.opcode)) - endcase - // TODO(#21111): The value of the status register can change in the middle - // of SPI transactions. The SPI domain updates its values every 8 clocks. - // However, the SW / SYS domain only updates on CSB de-assertion, with the - // sampling point delayed by the CSB edge detector. In addition, the read - // commands no longer rotate through the registers. - //foreach (item.payload_q[i]) begin - // // status has 3 bytes, if read OOB, it will wrap - // int offset = (start_addr + i) % 3; - // `DV_CHECK_CASE_EQ(item.payload_q[i], status[offset * 8 +: 8], - // $sformatf("status mismatch, offset %0d, act: 0x%0h, exp: 0x%0h", - // offset, item.payload_q[i], status[offset * 8 +: 8])) - //end - - if (cfg.en_cov) begin - cov.flash_status_cg.sample(.status(status), .is_host_read(1), .sw_read_while_csb_active(0)); - end - endfunction - virtual function void check_internal_processed_read_jedec(spi_item item); bit [7:0] exp_jedec_q[$]; bit [15:0] id = `gmv(ral.jedec_id.id); @@ -864,32 +765,334 @@ class spi_device_scoreboard extends cip_base_scoreboard #(.CFG_T (spi_device_env end endtask - // read buffer cmd is handled separately as we can't wait until the item is completed. - // while upstream reads the buffer, SW may prepare the data on the other side of the buffer. - // when the item completes, the buffer may be overwritten with other data - virtual task process_read_buffer_cmd(); + // RX TXN on CSB active and each of the sampled bytes of data as well as CSB becoming inactive + virtual task process_flash_tl2spi_updates(); forever begin - spi_item item; - uint payload_idx; - bit [31:0] start_addr, offset, read_buffer_addr; - event interrupt_update_ev; - bit reading_readbuffer=1; - - upstream_spi_req_fifo.get(item); - if (cfg.spi_host_agent_cfg.spi_func_mode == SpiModeTpm) begin - bit [TPM_ADDR_WIDTH-1:0] addr = convert_addr_from_byte_queue(item.address_q); - // comparison is done when the item is transfered completedly - bit [TL_DW-1:0] ignored_returned_q[$]; - // Write commands trigger interrupts when the item is transferred - // completely. Return-by-hw reads don't trigger interrupts. - if (!item.write_command && !is_tpm_reg(addr, item.read_size, ignored_returned_q)) begin - update_pending_intr_w_delay(TpmHeaderNotEmpty); + spi_item spi_txn; + flash_status_t old_flash_status; + + upstream_csb_active_fifo.get(spi_txn); + // Received TXN is used to know when to update TL-UL and SPI flash_status values The SPI side + // updates on each 8th clock edge whereas the TL-UL side updates on CSB deassertion + `uvm_info(`gfn, "CSB is now active (low)", UVM_DEBUG) + fork + begin: isolation_thread + fork + begin + // Exit if CSB becomes inactive + wait (spi_txn.item_finished_ev.triggered); + `uvm_info(`gfn, {"[spi_txn.item_finished_ev triggered] - CSB has gone inactive", + " - killing thread"}, UVM_DEBUG) + end + begin + forever begin + // TXN.byte_sampled_ev is triggered in both TPM and FLASH modes + wait (spi_txn.byte_sampled_ev.triggered); + `uvm_info(`gfn, "Event 'spi_txn.byte_sampled' has been triggered", UVM_DEBUG) + + // task above is delay free since it's an infinite loop within a fork...join_any + // Adding some "SPI-side" clk_delay to ensure the triggering events are noticed + #(cfg.spi_host_agent_cfg.sck_period_ps/4 * 1ps); + if(tl_ul_side_flash_status_q.size > 0) begin + // 8th SCK clk and we update SPI side + `uvm_info(`gfn, + $sformatf("Updating the SPI-side. Old spi-side flash_status value: 0x%0x", + spi_side_flash_status), UVM_DEBUG) + + spi_side_flash_status = tl_ul_side_flash_status_q.pop_front(); + `uvm_info(`gfn, + $sformatf("Updating the SPI-side of flash_status to: 0x%0x", + spi_side_flash_status), UVM_DEBUG) + end + // event triggered after the spi_side is updated + -> check_spi_status_bits_ev; + end + end + join_any + disable fork; + end: isolation_thread + join + `uvm_info(`gfn, "[process_flash_tl2spi_updates] - thread killed", UVM_DEBUG) + + // All CSR predictions here: + `uvm_info(`gfn, "Updating TL-UL register side ", UVM_DEBUG) + + old_flash_status = `gmv(ral.flash_status); + `uvm_info(`gfn, + $sformatf("Updating predicted old flash_status value to: 0x%0x",old_flash_status), + UVM_DEBUG) + if(tl_ul_old_flash_status_q.size > 0) begin + bit not_in_q=1; + + foreach(tl_ul_old_flash_status_q[i]) begin + if(tl_ul_old_flash_status_q[i] == old_flash_status) begin + not_in_q = 0; + end + end + if(not_in_q) begin + // We push to the front because this is an older value: + tl_ul_old_flash_status_q.push_front(old_flash_status); + `uvm_info(`gfn, $sformatf( + "Pushing 0x%0x, on 'tl_ul_old_flash_status_q'",old_flash_status), UVM_DEBUG) end - continue; - end else if (!cfg.is_read_buffer_cmd(item)) begin - continue; end + else begin + // Queue is empty, needs to be populated + tl_ul_old_flash_status_q.push_back(old_flash_status); + `uvm_info(`gfn, $sformatf("Pushing 0x%0x, on 'tl_ul_old_flash_status_q'",old_flash_status), + UVM_DEBUG) + end + + foreach(tl_ul_old_flash_status_q[i]) begin + `uvm_info(`gfn, $sformatf( + "tl_ul_old_flash_status_q[i] = 0x%0x",tl_ul_old_flash_status_q[i]), UVM_DEBUG) + end + `uvm_info(`gfn, $sformatf("Up to know, 'tl_ul_old_flash_status_q' has %0d items", + tl_ul_old_flash_status_q.size), UVM_DEBUG) + + // tl_ul_old_flash_status_q will have up to 2 items max. That's because once CSB goes high, if + // SW reads flash_Status we may get the "older old" flash_status or the newer "old" value + // depending on when the read arrives + while(tl_ul_old_flash_status_q.size > 2) begin + `uvm_info(`gfn, $sformatf("Clearing item (0x%0x) from queue: 'tl_ul_old_flash_status_q'", + tl_ul_old_flash_status_q.pop_front()), UVM_DEBUG) + end + + // Update flash_status predicted value + if(!ral.flash_status.predict(.value(spi_side_flash_status),.kind(UVM_PREDICT_WRITE))) + `uvm_error(`gfn, "ral.flash_status.predict did not succeed") + else begin + `uvm_info(`gfn, + $sformatf("Updated the TL-UL flash_status CSR to: %p",spi_side_flash_status), UVM_DEBUG) + end + + // Triggered after CSB is asserted + -> CSB_not_active_ev; + + end // forever begin + endtask // process_flash_tl2spi_updates + + // RX a spi_txn from the monitor containing {opcode+addr} and shares it with the tasks who need it + virtual task upstream_req_fifo_distributor(); + forever begin + spi_item spi_txn; + upstream_spi_req_fifo.get(spi_txn); + `uvm_info(`gfn,$sformatf( + "RCVD partial item {OP=0x%x,addr=%p} on 'upstream_spi_req_fifo' distributing the item" + , spi_txn.opcode, spi_txn.address_q), UVM_DEBUG) + + process_read_status_cmd(spi_txn); + process_read_buffer_cmd(spi_txn); + end + endtask + + // Takes the opcode+addr and if it's a status commands check the return data is correct + virtual task process_read_status_cmd(spi_item spi_txn); + int start_addr; + bit [23:0] status = `gmv(ral.flash_status); // 3 bytes + bit [7:0] spi_read_status_bits; + flash_status_t exp_data_q[$] = {spi_side_flash_status, tl_ul_side_flash_status_q}; + int match; + flash_status_t matched_flash_status; + spi_item status_item; + bit ignore_busy_bit; + internal_process_cmd_e cmd_type; + bit is_fuzzy_q_match; + + status_item = spi_txn; + + + cmd_type = triage_flash_cmd(status_item.opcode, ignore_busy_bit); + if (cmd_type == InternalProcessStatus) begin + `uvm_info(`gfn, + $sformatf("TB received READ_STATUS command (opcode=0x%0x)",status_item.opcode), + UVM_DEBUG) + fork + begin: isolation_thread + fork + begin + // Exit if CSB becomes inactive + wait (CSB_not_active_ev.triggered); + `uvm_info(`gfn, + "[CSB_not_active_ev triggered] - CSB has gone inactive - killing thread", + UVM_DEBUG) + end + begin + forever begin + // TXN.byte_sampled_ev is triggered in both TPM and FLASH modes + wait (check_spi_status_bits_ev.triggered); + `uvm_info(`gfn, "[check_spi_status_bits_ev triggered]", UVM_DEBUG) + // Event above triggers after the spi-side value is updated + + // task above is delay free since it's an infinite loop within a fork...join_any + // Adding some "SPI-side" clk_delay to ensure the triggering events are noticed + #(cfg.spi_host_agent_cfg.sck_period_ps/4 * 1ps); + + if(status_item.payload_q.size==0) begin + // This may be the event triggered after the opcode or address + // We skip any comparison because the RTL hasn't output any status bits yet + continue; + end + + + exp_data_q = {spi_side_flash_status}; + + // (#21111): The value of the status register can change in the middle + // of SPI transactions. The SPI domain updates its values every 8 clocks. + // However, the SW / SYS domain only updates on CSB de-assertion, with the + // sampling point delayed by the CSB edge detector. In addition, the read + // commands don't rotate through the registers. + + `uvm_info(`gfn, + $sformatf("Checking SPI status read value for opcode=0x%0x", + status_item.opcode), UVM_DEBUG) + `uvm_info(`gfn, {"Flash_status CSR value per-byte: ",$sformatf( + "#0:0x%0x, #1:0x%0x, #2:0x%0x", status[7:0], status[15:8], + status[23:16])}, UVM_DEBUG) + + spi_read_status_bits = status_item.payload_q[$]; + `uvm_info(`gfn, {$sformatf("[READ_STATUS - 0x%0x] ",status_item.opcode), + $sformatf("RTL returned status_bits: 0x%0x (byte_number=%0d)", + spi_read_status_bits, status_item.payload_q.size)}, + UVM_DEBUG) + `uvm_info(`gfn, $sformatf("[READ_STATUS - 0x%0x] TB predicts status_bits: 0x%0x", + status_item.opcode, spi_side_flash_status ), UVM_DEBUG) + + case (status_item.opcode) + READ_STATUS_1: begin + if (spi_side_flash_status.wel != spi_read_status_bits[1]) begin + // The value isn't the predicted one, is the fuzzy queue empty? + is_fuzzy_q_match = spi_side_fuzzy_flash_status_q.size==0 ? 0 : + (spi_side_fuzzy_flash_status_q[0].other_status[1] == + spi_read_status_bits[1]); + if (!is_fuzzy_q_match) begin + `uvm_error(`gfn, {$sformatf("WEL mismatch: act=0x%0x, pred_fuzzy_q (%p)", + spi_read_status_bits[1], spi_side_fuzzy_flash_status_q), + $sformatf(" pred=0x%0x",spi_side_flash_status.wel)}) + end + end + if (spi_side_flash_status.busy != spi_read_status_bits[0]) begin + is_fuzzy_q_match = spi_side_fuzzy_flash_status_q.size==0 ? 0 : + (spi_side_fuzzy_flash_status_q[0].other_status[0] == + spi_read_status_bits[0]); + if (!is_fuzzy_q_match) begin + `uvm_error(`gfn, {$sformatf("BUSY mismatch: act=0x%0x, pred_fuzzy_q (%p) ", + spi_read_status_bits[0], spi_side_fuzzy_flash_status_q), + $sformatf("pred=0x%0x", spi_side_flash_status.busy)}) + end + end + if (spi_side_flash_status.other_status[5:0] != spi_read_status_bits[7:2]) begin + is_fuzzy_q_match = spi_side_fuzzy_flash_status_q.size==0 ? 0 : + (spi_side_fuzzy_flash_status_q[0].other_status[5:0] == + spi_read_status_bits[7:2]); + + if (!is_fuzzy_q_match) begin + `uvm_error(`gfn, {$sformatf("STATUS#1 other bits mismatch: {pred (0x%0x)", + spi_side_flash_status.other_status[5:0]), + $sformatf(" pred_fuzzy_q (%p) , act (0x%0x)}", + spi_side_fuzzy_flash_status_q, + spi_read_status_bits[7:2])}) + end + end + start_addr = 0; + end + READ_STATUS_2: begin + start_addr = 1; + if (spi_side_flash_status.other_status[13:6] != + spi_read_status_bits[7:0]) begin + // The value isn't the predicted one, is the fuzzy queue empty? + is_fuzzy_q_match = spi_side_fuzzy_flash_status_q.size==0 ? 0 : + (spi_side_fuzzy_flash_status_q[0].other_status[13:6] == + spi_read_status_bits[7:0]); + if (!is_fuzzy_q_match) begin + `uvm_error(`gfn, {$sformatf("STATUS#2 other bits mismatch: {pred (0x%0x), ", + spi_side_flash_status.other_status[13:6]), + $sformatf("pred_fuzzy_q (%p), act (0x%0x)}", + spi_side_fuzzy_flash_status_q,spi_read_status_bits[7:0])}) + end + end + end + READ_STATUS_3: begin + start_addr = 2; + if(spi_side_flash_status.other_status[21:14] != + spi_read_status_bits[7:0]) begin + + // The value isn't the predicted one, is the fuzzy queue empty? + is_fuzzy_q_match = spi_side_fuzzy_flash_status_q.size==0 ? 0 : + (spi_side_fuzzy_flash_status_q[0].other_status[21:14] == + spi_read_status_bits[7:0]); + if(!is_fuzzy_q_match) begin + `uvm_error(`gfn, {$sformatf("STATUS#2 other bits mismatch: {pred (0x%0x), ", + spi_side_flash_status.other_status[21:14]), + $sformatf("pred_fuzzy_q (%p), act (0x%0x)}", + spi_side_fuzzy_flash_status_q,spi_read_status_bits[7:0])}) + end + end + end + default: begin + `uvm_error(`gfn, $sformatf("unexpected status opcode: 0x%0h", + status_item.opcode)) + end + endcase + + `uvm_info(`gfn, $sformatf("Read status bits[%d:%0d] (READ_STATUS#%0d)", + (8+start_addr * 8), start_addr * 8, start_addr+1), + UVM_DEBUG) + + matched_flash_status = is_fuzzy_q_match ? spi_side_fuzzy_flash_status_q[0] : + spi_side_flash_status; + if(spi_side_fuzzy_flash_status_q.size>0 && is_fuzzy_q_match) begin + spi_side_fuzzy_flash_status_q = {}; + `uvm_info(`gfn, "'spi_side_fuzzy_flash_status_q' has been cleared", UVM_DEBUG) + end + + if (cfg.en_cov) begin + // TODO: FCOV needed to add extra insight of different states in which + // flash_status is checked (e.g, single beat SPI txn, + // multi-beat READ_STATUS command, Command with no data, ...) + cov.flash_status_cg.sample(.status(matched_flash_status), .is_host_read(1), + .sw_read_while_csb_active(0)); + end + + end + end + join_any + disable fork; + end: isolation_thread + join + + `uvm_info(`gfn, "[process_flash_tl2spi_updates] - thread killed", UVM_DEBUG) + + + end // if (cmd_type == InternalProcessStatus) + endtask + + // read buffer cmd is handled separately as we can't wait until the item is completed. + // while upstream reads the buffer, SW may prepare the data on the other side of the buffer. + // when the item completes, the buffer may be overwritten with other data + virtual task process_read_buffer_cmd(spi_item spi_txn); + spi_item item; + uint payload_idx; + bit [31:0] start_addr, offset, read_buffer_addr; + event interrupt_update_ev; + bit reading_readbuffer=1; + + item = spi_txn; + + if (cfg.spi_host_agent_cfg.spi_func_mode == SpiModeTpm) begin + bit [TPM_ADDR_WIDTH-1:0] addr = convert_addr_from_byte_queue(item.address_q); + // comparison is done when the item is transfered completedly + bit [TL_DW-1:0] ignored_returned_q[$]; + // Write commands trigger interrupts when the item is transferred + // completely. Return-by-hw reads don't trigger interrupts. + if (!item.write_command && !is_tpm_reg(addr, item.read_size, ignored_returned_q)) begin + update_pending_intr_w_delay(TpmHeaderNotEmpty); + end + end else if (!cfg.is_read_buffer_cmd(item)) begin + `uvm_info(`gfn, "Not a read buffer command!", UVM_DEBUG) + end + else begin `uvm_info(`gfn, "Process read buffer command", UVM_DEBUG) // Use fork...join_none and lock until we return from 'compare_mem_byte' below. @@ -919,40 +1122,45 @@ class spi_device_scoreboard extends cip_base_scoreboard #(.CFG_T (spi_device_env start_addr = convert_addr_from_byte_queue(item.address_q); `DV_SPINWAIT( - while (1) begin - wait (item.payload_q.size > payload_idx || item.mon_item_complete); - if (item.payload_q.size > payload_idx) begin - offset = (start_addr + payload_idx) % READ_BUFFER_SIZE; - compare_mem_byte(READ_BUFFER_START_ADDR, offset, item.payload_q[payload_idx], - payload_idx, "Read buffer"); - read_buffer_addr = start_addr + payload_idx; // it's kept until reset - payload_idx++; // clear to 0 when transaction is done - -> interrupt_update_ev; - `uvm_info(`gfn, - $sformatf("Triggering 'interrupt_update_ev' event (buffer_addr=0x%0x)", - read_buffer_addr + 1 ), UVM_DEBUG) - - `DV_CHECK_EQ(item.payload_q.size, payload_idx) - end - if (item.mon_item_complete) begin - `uvm_info(`gfn, - "item.mon_item_complete=1 -> disabling all threads under DV_SPINWAIT", UVM_DEBUG) - break; - end - end - ) - //Exiting while loop after we finish reading the buffer and triggering the event + while (1) begin + wait (item.payload_q.size > payload_idx || item.mon_item_complete); + if (item.payload_q.size > payload_idx) begin + offset = (start_addr + payload_idx) % READ_BUFFER_SIZE; + compare_mem_byte(READ_BUFFER_START_ADDR, offset, item.payload_q[payload_idx], + payload_idx, "Read buffer"); + read_buffer_addr = start_addr + payload_idx; // it's kept until reset + payload_idx++; // clear to 0 when transaction is done + -> interrupt_update_ev; + `uvm_info(`gfn, + $sformatf("Triggering 'interrupt_update_ev' event (buffer_addr=0x%0x)", + read_buffer_addr + 1 ), UVM_DEBUG) + + `DV_CHECK_EQ(item.payload_q.size, payload_idx) + end + if (item.mon_item_complete) begin + `uvm_info(`gfn, + "item.mon_item_complete=1 -> disabling all threads under DV_SPINWAIT", + UVM_DEBUG) + break; + end + end, // while (1) + , // Default message + default_spinwait_timeout_ns*2, // Increase the default timeout by 2 since the + // payload_size can be up to 3000 bytes based + // on sequence constraints + ) + // Exiting while loop after we finish reading the buffer and triggering the event // in case the process is waiting - so we unlock it reading_readbuffer = 0; -> interrupt_update_ev; - // only update when it has payload if (payload_idx > 0) begin `uvm_info(`gfn, $sformatf("Update last_read_addr to 0x%0x", read_buffer_addr), UVM_MEDIUM) void'(ral.last_read_addr.predict(.value(read_buffer_addr), .kind(UVM_PREDICT_READ))); end - end // forever begin - endtask + end + endtask // process_read_buffer_cmd + virtual function void predict_read_buffer_intr(int addr, bit [7:0] opcode); int threshold = `gmv(ral.read_threshold); @@ -1026,6 +1234,14 @@ class spi_device_scoreboard extends cip_base_scoreboard #(.CFG_T (spi_device_env `uvm_fatal(`gfn, $sformatf("Access unexpected addr 0x%0h", csr_addr)) end + `uvm_info(`gfn,$sformatf( + "TL-UL txn: [channel=%p] - {addr=0x%0x, a_size=d'%0d, a_mask=0x%0x, is_write=d'%0d, data=0x%0x", + channel,item.a_addr, item.a_size, item.a_mask, write, + write ? item.a_data : item.d_data), + UVM_DEBUG) + + `uvm_info(`gfn, $sformatf("CSR name: %s",csr.get_name()), UVM_DEBUG) + // if incoming access is a write to a valid csr, then make updates right away // don't update flash_status predict value as it's updated at the end of spi transaction if (write && channel == AddrChannel && csr.get_name != "flash_status") begin @@ -1057,19 +1273,62 @@ class spi_device_scoreboard extends cip_base_scoreboard #(.CFG_T (spi_device_env case (csr.get_name()) "flash_status": begin if (write && channel == AddrChannel) begin - // store the item in a queue as flash_status is updated at the end of spi transaction + flash_status_t flash_status = item.a_data; // busy field is W0C. Setting to 1 has no effect flash_status.busy = flash_status.busy & `gmv(ral.flash_status.busy); - flash_status_q.push_back(flash_status); + flash_status.wel = flash_status.wel & `gmv(ral.flash_status.wel); + + `uvm_info(`gfn, $sformatf("SW Write to flash_status (0x%0x)", flash_status), UVM_DEBUG) + + //'tl_ul_side_flash_status_q' is picked inmediatly on every SPI byte beat. + // Hence, we need to only push the items to the queue at the "right time". + populate_tl_ul_side_flash_status_q(flash_status); + end if (!write && channel == DataChannel) begin - // it's ok to read back old value once. - flash_status_t exp_val = `gmv(ral.flash_status); - flash_status_t exp_data_q[$] = {flash_status_tl_pre_val_q, exp_val}; + flash_status_t exp_val = `gmv(ral.flash_status); // predicted value updated when CSB (0->1) + flash_status_t exp_data_q[$] = {tl_ul_old_flash_status_q, exp_val, + tl_ul_fuzzy_flash_status_q}; + `uvm_info(`gfn, $sformatf( + "SW Read to flash_status (0x%0x) (exp_data_q.size=%0d) - exp_data_q = %p", + item.d_data, exp_data_q.size, exp_data_q), UVM_DEBUG) + + // matching against predicted values? `DV_CHECK(item.d_data inside {exp_data_q}, $sformatf("act (0x%0x) != exp %p", item.d_data, exp_data_q)) - flash_status_tl_pre_val_q.delete(); + + // After matching we update the old value with the matched one: + if(tl_ul_fuzzy_flash_status_q.size > 0) begin + if(tl_ul_fuzzy_flash_status_q[0] == item.d_data) begin + string str; + // The value reported matches the fuzzy one, so we move the fuzzy value toward the + // "old" value and clear the fuzzy queue: + tl_ul_old_flash_status_q = {tl_ul_fuzzy_flash_status_q[0]}; + // Clearing queue after using it's fuzzy contents + tl_ul_fuzzy_flash_status_q = {}; + // Splitting due to line-length lint rule: + str = "Cleared 'tl_ul_fuzzy_flash_status_q': it matched the"; + str = {str, " ", "read value. Fuzzy value moved to'tl_ul_old_flash_status_q'"}; + `uvm_info(`gfn,str , UVM_DEBUG) + end + end + + if(tl_ul_old_flash_status_q.size==2) begin + if(tl_ul_old_flash_status_q[1] == item.d_data) begin + void'(tl_ul_old_flash_status_q.pop_front()); + `uvm_info(`gfn, {$sformatf("Newest item of'tl_ul_old_flash_status_q (size=%0d)'", + tl_ul_old_flash_status_q.size), + " read by SW. Cleared oldest item from the queue"}, UVM_DEBUG) + end + end + + if(tl_ul_old_flash_status_q.size > 2) begin + `uvm_fatal(`gfn, $sformatf( + "'tl_ul_old_flash_status_q.size=%0d' - the max allowedvalue is 2", + tl_ul_old_flash_status_q.size)) + end + if (cfg.en_cov) begin cov.flash_status_cg.sample(.status(item.d_data), .is_host_read(0), .sw_read_while_csb_active(!cfg.spi_host_agent_cfg.vif.csb[FW_FLASH_CSB_ID])); @@ -1110,7 +1369,7 @@ class spi_device_scoreboard extends cip_base_scoreboard #(.CFG_T (spi_device_env intr_trigger_pending[i] = 0; end end // foreach (intr_exp[i]) - //Update local mirror copy of intr_exp + // Update local mirror copy of intr_exp intr_exp_read_mirrored = `gmv(csr); // skip updating predict value to d_data @@ -1125,8 +1384,8 @@ class spi_device_scoreboard extends cip_base_scoreboard #(.CFG_T (spi_device_env if (intr_val[i]) begin `uvm_info(`gfn, $sformatf("Clear %s", intr.name), UVM_MEDIUM) - //If intr_trigger_pending[i] is set and intr_exp_read_mirrored[i] is also set, that - //means the TB has predicted another interrupt since last time intr_state was read. + // If intr_trigger_pending[i] is set and intr_exp_read_mirrored[i] is also set, that + // means the TB has predicted another interrupt since last time intr_state was read. // But in the meantime, there's not been any intr_state.writes clearing the intr_state // register. if(intr_trigger_pending[i] && intr_exp_read_mirrored[i]) begin @@ -1239,7 +1498,105 @@ class spi_device_scoreboard extends cip_base_scoreboard #(.CFG_T (spi_device_env end void'(csr.predict(.value(item.d_data), .kind(UVM_PREDICT_READ))); end - endtask + endtask // process_tl_access + + + virtual task populate_tl_ul_side_flash_status_q(flash_status_t flash_status); + bit csb_active = cfg.spi_host_agent_cfg.vif.csb[FW_FLASH_CSB_ID]===0; + + `uvm_info(`gfn, {$sformatf("Flash_status write (0x%0x) - Populating 'tl_ul_side_flash_status_q'" + ,flash_status), + $sformatf(". Up to know it has %0d entries", tl_ul_side_flash_status_q.size)}, + UVM_DEBUG) + + // There are 2 directions: + // - TL-UL to SPI -> update during ongoing SPI_TXN (byte beats -> check_spi_status_bits_ev) + // - SPI to TL-UL -> update the moment CSB goes is high (plus some CDC crossing delay) + // Thread is killed either after 2 SPI bytes, CSB going high, or CSB going low + fork begin + begin + fork + begin + // If there is a READ_STATUS command on flight whils flash_status is being written, We + // need to wait for 2 spi byte beats. + // For the first beat, the RTL moves the TL-UL flash_status towards the spi-side,but the + // returned value may have been already committed to the upstream spi host and after the + // second beat is when the RTL commits this written value to the return it to the host. + `uvm_info(`gfn,$sformatf("Populating fuzzy Qs with flash_status = 0x%0x",flash_status), + UVM_DEBUG) + + if(tl_ul_fuzzy_flash_status_q.size > 0) begin + tl_ul_old_flash_status_q.push_back(tl_ul_fuzzy_flash_status_q[0]); + `uvm_info(`gfn, {$sformatf("Pushing old 'tl_ul_fuzzy_flash_status_q[0]=0x%0x' item", + tl_ul_fuzzy_flash_status_q[0]), + $sformatf(" onto 'tl_ul_old_flash_status_q.size = %0d'", + tl_ul_old_flash_status_q.size)}, UVM_DEBUG) + + + if(tl_ul_old_flash_status_q.size > 2) begin + while(tl_ul_old_flash_status_q.size > 2) begin + `uvm_info(`gfn, {$sformatf("'tl_ul_old_flash_status_q.size = %0d, Popping ", + tl_ul_old_flash_status_q.size), + $sformatf("the oldest entry (0x%0x)", + tl_ul_old_flash_status_q.pop_front())}, UVM_DEBUG) + end + end + end + + // Keep written value in Fuzzy Qs since the TB won't accuretly model the CDC crossing + tl_ul_fuzzy_flash_status_q = {flash_status}; + spi_side_fuzzy_flash_status_q = {flash_status}; + + wait (check_spi_status_bits_ev.triggered); + + // Quarter of a SPI cycle to ensure a second event is triggered for the 2nd wait + // statement + #(cfg.spi_host_agent_cfg.sck_period_ps/4 * 1ps); + wait (check_spi_status_bits_ev.triggered); + end + begin + wait (CSB_not_active_ev.triggered); + if (csb_active) begin + // It can happen the SPI command is less than 2-bytes, in which case we need to + // update 'spi_side_flash_status' here + + `uvm_info(`gfn, + "Updating 'spi_side_flash_status' in different task due to CMD too short", + UVM_DEBUG) + spi_side_flash_status = flash_status; + // Update flash_status predicted value + if (!ral.flash_status.predict(.value(spi_side_flash_status),.kind(UVM_PREDICT_WRITE))) + `uvm_error(`gfn, "ral.flash_status.predict did not succeed") + else begin + `uvm_info(`gfn, {"Updated TL-UL flash_status (2nd time) due to short command (2B)" + , $sformatf(" CSR to: %p",spi_side_flash_status)}, UVM_DEBUG) + end + + end + end + begin + // Terminating thread here if CSB becomes active again + @(negedge cfg.spi_host_agent_cfg.vif.csb[FW_FLASH_CSB_ID]); + + end + join_any + disable fork; + + if (!csb_active || // Either CSB was inactive at the time + cfg.spi_host_agent_cfg.vif.csb[FW_FLASH_CSB_ID]===0 // Or CSB is active now + ) begin + // store the item in a queue as. TL-UL side updates on SPI-side on each 8th SCK cycle + tl_ul_side_flash_status_q.push_back(flash_status); + `uvm_info(`gfn, $sformatf( + "SW Write to flash_status (0x%0x) (tl_ul_side_flash_status_q.size=%0d)", + flash_status, tl_ul_side_flash_status_q.size), UVM_DEBUG) + end + + end + end join_none + + endtask // populate_tl_ul_side_flash_status_q + // send out SPI data from tx_fifo and fetch more data from sram to fifo virtual function void sendout_spi_tx_data(bit [31:0] data_act); @@ -1312,9 +1669,6 @@ class spi_device_scoreboard extends cip_base_scoreboard #(.CFG_T (spi_device_env tx_word_q.delete(); rx_word_q.delete(); spi_passthrough_downstream_q.delete(); - flash_status_q.delete(); - flash_status_settle_q.delete(); - flash_status_tl_pre_val_q.delete(); tpm_read_sw_q.delete(); tpm_hw_reg_pre_val_aa.delete(); @@ -1324,6 +1678,8 @@ class spi_device_scoreboard extends cip_base_scoreboard #(.CFG_T (spi_device_env read_buffer_watermark_triggered = 0; + spi_side_flash_status = `gmv(ral.flash_status); + clear_mems(); endfunction @@ -1338,9 +1694,6 @@ class spi_device_scoreboard extends cip_base_scoreboard #(.CFG_T (spi_device_env `DV_CHECK_EQ(spi_passthrough_downstream_q.size, 0) `DV_CHECK_EQ(upload_cmd_q.size, 0) `DV_CHECK_EQ(upload_addr_q.size, 0) - `DV_CHECK_EQ(flash_status_q.size, 0) - `DV_CHECK_EQ(flash_status_settle_q.size, 0) - `DV_CHECK_EQ(flash_status_tl_pre_val_q.size, 0) `DV_CHECK_EQ(tpm_read_sw_q.size, 0) endfunction endclass