Uçtan Uca ALU UVM Testbench'i: Scoreboard, Coverage ve Test Varyantları

UVM Gün 7: Bitirme Projesi | Predictor (referans model) + uvm_tlm_analysis_fifo'lu scoreboard, check_phase / report_phase, uvm_subscriber ile coverage, sequence seçen test hiyerarşisi, +UVM_TESTNAME ile regresyon, bilinçli hata enjeksiyonu

Amaç

Gün 6'da DUT'a bağlanan iskeleti bu derste kendi kendini denetleyen (self-checking) bir ortama dönüştüreceğiz: monitor'ün gözlediği her işlem bir referans modelden geçecek, scoreboard beklenen ile gözleneni eşleştirecek, coverage "yeterince denedik mi?" sorusunu ölçecek ve testler yalnızca senaryo (sequence) seçerek çoğalacak. Dersin sonunda +UVM_TESTNAME=alu_corner_test ile koşan, *** TEST PASSED *** basan ve DUT'a enjekte edilen bir hatayı yakalayan tam bir UVM testbench'iniz olacak.

Bu, SystemVerilog Gün 7 bitirme projesinin (Dersler 41–46) UVM karşılığıdır; her bileşen için oradaki dersi referans vereceğiz.

Ön Koşullar

  • UVM Gün 3 (analysis port/imp, uvm_subscriber, TLM port türleri tablosu), Gün 5 (factory, test varyantları), Gün 6 (alu_transaction, alu_driver, alu_monitor, alu_agent, tb_top).
  • SystemVerilog Ders 42 (referans model ve bayrak sözleşmesi), Ders 44 (test hiyerarşisi), Ders 45 (covergroup, cross, ignore_bins).

Mimari: Gözlemden Karara

                       agent.ap  (alu_monitor her out_valid'de bir alu_transaction yayinlar)
                           │
          ┌────────────────┼──────────────────────┐
          ▼                ▼                       ▼
   alu_predictor      alu_scoreboard          alu_coverage
   (referans model)   act_export ─▶ act_fifo  (uvm_subscriber, covergroup)
   expected_ap ──────▶ exp_export ─▶ exp_fifo
                       run_phase: exp_fifo.get / act_fifo.get / compare
                       check_phase: kuyruklar bos mu?   report_phase: ozet

SystemVerilog projesinde scoreboard "beklenen" veriyi driver'ın kopyasından alıyordu. Burada her şey monitor'den gelir: predictor, gözlenen girişlerden beklenen çıkışı hesaplar; scoreboard iki akışı sırayla eşleştirir. Driver'a hiç güvenilmez; doğrulama tamamen pinlerde gözlenen gerçeklere dayanır (Ders 42'deki "predictor" satırı).

Lab 7.1: Predictor — Referans Model Ayrı Bir Bileşen

Görev: Ders 42'deki compute_reference()'ı bir uvm_subscriber içine taşıyın; beklenen işlemi ayrı bir analysis port'tan yayınlasın.

class alu_predictor extends uvm_subscriber #(alu_transaction);
  `uvm_component_utils(alu_predictor)

  uvm_analysis_port #(alu_transaction) expected_ap;

  function new(string name = "alu_predictor", uvm_component parent = null);
    super.new(name, parent);
  endfunction

  virtual function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    expected_ap = new("expected_ap", this);
  endfunction

  // uvm_subscriber: analysis_export'a gelen her gozlem icin write() cagrilir
  virtual function void write(alu_transaction t);
    alu_transaction exp;
    if (!$cast(exp, t.clone()))            // monitor'un nesnesine dokunma: once kopyala
      `uvm_fatal("PRED", "clone() basarisiz")
    predict(exp);                          // result/flags'i SPESIFIKASYONA gore doldur
    expected_ap.write(exp);
  endfunction

  // Ders 42: compute_reference() — DUT koduna bakmadan, sozlesmeden yazilir
  // Bayraklar (Ders 36-1): [0]=Z  [1]=N  [2]=C (yalnizca ADD/SUB)  [3]=P
  function void predict(alu_transaction tr);
    case (tr.opcode)
      OP_ADD:  tr.result = {8'h00, tr.a} + {8'h00, tr.b};   // bit 8 = carry
      OP_SUB:  tr.result = {8'h00, tr.a} - {8'h00, tr.b};   // a<b ise 16'hFFxx (borrow)
      OP_AND:  tr.result = {8'h00, tr.a & tr.b};
      OP_OR:   tr.result = {8'h00, tr.a | tr.b};
      OP_XOR:  tr.result = {8'h00, tr.a ^ tr.b};
      OP_NOT:  tr.result = {8'h00, ~tr.a};
      OP_SHL:  tr.result = {8'h00, tr.a} << tr.b[2:0];
      OP_SHR:  tr.result = {8'h00, tr.a} >> tr.b[2:0];
      default: tr.result = 16'h0000;
    endcase
    tr.flags    = 4'b0000;
    tr.flags[0] = (tr.result == 16'h0000);                                   // Z
    tr.flags[1] = tr.result[15];                                             // N
    tr.flags[2] = (tr.opcode inside {OP_ADD, OP_SUB}) ? tr.result[8] : 1'b0; // C
    tr.flags[3] = ^tr.result;                                                // P
  endfunction
endclass

Kodun Açıklaması

  • Neden ayrı bileşen? Scoreboard "iki akışı eşleştir ve karşılaştır" işini yapar; bu iş protokolden bağımsızdır. "Beklenen değer nedir?" sorusu ise tamamen ALU'ya özgüdür. İkisini ayırmak, scoreboard'u başka projede değiştirmeden kullanmanızı, predictor'ı ise tek başına test etmenizi sağlar. Gerçek bir pipeline'da predictor giriş monitor'ünden, scoreboard'un act tarafı çıkış monitor'ünden beslenir; bu ALU'da tek monitor hem girişi hem çıkışı aynı anda yakaladığı için ikisi aynı porttan gelir.
  • t.clone() + $cast: write()'a gelen t, monitor'ün yayınladığı nesnedir ve aynı anda scoreboard ile coverage'a da gider. Üzerine result yazarsanız scoreboard'un "gözlenen" değerini de değiştirmiş olursunuz. clone() factory üzerinden yeni nesne yaratıp copy() yapar (field makroları sayesinde elle do_copy yazmadan); dönüş tipi uvm_object olduğu için $cast gerekir.
  • predict() bir function: Zaman tüketmez; write() zinciri içinde çağrılabilir. Ders 42'deki uyarı aynen geçerli: model, DUT'un always_comb bloğunu kopyalayarak değil, spesifikasyondan yazılır.

Lab 7.2: Scoreboard — İki Akış, Bir Karar

Görev: Gün 3'teki "Kendinizi Deneyin #4" fikrini uygulayın: write() içinde hemen karşılaştırmak yerine iki uvm_tlm_analysis_fifo kullanın ve eşleştirmeyi run_phase'de yapın.

class alu_scoreboard extends uvm_scoreboard;
  `uvm_component_utils(alu_scoreboard)

  // Dis yuz: env yalnizca bu iki export'u bilir
  uvm_analysis_export #(alu_transaction) exp_export;
  uvm_analysis_export #(alu_transaction) act_export;
  // Ic yuz: write() cagrilarini zaman tuketmeyen bir kuyruga cevirir
  uvm_tlm_analysis_fifo #(alu_transaction) exp_fifo;
  uvm_tlm_analysis_fifo #(alu_transaction) act_fifo;

  int unsigned n_match = 0, n_mismatch = 0;

  function new(string name = "alu_scoreboard", uvm_component parent = null);
    super.new(name, parent);
  endfunction

  virtual function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    exp_export = new("exp_export", this);
    act_export = new("act_export", this);
    exp_fifo   = new("exp_fifo",   this);
    act_fifo   = new("act_fifo",   this);
  endfunction

  virtual function void connect_phase(uvm_phase phase);
    super.connect_phase(phase);
    exp_export.connect(exp_fifo.analysis_export);   // export -> export: asagi dogru
    act_export.connect(act_fifo.analysis_export);
  endfunction

  virtual task run_phase(uvm_phase phase);
    alu_transaction exp, act;
    forever begin
      exp_fifo.get(exp);          // beklenen gelene kadar bloklar
      act_fifo.get(act);          // gozlenen gelene kadar bloklar
      compare(exp, act);
    end
  endtask

  function void compare(alu_transaction exp, alu_transaction act);
    if (act.result === exp.result && act.flags === exp.flags) begin
      n_match++;
      `uvm_info("SB", {"PASS ", act.convert2string()}, UVM_HIGH)
    end else begin
      n_mismatch++;
      `uvm_error("SB", $sformatf("MISMATCH #%0d\n  beklenen: %s\n  gozlenen: %s",
                                 n_mismatch, exp.convert2string(), act.convert2string()))
    end
  endfunction

  // run_phase bitince kuyruklarda eslesmemis islem kalmamali
  virtual function void check_phase(uvm_phase phase);
    super.check_phase(phase);
    if (exp_fifo.used() != 0 || act_fifo.used() != 0)
      `uvm_error("SB", $sformatf("Eslesmemis islem kaldi: exp=%0d act=%0d",
                                 exp_fifo.used(), act_fifo.used()))
    if (n_match + n_mismatch == 0)
      `uvm_error("SB", "Hic islem karsilastirilmadi: monitor veya baglantilari kontrol edin")
  endfunction

  virtual function void report_phase(uvm_phase phase);
    super.report_phase(phase);
    `uvm_info("SB", $sformatf("\n  ===== SCOREBOARD =====\n  Toplam    : %0d\n  Basarili  : %0d\n  Basarisiz : %0d\n  ======================",
                              n_match + n_mismatch, n_match, n_mismatch), UVM_NONE)
  endfunction
endclass

Kodun Açıklaması

  • Export → FIFO: uvm_tlm_analysis_fifo içinde hazır bir analysis_export taşır; gelen write() çağrısı veriyi kuyruğa koyar ve hemen döner. Scoreboard dışarıya kendi exp_export/act_export'unu sunar ve bunları FIFO'ların export'larına bağlar. Env, scoreboard'un içinde FIFO olduğunu bilmek zorunda değildir; yarın kuyruk tabanlı (out-of-order) bir eşleştirmeye geçerseniz env değişmez.
  • run_phase'de get: write() bir function'dır, bloklayamaz; iki akışın "ikisi de geldiğinde karşılaştır" mantığı ancak bloklayan get ile doğal yazılır. SystemVerilog projesindeki drv2scb.get(expected); mon2scb.get(actual); kalıbının (Ders 42) birebir UVM karşılığı budur.
  • === ile karşılaştırma: result/flags logic olduğu için X yakalanır. exp.compare(act) (field makrolarının ürettiği karşılaştırma) burada kullanılmadı; çünkü girişleri de karşılaştırır ve girişler her iki nesnede zaten aynıdır; anlamlı olan yalnızca çıkışlardır. Alternatifler FAQ'da.
  • check_phase: Objection erken düşerse (boşaltma süresi yoksa) exp_fifo'da bir öğe kalır; bu kontrol "son işlem hiç karşılaştırılmadı" durumunu görünür kılar. İkinci kontrol, bağlantı unutulduğunda testin sessizce geçmesini engeller: hiç karşılaştırma yapılmamış bir test geçmiş sayılmaz.
  • report_phase'de özet, UVM_NONE ile: Verbosity ne olursa olsun basılır; regresyon betikleri bu bloğu okuyabilir.

Lab 7.3: Coverage — uvm_subscriber ile Kapsam

Görev: Ders 45'teki covergroup'u sadeleştirerek bir subscriber'a taşıyın.

class alu_coverage extends uvm_subscriber #(alu_transaction);
  `uvm_component_utils(alu_coverage)

  covergroup cg with function sample(opcode_e op, bit [7:0] a, bit [7:0] b, logic [3:0] flags);
    option.per_instance = 1;
    cp_op: coverpoint op;                                   // enum: 8 otomatik bin
    cp_a: coverpoint a {
      bins zero = {8'h00}; bins one = {8'h01}; bins mid = {8'h7F, 8'h80};
      bins max  = {8'hFF}; bins other = default;
    }
    cp_b: coverpoint b {
      bins zero = {8'h00}; bins one = {8'h01}; bins mid = {8'h7F, 8'h80};
      bins max  = {8'hFF}; bins other = default;
    }
    cp_flags: coverpoint flags {
      bins none = {4'b0000}; bins z = {4'b0001}; bins n = {4'b0010};
      bins c    = {4'b0100}; bins p = {4'b1000}; bins multi = default;
    }
    cx_op_a: cross cp_op, cp_a;
    cx_op_b: cross cp_op, cp_b {
      // Kaydirmada b, c_shift_range ile 0-7'ye kisitli: mid/max hic gelemez -> kapsam disi
      ignore_bins shift_big = binsof(cp_op) intersect {OP_SHL, OP_SHR} &&
                              binsof(cp_b)  intersect {[8:255]};
    }
  endgroup

  function new(string name = "alu_coverage", uvm_component parent = null);
    super.new(name, parent);
    cg = new();                                  // covergroup elle yaratilir (Gun 3)
  endfunction

  virtual function void write(alu_transaction t);
    cg.sample(t.opcode, t.a, t.b, t.flags);
  endfunction

  virtual function void report_phase(uvm_phase phase);
    super.report_phase(phase);
    `uvm_info("COV", $sformatf("Fonksiyonel kapsam (alu_coverage): %0.2f%%", cg.get_inst_coverage()), UVM_NONE)
  endfunction
endclass

Kodun Açıklaması

  • with function sample(...): Gün 3'teki handle tabanlı toplayıcının tekniği, ama tetikleyici TLM (write()). İki yaklaşım birleşti: port üzerinden bağımsızlık, argümanlı sample ile sınıf üyesi değişkenlere bağımlılıktan kurtulma.
  • cp_op: coverpoint op; Enum tipli coverpoint'te her enum değeri otomatik bin olur; 8 satır yazmaya gerek yok.
  • ignore_bins gerekçeli: OP_SHL/OP_SHR ile b ≥ 8 kısıt gereği hiç üretilemez; bu çaprazları saymak kapsamı asla %100'e ulaşmayacak biçimde şişirirdi. Ders 34-1'deki kural: her exclusion'ın bir gerekçesi olmalı; burada gerekçe c_shift_range'dir.
  • default bin'ler çaprazlara girmez: other/multi yalnızca coverpoint'in kendi yüzdesini anlamlı tutar; cross yalnızca adlandırılmış bin'leri çaprazlar.

Lab 7.4: Environment — Dört Alıcı, Tek Yayın

class alu_env extends uvm_env;
  `uvm_component_utils(alu_env)

  alu_agent      agent;
  alu_predictor  pred;
  alu_scoreboard sb;
  alu_coverage   cov;

  function new(string name = "alu_env", uvm_component parent = null);
    super.new(name, parent);
  endfunction

  virtual function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    agent = alu_agent::type_id::create("agent", this);
    pred  = alu_predictor::type_id::create("pred", this);
    sb    = alu_scoreboard::type_id::create("sb", this);
    cov   = alu_coverage::type_id::create("cov", this);
  endfunction

  virtual function void connect_phase(uvm_phase phase);
    super.connect_phase(phase);
    agent.ap.connect(pred.analysis_export);     // gozlem -> referans model
    agent.ap.connect(sb.act_export);            // gozlem -> scoreboard (gercek)
    agent.ap.connect(cov.analysis_export);      // gozlem -> kapsam
    pred.expected_ap.connect(sb.exp_export);    // beklenen -> scoreboard
  endfunction
endclass

Kodun Açıklaması

  • agent.ap üç kez connect edilir: Gün 3'teki broadcast davranışı; mon.ap.write(tr) tek çağrıyla predictor, scoreboard ve coverage'ın write()'larını sırayla tetikler. Env, monitor'ü hiç görmez; agent'ın ap'si yeterlidir (Gün 6).
  • Çağrı sırası ve FIFO düzeni: Aynı write zinciri içinde önce predictor çalışır (beklenen exp_fifo'ya girer), sonra act_fifo dolar. Scoreboard get'leri sırayla okuduğu için her beklenen doğru gözlenenle eşleşir. Bağlantı sırası değişse bile her iki FIFO aynı gözlem için birer öğe alır; eşleşme bozulmaz.
  • Predictor ve coverage uvm_subscriber: İkisinin de tek girişi var; analysis_export hazır geldiği için ek port tanımlanmadı. Scoreboard'un iki girişi olduğu için export'ları kendisi tanımladı.

Lab 7.5: Sequence'ler ve Test Hiyerarşisi

Görev: Gün 6'daki alu_random_seq'e Ders 38'deki köşe tarayıcıyı ekleyin; testler yalnızca hangi sequence'in koşacağını seçsin.

// Ders 38: ALU_Corner_Generator — her kose degeri her islemle caprazlar (240 islem)
class alu_corner_seq extends uvm_sequence #(alu_transaction);
  `uvm_object_utils(alu_corner_seq)
  bit [7:0] corners[] = '{8'h00, 8'h01, 8'h7F, 8'h80, 8'hFE, 8'hFF};

  function new(string name = "alu_corner_seq");
    super.new(name);
  endfunction

  virtual task body();
    foreach (corners[i]) begin
      foreach (corners[j]) begin
        for (int op = 0; op < 8; op++) begin
          opcode_e o = opcode_e'(op);
          if (o inside {OP_SHL, OP_SHR} && corners[j] > 7) continue;   // c_shift_range ile celisir
          req = alu_transaction::type_id::create("req");
          start_item(req);
          if (!req.randomize() with { opcode == o; a == corners[i]; b == corners[j]; })
            `uvm_error("SEQ", "randomize basarisiz")
          finish_item(req);
        end
      end
    end
  endtask
endclass

class alu_base_test extends uvm_test;
  `uvm_component_utils(alu_base_test)
  alu_env env;

  function new(string name = "alu_base_test", uvm_component parent = null);
    super.new(name, parent);
  endfunction

  virtual function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    env = alu_env::type_id::create("env", this);
  endfunction

  virtual function void end_of_elaboration_phase(uvm_phase phase);
    super.end_of_elaboration_phase(phase);
    uvm_top.print_topology();
  endfunction

  // KANCA: turetilmis testler yalnizca bunu ezer (Ders 44'teki configure_env fikri)
  virtual function uvm_sequence #(alu_transaction) make_sequence();
    alu_random_seq seq = alu_random_seq::type_id::create("seq");
    return seq;
  endfunction

  virtual task run_phase(uvm_phase phase);
    uvm_sequence #(alu_transaction) seq = make_sequence();
    phase.raise_objection(this, get_type_name());
    phase.phase_done.set_drain_time(this, 50);   // son islemin sonucu pinlere ciksin (Gun 1 ek bolumu)
    seq.start(env.agent.sqr);
    phase.drop_objection(this, get_type_name());
  endtask

  // Gecti/kaldi karari: hata sayacina bakilir, scoreboard'a degil
  virtual function void report_phase(uvm_phase phase);
    uvm_report_server svr = uvm_report_server::get_server();
    super.report_phase(phase);
    if (svr.get_severity_count(UVM_ERROR) == 0 && svr.get_severity_count(UVM_FATAL) == 0)
      `uvm_info("TEST", "*** TEST PASSED ***", UVM_NONE)
    else
      `uvm_info("TEST", $sformatf("*** TEST FAILED *** (%0d UVM_ERROR)", svr.get_severity_count(UVM_ERROR)), UVM_NONE)
  endfunction
endclass

class alu_random_test extends alu_base_test;
  `uvm_component_utils(alu_random_test)
  function new(string name = "alu_random_test", uvm_component parent = null);
    super.new(name, parent);
  endfunction
  virtual function uvm_sequence #(alu_transaction) make_sequence();
    alu_random_seq seq = alu_random_seq::type_id::create("seq");
    seq.n_items = 200;
    return seq;
  endfunction
endclass

class alu_corner_test extends alu_base_test;
  `uvm_component_utils(alu_corner_test)
  function new(string name = "alu_corner_test", uvm_component parent = null);
    super.new(name, parent);
  endfunction
  virtual function uvm_sequence #(alu_transaction) make_sequence();
    return alu_corner_seq::type_id::create("seq");
  endfunction
endclass

tb_top Gün 6'dakiyle aynıdır; yalnızca run_test("alu_base_test") yerine argümansız run_test() yazın ve testi komut satırından seçin:

+UVM_TESTNAME=alu_random_test      # 200 rastgele islem
+UVM_TESTNAME=alu_corner_test      # 240 yonlendirilmis kose islemi

Kodun Açıklaması

  • make_sequence() kancası: Ders 44'te ALU_Corner_Test::run() taban akışı kopyalıyordu ve bunu "kod kokusu" olarak işaretlemiştik. Burada taban run_phase sabittir; türetilmiş test yalnızca sequence'i seçer. Yeni bir senaryo = yeni sequence + 10 satırlık test sınıfı.
  • Dönüş tipi uvm_sequence #(alu_transaction): Her iki sequence de bundan türediği için kanca tek tipte dönebilir; seq.start(sqr) polimorfik çalışır (SystemVerilog Ders 14).
  • set_drain_time(this, 50): Objection sıfıra düştükten sonra UVM 50 birim daha bekler; son finish_item döndüğünde DUT sonucu henüz pinlerde değildir, monitor bu süre içinde görür. Gün 6'daki #50'nin standart karşılığıdır.
  • report_phase'de hata sayacı: *** TEST PASSED *** kararı scoreboard'un sayaçlarına değil UVM hata sayacına bakar: check_phase'deki "kuyrukta öğe kaldı", driver'ın NOVIF'i, sequence'in randomize hatası, hepsi UVM_ERROR/UVM_FATAL üretir ve testi düşürür. Regresyon betiği tek bir satırı arar.
  • alu_corner_seq 240 işlem üretir: 6 × 6 × 8 = 288 kombinasyondan kaydırma + b > 7 olan 48'i atlanır (Ders 38'deki hesap). randomize() with ile değerler sabitlenir ama sınıf kısıtları yine denetlenir; continue olmasaydı c_shift_range ile çelişen kombinasyonlar randomize'ı düşürürdü.

Çalıştırma ve Beklenen Çıktı

+UVM_TESTNAME=alu_corner_test ile (zamanlar yaklaşıktır):

UVM_INFO @ 0: reporter [RNTST] Running test alu_corner_test...
UVM_INFO @ 0: reporter [UVMTOP] UVM testbench topology:   (portlar kisaltildi)
  uvm_test_top          alu_corner_test
    env                 alu_env
      agent             alu_agent
        ap              uvm_analysis_port
        drv             alu_driver
        mon             alu_monitor
        sqr             uvm_sequencer #(REQ,RSP)
      cov               alu_coverage
      pred              alu_predictor
      sb                alu_scoreboard
        act_export      uvm_analysis_export
        act_fifo        uvm_tlm_analysis_fifo #(T)
        exp_export      uvm_analysis_export
        exp_fifo        uvm_tlm_analysis_fifo #(T)
UVM_INFO @ 0:  uvm_test_top.env.agent.drv [DRV] Reset basliyor
UVM_INFO @ 60: uvm_test_top.env.agent.drv [DRV] Reset tamamlandi
UVM_INFO @ 85: uvm_test_top.env.agent.mon [MON] Gozlendi: OP_ADD a=0x00 b=0x00 -> result=0x0000 flags=0001 (P C N Z)
...
UVM_INFO @ 4915: uvm_test_top.env.sb [SB]
  ===== SCOREBOARD =====
  Toplam    : 240
  Basarili  : 240
  Basarisiz : 0
  ======================
UVM_INFO @ 4915: uvm_test_top.env.cov [COV] Fonksiyonel kapsam (alu_coverage): 100.00%
UVM_INFO @ 4915: uvm_test_top [TEST] *** TEST PASSED ***
--- UVM Report Summary ---
UVM_ERROR :    0
UVM_FATAL :    0

Rastgele test 200 işlemle genellikle %100'e ulaşmaz (köşe çaprazlarının bir kısmı eksik kalır); bu, Ders 34-1'deki "tek test nadiren her şeyi kapatır, veritabanları birleştirilir" dersinin canlı örneğidir.

Hata Enjeksiyonu: Scoreboard Gerçekten Çalışıyor mu?

Hiç hata bulmamış bir scoreboard'a güvenmeyin. Ders 36-1, Kendinizi Deneyin #2'deki hatayı DUT'a enjekte edin: OP_SUB: next_result = operand_b - operand_a;. alu_corner_test artık şunu basar:

UVM_ERROR @ 2545: uvm_test_top.env.sb [SB] MISMATCH #1
  beklenen: OP_SUB a=0x00 b=0x01 -> result=0xffff flags=0110 (P C N Z)
  gozlenen: OP_SUB a=0x00 b=0x01 -> result=0x0001 flags=1000 (P C N Z)
...
  Toplam    : 240
  Basarili  : 210
  Basarisiz : 30
UVM_INFO @ 4915: uvm_test_top [TEST] *** TEST FAILED *** (30 UVM_ERROR)

Neden 30? SUB için 36 köşe çifti vardır; a == b olan 6'sında b - a == a - b olduğundan hata görünmez. Bir hatanın bazı girdilerde gizlenebildiğini görmek, "testler geçti" cümlesine neden şüpheyle bakmanız gerektiğini anlatır. Hatayı geri alın ve testin tekrar geçtiğini doğrulayın.

Küçük Bir Regresyon

for t in alu_random_test alu_corner_test; do
  <simulator> ... +UVM_TESTNAME=$t > $t.log 2>&1
  grep -q "TEST PASSED" $t.log && echo "$t: PASS" || echo "$t: FAIL"
done

Ders 46'daki +TEST= betiğinin UVM karşılığı: test seçimi case bloğuyla değil factory'yle yapılır (Gün 5), karar *** TEST PASSED *** satırından okunur.

Dosya Yapısı

alu_pkg.sv                 package alu_pkg; import uvm_pkg::*; `include "uvm_macros.svh"
  ├─ alu_transaction.sv    Gun 6
  ├─ alu_driver.sv         Gun 6
  ├─ alu_monitor.sv        Gun 6
  ├─ alu_agent.sv          Gun 6
  ├─ alu_predictor.sv      Gun 7
  ├─ alu_scoreboard.sv     Gun 7
  ├─ alu_coverage.sv       Gun 7
  ├─ alu_env.sv            Gun 7 (guncellendi)
  ├─ alu_sequences.sv      Gun 6 + Gun 7
  └─ alu_tests.sv          Gun 7
alu_if.sv, alu.sv          SystemVerilog projesi (Ders 36, 36-1), degismedi
tb_top.sv                  Gun 6; run_test() argumansiz

`include sırası bağımlılık sırasıdır (transaction herkesten önce, env scoreboard'dan sonra, testler en sonda); paket tek derleme birimi olduğu için Ders 46'daki "önce tanımla, sonra kullan" kuralı aynen geçerlidir.

SystemVerilog Projesi ile Son Karşılaştırma

SV projesi (Dersler 41–46) UVM (Gün 6–7) Kazanım
ALU_Generator::run() + -> done alu_random_seq::body(); bitiş start() dönüşünde Senaryolar birleştirilebilir, yeniden kullanılabilir
gen2drv / drv2scb / mon2scb mailbox'ları seq_item_port + analysis port/FIFO 1→N yayın, el sıkışma, zaman tüketmeyen write()
Scoreboard driver kopyasından beslenir Predictor monitor'den beslenir Doğrulama tamamen gözleme dayanır
env.run(): fork … join_any + wait zinciri run_phase paralel, objection + drain time Bitiş mantığı standart, bileşen sayısından bağımsız
env.report() → scb.report(), cov.report() report_phase her bileşende otomatik Rapor sırası ve "unutulan rapor" sorunu yok
+TEST= + case +UVM_TESTNAME + factory Test eklemek için tb_top değişmez
env.gen = corner_gen (Ders 44) make_sequence() kancası veya factory override Env'in içine dokunulmaz
$display("[Scoreboard] HATA") `uvm_error → hata sayacı → TEST FAILED Geçti/kaldı kararı makine tarafından okunur

Önemli Noktalar

  • Scoreboard monitor'den beslenir, driver'dan değil: Driver'ın "ne gönderdiğini sandığı" ile pinlerde olan arasındaki fark, tam da yakalamak istediğiniz hatadır.
  • Predictor ayrı, scoreboard genel: "Beklenen nedir?" protokole özgü; "eşleştir ve karşılaştır" değildir. Ayrım, her iki parçanın yeniden kullanımını sağlar.
  • write() içinde kopyala, zaman tüketme: Nesneyi değiştirecekseniz clone(); bloklamanız gerekiyorsa FIFO + run_phase.
  • check_phase sessiz başarıyı engeller: "Kuyrukta öğe kaldı" ve "hiç karşılaştırma yapılmadı" kontrolleri olmadan, bağlantısı unutulmuş bir scoreboard testi geçirir.
  • Geçti/kaldı = hata sayacı: uvm_report_server tüm bileşenlerin UVM_ERROR/UVM_FATAL'larını toplar; test kararı tek yerden, tek satırla verilir.
  • ignore_bins gerekçe ister: Kısıt gereği üretilemeyen kombinasyonları çaprazdan düşürün; "ulaşamadım" diye düşürmeyin (Ders 34-1).
  • Hata enjeksiyonu rutin olmalı: Scoreboard'un bir hatayı gerçekten yakaladığını görmeden ortam "bitti" sayılmaz.
  • Test = senaryo seçimi: Sequence kancası ya da factory override; env ve agent'a dokunmadan her yeni senaryo bir test sınıfıdır.

Sık Yapılan Hatalar

  • Predictor'da clone() yapmamak: Monitor'ün nesnesine result yazılır; scoreboard "gözlenen"i beklenenle aynı nesneden okur, her şey geçer. Hata enjeksiyonu testi bunu anında ortaya çıkarır.
  • FIFO'ları type_id::create ile yaratmaya çalışmak: FIFO ve port'lar new("ad", this) ile yaratılır (Gün 3); create kullanılırsa parametreli tip için factory kaydı gerekir ve gereksizdir.
  • exp_export'u FIFO'ya bağlamayı unutmak: connect_phase sonunda UVM "export bağlı değil" uyarısı verir ve exp_fifo hiç dolmaz; scoreboard ilk get'te sonsuza kadar bekler. check_phase'deki "hiç karşılaştırılmadı" hatası bunu görünür kılar.
  • Objection'ı boşaltmadan düşürmek: Son exp gelir, act gelmez; check_phase exp=1 act=0 basar. set_drain_time ekleyin.
  • Field makrolarının compare()'ını körü körüne kullanmak: Girişleri de karşılaştırır; bu projede zararsız ama gürültülü, başka projede yanlış pozitif kaynağıdır. Neyi karşılaştırdığınızı bilin (FAQ #3).
  • cp_op: coverpoint op için bins yazıp enum değerlerini unutmak: Enum coverpoint'inde otomatik bin'ler yeterlidir; elle bin yazarken bir değeri atlamak kapsamı sessizce düşürür.
  • run_test()'i argümansız bırakıp +UVM_TESTNAME vermemek: Hiçbir test yaratılmaz; UVM "no components instantiated" fatal'ıyla durur. Regresyon betiği her zaman test adını geçmeli.

Kendinizi Deneyin

  1. Predictor'daki $cast(exp, t.clone()) satırını exp = t; yapın ve alu_corner_test'i koşun. Geçti mi? Şimdi SUB hatasını enjekte edip tekrar koşun. Scoreboard neden hiçbir şey yakalamıyor?
  2. set_drain_time satırını silin. check_phase ne basıyor? *** TEST FAILED *** görünüyor mu? Neden bu, "kaçırılmış bir hata"dan daha iyidir?
  3. alu_env::connect_phase'de agent.ap.connect(sb.act_export) satırını silin. Hangi faz, hangi mesajla sizi uyarıyor? Testin sonucu ne?
  4. Gün 5 tekniğiyle bir alu_narrow_transaction yazın (constraint c_narrow { a inside {[0:15]}; b inside {[0:15]}; }) ve yalnızca override yapan bir alu_narrow_test ekleyin. Kapsam yüzdesi nasıl değişti? Hangi bin'ler boş kaldı?
  5. Scoreboard'u sırasız (out-of-order) hâle getirin: beklenenleri alu_transaction exp_q[$] kuyruğunda tutun, gözlenen geldiğinde a, b, opcode üçlüsü eşleşen ilk öğeyi find_first_index ile bulup çıkarın. Hangi durumda bu yapı zorunludur (Ders 42 tablosu)?
  6. alu_coverage::report_phase'e cg.cp_op.get_inst_coverage() gibi coverpoint bazlı yüzdeler ekleyin; rastgele testte en düşük hangi coverpoint?
  7. alu_random_test'i 50 kez farklı seed ile koşup (+ntb_random_seed= / -sv_seed / +UVM_... aracınıza göre) kapsam veritabanlarını birleştirin. Köşe testi olmadan %100'e kaç koşuda ulaşıyorsunuz?
Hızlı Kontrol: Scoreboard'un check_phase'i neden "hiç karşılaştırma yapılmadı" diye hata verir; sıfır hata zaten iyi değil mi?

Sıfır hata ile sıfır karşılaştırma farklı şeylerdir. Bağlantısı unutulmuş, monitor'ü hiç out_valid görmemiş ya da objection'ı anında düşmüş bir ortamda scoreboard hiç çalışmaz ve hata sayacı sıfır kalır; test geçer. check_phase bu "sessiz başarı"yı açık bir hataya çevirir: bir doğrulama ortamı, hiçbir şeyi denetlemediğini de raporlamalıdır.

UVM Gün 7: Sıkça Sorulan Sorular (FAQ)

Soru 1: Predictor'ı scoreboard'un içine bir predict() fonksiyonu olarak koysam olmaz mıydı?

Cevap: Olurdu ve küçük projelerde yaygındır (Ders 42'de böyle yaptık). Ayırmanın iki getirisi vardır: scoreboard protokolden bağımsız kalır (iki FIFO + karşılaştır, her projede aynı) ve gerçek tasarımlarda giriş/çıkış monitor'leri ayrı olduğunda predictor doğal olarak giriş tarafına bağlanır. Tek monitor'lü bu ALU'da ayrım biraz "fazla" görünebilir; alışkanlık, büyük projede karşılığını verir.

Soru 2: Neden write() içinde hemen karşılaştırmak yerine FIFO + run_phase kullanıldı?

Cevap: Burada iki akış var: beklenen (predictor'dan) ve gözlenen (monitor'den). write() tek bir akışın tek bir öğesini görür; "ikisi de geldi mi?" sorusunu cevaplayamaz, üstelik bloklayamaz. FIFO, write()'ı kuyruğa koymaya indirger; run_phase'deki bloklayan get çifti ise eşleştirmeyi doğal biçimde yazdırır. Gün 3'te tek akışta write() içinde karşılaştırmak yeterliydi; iki akış olunca FIFO standarttır.

Soru 3: exp.compare(act) kullansam ne olurdu?

Cevap: Field makroları compare()'ı tüm UVM_ALL_ON alanlar için üretir: opcode, a, b, result, flags. Girişler iki nesnede zaten aynı olduğu için sonuç yalnızca çıkışlara bağlı olurdu; yani çalışırdı. Üç alternatif: (1) girişleri UVM_ALL_ON | UVM_NOCOMPARE ile işaretlemek; (2) do_compare(uvm_object rhs, uvm_comparer comparer) yazıp yalnızca çıkışları karşılaştırmak (Gün 2 ek bölümündeki do_* kalıbı); (3) bu derste yapıldığı gibi alanları elle === ile karşılaştırmak. Üçüncüsü en şeffaf, ikincisi büyük transaction'larda en ölçeklenebilir olanıdır. compare()'ın == kullandığını, X'i yakalamak için uvm_comparer'ın ayarlanması gerektiğini de unutmayın.

Soru 4: clone() ile copy() arasındaki fark nedir?

Cevap: copy(rhs) mevcut bir nesnenin alanlarını rhs'den doldurur; clone() ise factory üzerinden yeni bir nesne yaratıp ona copy() yapar ve uvm_object döndürür. Predictor elinde boş bir nesne olmadığı için clone() kullandı; $cast gerekliliği de bu yüzdendir. İkisi de field makrolarından (ya da elle yazılmış do_copy'den) beslenir; uvm_field_* yazmadıysanız copy() hiçbir alanı kopyalamaz.

Soru 5: Coverage'ı neden Gün 3'teki gibi handle ile değil subscriber ile bağladık?

Cevap: İkisi de geçerlidir. Burada tüm alıcılar (predictor, scoreboard, coverage) aynı agent.ap'den beslendiği için bir tane daha connect satırı en tutarlı yoldu; monitor'ün coverage'ı tanıması gerekmedi. Handle tabanlı yaklaşım, örneklemenin bileşen içinde birden çok noktadan ve farklı argümanlarla yapılması gerektiğinde (örneğin driver'ın iç durumunu örneklemek) tercih edilir.

Soru 6: +UVM_TESTNAME ile Gün 5'teki factory override aynı mekanizma mı?

Cevap: Aynı factory, iki farklı kapı. +UVM_TESTNAME=alu_corner_test, run_test()'in factory'den adıyla hangi test sınıfını yaratacağını söyler (create_component_by_name). Override ise "X istenince Y ver" kuralıdır. İkisini birleştirebilirsiniz: +UVM_TESTNAME=alu_random_test +uvm_set_type_override=alu_transaction,alu_narrow_transaction tek derlemeyle yepyeni bir senaryo üretir; regresyonların "tek derleme, yüzlerce koşu" ilkesi buna dayanır.