UVM/UVM Cookbook/Scoreboards
Материал из Wiki
- 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
In-order компаратор предполагает, что сравниваемые транзакции будут появляться в одном и том же порядке и с ожидаемого и фактического потоков. Компоратор получает транзакции с ожидаемой и фактической стороны и оценивает их. Транзакции прибудут независимо, поэтому оценка должна блокировать, пока обе транзакции не появятся. В этом случае простой реализацией было бы встроить две analysis FIFOs в компаратор и выполнять синхронизацию и оценку в task run(). Оценка может быть простым вызовом метода compare() транзакции, или может быть более сложной, потому что для оценки правильного поведения, сравнение не обязательно означает равенство.
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
Out-of-order компаратор не делает предположение, что сравниваемые транзакции будут появляться в том же порядке и от ожидаемой и от фактической сторон. Так, несравненные транзакции должны быть сохранены пока подходящая транзакция не появится на другом потоке. Для большинства Out-of-order компараторов для хранения используется ассоциативный массив. В примере компаратор имеет два входных потока, поступающих с analysis exports (порты). Реализация компаратора является симметричной, поэтому имена export`ов не имеют никакого реального значения. Этот пример использует встроенные буферы FIFO для реализации функции анализа write(), но так как транзакции либо хранятся в ассоциативном массиве или оцениваются при поступлении, этот пример может быть легко написан с использованием analysis imps и функций write().
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.
В связи с необходимостью определения, если две транзакции матч и следует сравнивать, этот пример требует операции для реализации index_id функцию (), которая возвращает значение, которое используется в качестве ключа для ассоциативного массива. Если запись с этим ключом уже существует в ассоциативный массив, то это означает, что транзакция ранее приехал из другой поток, и сделки по сравнению. Если ни одна кнопка не существует, то эта сделка будет добавлен в ассоциативный массив.
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.
Этот пример имеет дополнительную особенность в том, что он не считает, что index_id () значения всегда был уникальным для данного потока. В случае, когда несколько нерешенных транзакции из того же самого потока имеют одинаковое значение индекса, они хранятся в очереди, и очередь это значение, часть ассоциативного массива. Когда результаты от другой поток прибыли, они сравниваются в порядке поступления.
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) |