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
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) |