«Бог не меняет того, что (происходит) с людьми, пока они сами не изменят своих помыслов.» Коран, Сура 12:13

UVM/UVM Cookbook/Scoreboards

Материал из Wiki

< UVM
Перейти к: навигация, поиск

Содержание

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

InOrderComparator.png InOrderComparator.png

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

OutOfOrderScoreboard.png OutOfOrderScoreboard.png

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)