UVM/UVM Cookbook/Scoreboards
- Sequences
- Analysis Components & Techniques
- Scoreboards (link)
- UVM Tutorial for Candy Lovers
Overview
The Scoreboard's job is to determine whether or not the DUT is functioning properly. The scoreboard is usually the most difficult part of the testbench to write, but it can be generalized into two parts: The first step is determining what exactly is the correct functionality. Once the correct functionality is predicted, the scoreboard can then evaluate whether or not the actual results observed on the DUT match the predicted results. The best scoreboard architecture is to separate the prediction task from the evaluation task. This gives you the most flexibility for reuse by allowing for substitution of predictor and evaluation models, and follows the best practice of separation of concerns.
In cases where there is a single stream of predicted transactions and a single stream of actual transactions, the scoreboard can perform the evaluation with a simple comparator. The most common comparators are an in-order and out-of-order comparator.
Comparing Transactions Assuming In-Order Arrival
An in-order comparator assumes that matching transactions will appear in the same order from both expected and actual streams. It gets transactions from the expected and actual side and evaluates them. The transactions will arrive independently, so the evaluation must block until both transactions are present. In this case, an easy implementation would be to embed two analysis fifos in the comparator and perform the synchronization and evaluation in the run() task. Evaluation can be as simple as calling the transaction's compare() method, or it can be more involved, because for the purposes of evaluating correct behavior, comparison does not necessarily mean equality.
class comparator_inorder extends uvm_component; `uvm_component_utils(comparator_inorder) uvm_analysis_export #(alu_txn) before_export; uvm_analysis_export #(alu_txn) after_export; uvm_tlm_analysis_fifo #(alu_txn) before_fifo, after_fifo; int m_matches, m_mismatches; function new( string name , uvm_component parent) ; super.new( name , parent ); m_matches = 0; m_mismatches = 0; endfunction function void build_phase( uvm_phase phase ); before_fifo = new("before_fifo", this); after_fifo = new("after_fifo", this); before_export = new("before_export", this); after_export = new("after_export", this); endfunction function void connect_phase( uvm_phase phase ); before_export.connect(before_fifo.analysis_export); after_export.connect(after_fifo.analysis_export); endfunction task run_phase( uvm_phase phase ); string s; alu_txn before_txn, after_txn; forever begin before_fifo.get(before_txn); after_fifo.get(after_txn); if (!before_txn.compare(after_txn)) begin $sformat(s, "%s does not match %s", before_txn.convert2string(), after_txn.convert2string()); uvm_report_error("Comparator Mismatch",s); m_mismatches++; end else begin m_matches++; end end endtask function void report_phase( uvm_phase phase ); uvm_report_info("Inorder Comparator", $sformatf("Matches: %0d", m_matches)); uvm_report_info("Inorder Comparator", $sformatf("Mismatches: %0d", m_mismatches)); endfunction endclass |
Comparing transactions out-of-order
An out-of-order comparator makes no assumption that matching transactions will appear in the same order from the expected and actual sides. So, unmatched transactions need to be stored until a matching transaction appears on the opposite stream. For most out-of-order comparators, an associative array is used for storage. This example comparator has two input streams arriving through analysis exports. The implementation of the comparator is symmetrical, so the export names do not have any real importance. This example uses embedded fifos to implement the analysis write() functions, but since the transactions are either stored into the associative array or evaluated upon arrival, this example could easily be written using analysis imps and write() functions.
Because of the need to determine if two transactions are a match and should be compared, this example requires transactions to implement an index_id() function that returns a value that is used as a key for the associative array. If an entry with this key already exists in the associative array, it means that a transaction previously arrived from the other stream, and the transactions are compared. If no key exists, then this transaction is added to associative array.
This example has an additional feature in that it does not assume that the index_id() values are always unique on a given stream. In the case where multiple outstanding transactions from the same stream have the same index value, they are stored in a queue, and the queue is the value portion of the associative array. When matches from the other stream arrive, they are compared in FIFO order.
class ooo_comparator #(type T = int, type IDX = int) extends uvm_component; typedef ooo_comparator #(T, IDX) this_type; `uvm_component_param_utils(this_type) typedef T q_of_T[$]; typedef IDX q_of_IDX[$]; uvm_analysis_export #(T) before_axp, after_axp; protected uvm_tlm_analysis_fifo #(T) before_fifo, after_fifo; bit before_queued = 0; bit after_queued = 0; protected int m_matches, m_mismatches; protected q_of_T received_data[IDX]; protected int rcv_count[IDX]; protected process before_proc = null; protected process after_proc = null; function new(string name, uvm_component parent); super.new(name, parent); endfunction function void build_phase( uvm_phase phase ); before_axp = new("before_axp", this); after_axp = new("after_axp", this); before_fifo = new("before", this); after_fifo = new("after", this); endfunction function void connect_phase( uvm_phase phase ); before_axp.connect(before_fifo.analysis_export); after_axp.connect(after_fifo.analysis_export); endfunction : connect // The component forks two concurrent instantiations of this task // Each instantiation monitors an input analysis fifo protected task get_data(ref uvm_tlm_analysis_fifo #(T) txn_fifo, ref process proc, input bit is_before); T txn_data, txn_existing; IDX idx; string rs; q_of_T tmpq; bit need_to_compare; forever begin proc = process::self(); // Get the transaction object, block if no transaction available txn_fifo.get(txn_data); idx = txn_data.index_id(); // Check to see if there is an existing object to compare need_to_compare = (rcv_count.exists(idx) && ((is_before && rcv_count[idx] > 0) || (!is_before && rcv_count[idx] < 0))); if (need_to_compare) begin // Compare objects using compare() method of transaction tmpq = received_data[idx]; txn_existing = tmpq.pop_front(); received_data[idx] = tmpq; if (txn_data.compare(txn_existing)) m_matches++; else m_mismatches++; end else begin // If no compare happened, add the new entry if (received_data.exists(idx)) tmpq = received_data[idx]; else tmpq = {}; tmpq.push_back(txn_data); received_data[idx] = tmpq; end // Update the index count if (is_before) if (rcv_count.exists(idx)) begin rcv_count[idx]--; end else rcv_count[idx] = -1; else if (rcv_count.exists(idx)) begin rcv_count[idx]++; end else rcv_count[idx] = 1; // If index count is balanced, remove entry from the arrays if (rcv_count[idx] == 0) begin received_data.delete(idx); rcv_count.delete(idx); end end // forever endtask virtual function int get_matches(); return m_matches; endfunction : get_matches virtual function int get_mismatches(); return m_mismatches; endfunction : get_mismatches virtual function int get_total_missing(); int num_missing; foreach (rcv_count[i]) begin num_missing += (rcv_count[i] < 0 ? -rcv_count[i] : rcv_count[i]); end return num_missing; endfunction : get_total_missing virtual function q_of_IDX get_missing_indexes(); q_of_IDX rv = rcv_count.find_index() with (item != 0); return rv; endfunction : get_missing_indexes; virtual function int get_missing_index_count(IDX i); // If count is < 0, more "before" txns were received // If count is > 0, more "after" txns were received if (rcv_count.exists(i)) return rcv_count[i]; else return 0; endfunction : get_missing_index_count; task run_phase( uvm_phase phase ); fork get_data(before_fifo, before_proc, 1); get_data(after_fifo, after_proc, 0); join endtask : run_phase virtual function void kill(); before_proc.kill(); after_proc.kill(); endfunction endclass : ooo_comparator |
Advanced Scenarios
In more advanced scenarios, there can be multiple predicted and actual transaction streams coming from multiple DUT interfaces. In this case, a simple comparator is insufficient and the implementation of the evaluation portion of the scoreboard is more complex and DUT-specific.
Reporting and Recording
The result of the evaluation is a boolean value, which the Scoreboard should use to report and record failures. Usually successful evaluations are not individually reported, but can be recorded for later summary reports.
Download-tarball.png | Download a complete working example: (tarball: Alu_config_Analysis.tgz) |