Virtual Interface ile DUT'a Bağlanma: Gerçek Driver ve Monitor

UVM Gün 6: DUT Entegrasyonu | uvm_config_db#(virtual alu_if), clocking block üzerinden süren driver, out_valid'de örnekleyen monitor, agent'ın analysis port'u, tb_top'ta interface + DUT + run_test()

Amaç

Gün 1–5 boyunca ortamı DUT'suz kurduk: driver yalnızca log basıp #20 bekledi, monitor sahte transaction üretti. Bu derste SystemVerilog bitirme projesinin 8-bit ALU'sunu (Ders 36-1) ve alu_if arayüzünü (Ders 36) UVM ortamına bağlayacağız. Dersin sonunda elinizde gerçek pinleri süren bir alu_driver, DUT çıkışlarını transaction'a çeviren bir alu_monitor, bunları paketleyen bir agent ve DUT'u gerçekten çalıştıran bir tb_top olacak. Scoreboard ve coverage Gün 7'de bu iskeletin üzerine gelecek.

Ön Koşullar

  • SystemVerilog Gün 5: interface, modport, clocking block, virtual interface (Dersler 30–32).
  • SystemVerilog bitirme projesi: alu modülü (Ders 36-1), alu_if (Ders 36), ALU_Driver (Ders 39), ALU_Monitor (Ders 41). Bu derste aynı interface ve DUT değişmeden kullanılır; driver ve monitor UVM'e taşınır.
  • UVM Gün 2–4: uvm_sequence_item, sequencer–driver el sıkışması, uvm_analysis_port, uvm_config_db.

İki Dünya, Bir Köprü

UVM bileşenleri sınıftır: build_phase'de yaratılır, hiyerarşik modül yolu yoktur. DUT ve interface ise modüldür: elaboration'da var olur, sabit bir yolu vardır (tb_top.aif). Sınıfın interface'e ulaşmasının tek taşınabilir yolu virtual interface handle'ıdır; handle'ın sınıfa ulaşmasının UVM'deki standart yolu ise uvm_config_db'dir. SystemVerilog projesinde bu işi new(aif, aif) ile yapıcıya vererek çözmüştünüz; UVM'de yapıcı imzası sabit olduğu için (new(name, parent)) veri tabanı devreye girer.

tb_top (modül, statik)                         UVM hiyerarşisi (sınıflar, dinamik)
──────────────────────────────                 ─────────────────────────────────────────
clk ──▶ alu_if aif(clk) ──▶ alu dut(...)       uvm_test_top : alu_base_test
            │                                     └─ env : alu_env
            │  uvm_config_db#(virtual alu_if)         └─ agent : alu_agent
            └────── ::set(null,"uvm_test_top.*",         ├─ sqr : alu_sequencer ◀── alu_random_seq
                           "vif", aif)                   ├─ drv : alu_driver   ── vif.drv_cb ──▶ DUT girişleri
                                                         └─ mon : alu_monitor  ◀─ vif.mon_cb ─── DUT çıkışları
                                                               └─ ap ─▶ agent.ap ─▶ (Gün 7: scoreboard, coverage)

set çağrısı tb_top'ta, run_test()'ten önce yapılır; get çağrıları driver ve monitor'ün build_phase'inde, yani simülasyonun 0. anında çalışır. Sıralama bu yüzden kritiktir.

Gün 2–4'teki İskeletten Gerçek Bileşenlere

Gün 2–4 (DUT'suz) Bu ders (ALU) Değişen
my_transaction (addr/data/we) alu_transaction (opcode/a/b + result/flags) Alanlar DUT sözleşmesine göre
my_driver: log + #20 alu_driver: vif.drv_cb üzerinden pin sürer, reset yapar drive_item gövdesi
my_monitor: sahte tr alu_monitor: out_valid anında pinleri okur run_phase gövdesi
my_agent alu_agent + kendi ap'si Monitor portunu dışarı açar
tb_top: yalnızca run_test() tb_top: saat + interface + DUT + config_db::set + run_test() Statik dünya eklendi

Fazlar, factory, sequencer–driver el sıkışması, objection: hiçbiri değişmedi. UVM'in vaadi tam olarak budur: altyapı sabit kalır, yalnızca protokole özgü parçalar yazılır.

Lab 6.1: Interface ve DUT (SystemVerilog Projesinden Aynen)

alu_if ve alu modülü Ders 36 ve 36-1'dekiyle birebir aynıdır; burada yalnızca hatırlatma için interface'in iskeleti verilmiştir.

interface alu_if(input logic clk);
  logic        rst_n;
  logic        in_valid;
  logic [7:0]  operand_a, operand_b;
  logic [2:0]  opcode;
  logic        out_valid;
  logic [15:0] result;
  logic [3:0]  flags;

  clocking drv_cb @(posedge clk);          // driver: surer + okur
    default input #1 output #1;
    output in_valid, operand_a, operand_b, opcode;
    input  out_valid, result, flags;
  endclocking

  clocking mon_cb @(posedge clk);          // monitor: yalnizca okur
    default input #1;
    input in_valid, operand_a, operand_b, opcode, out_valid, result, flags;
  endclocking

  modport driver  (clocking drv_cb, output rst_n, input clk);
  modport monitor (clocking mon_cb, input clk, rst_n);
endinterface

Kodun Açıklaması

  • İki clocking block, iki bakış açısı: drv_cb giriş sinyallerini output olarak sürer; mon_cb aynı sinyalleri input olarak okur. Bir clocking block'un output'u okunamadığı için monitor'ün kendi penceresi olmak zorundadır (Ders 36'daki tartışma).
  • rst_n clocking dışında: Reset asenkron bir sinyaldir; driver onu doğrudan (vif.rst_n = 0) sürer.
  • Modport'lar isteğe bağlı: Bu derste uvm_config_db#(virtual alu_if) ile modport'suz handle taşınır ve driver/monitor vif.drv_cb / vif.mon_cb üzerinden çalışır. Modport'lu handle (virtual alu_if.driver) da taşınabilir; ama set ve get'teki tip parametresi birebir aynı olmak zorunda olduğu için (virtual alu_if ≠ virtual alu_if.driver) ekipler genellikle tek bir tipte karar kılar. Sektörde yaygın olan, modport'suz tam interface'i taşımaktır.

Lab 6.2: ALU Transaction

Görev: alu_transaction sınıfını DUT sözleşmesine (Ders 36-1) göre yazın. Opcode kodlaması DUT ile birebir aynı olmalıdır.

package alu_pkg;
  import uvm_pkg::*;
  `include "uvm_macros.svh"

  // DUT'taki localparam'larla BIREBIR ayni kodlama (Ders 36-1)
  typedef enum logic [2:0] {
    OP_ADD = 3'b000, OP_SUB = 3'b001, OP_AND = 3'b010, OP_OR  = 3'b011,
    OP_XOR = 3'b100, OP_NOT = 3'b101, OP_SHL = 3'b110, OP_SHR = 3'b111
  } opcode_e;

  class alu_transaction extends uvm_sequence_item;
    // Girisler: sequence rastgele uretir, driver surer
    rand opcode_e    opcode;
    rand bit [7:0]   a;
    rand bit [7:0]   b;
    // Cikislar: monitor pinlerden doldurur (logic: X/Z yakalanabilsin)
    logic [15:0]     result;
    logic [3:0]      flags;      // [0]=Z  [1]=N  [2]=C  [3]=P

    constraint c_shift_range { (opcode inside {OP_SHL, OP_SHR}) -> b inside {[0:7]}; }
    constraint c_corners {
      a dist { 8'h00 := 10, [8'h01:8'hFE] :/ 80, 8'hFF := 10 };
      b dist { 8'h00 := 10, [8'h01:8'hFE] :/ 80, 8'hFF := 10 };
    }

    `uvm_object_utils_begin(alu_transaction)
      `uvm_field_enum(opcode_e, opcode, UVM_ALL_ON)
      `uvm_field_int(a,      UVM_ALL_ON)
      `uvm_field_int(b,      UVM_ALL_ON)
      `uvm_field_int(result, UVM_ALL_ON)
      `uvm_field_int(flags,  UVM_ALL_ON)
    `uvm_object_utils_end

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

    // Loglar icin tek satirlik ozet (Gun 2: field makrolarinin print()'ine hafif alternatif)
    virtual function string convert2string();
      return $sformatf("%-6s a=0x%02h b=0x%02h -> result=0x%04h flags=%04b (P C N Z)",
                       opcode.name(), a, b, result, flags);
    endfunction
  endclass

  // Diger siniflar asagidaki laboratuvarlarda eklenir:
  // `include "alu_driver.sv" ... "alu_test.sv"
endpackage

Kodun Açıklaması

  • opcode_e paket seviyesinde: Enum'u sınıfın içine değil paketin içine koyduk; böylece driver (vif.drv_cb.opcode <= tr.opcode) ve monitor (opcode_e'(vif.mon_cb.opcode)) aynı tipi uzun yol yazmadan kullanır. SystemVerilog projesinde ALU_Transaction::opcode_e biçiminde sınıf içindeydi; ikisi de geçerlidir.
  • Girişler rand bit, çıkışlar logic: Girişleri sequence üretir, bit yeterlidir. Çıkışları monitor pinlerden okur; reset öncesi X değerlerini kaybetmemek için logic seçilir. uvm_field_int her iki tiple çalışır.
  • Kısıtlar SystemVerilog projesinden (Ders 37) sadeleştirildi: c_shift_range DUT'un yalnızca b[2:0]'ı kullandığı gerçeğini yansıtır; c_corners köşe değerleri öne çıkarır. Sequence'ler randomize() with {...} ile bunların üstüne kısıt ekleyebilir (Gün 4).
  • uvm_field_enum(opcode_e, opcode, UVM_ALL_ON): Enum alanları için ayrı makro vardır; print() çıktısında ham sayı yerine OP_ADD görünür.
  • convert2string(): `uvm_info içinde tr.convert2string() çağırmak, tr.print()'in çok satırlı tablosundan çok daha okunaklı bir log verir. Driver ve monitor bunu kullanacak.

Lab 6.3: Gerçek Driver — Clocking Block Üzerinden Sürmek

Görev: Gün 5'teki kancalı driver kalıbını koruyarak (drive_item), gövdeyi Ders 39'daki ALU_Driver'dan taşıyın.

class alu_driver extends uvm_driver #(alu_transaction);
  `uvm_component_utils(alu_driver)

  virtual alu_if vif;     // tb_top'tan uvm_config_db ile gelir

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

  virtual function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    // vif olmadan driver'in calismasi imkansiz: uyari degil, oldurucu hata
    if (!uvm_config_db#(virtual alu_if)::get(this, "", "vif", vif))
      `uvm_fatal("NOVIF", {"Virtual interface bulunamadi: ", get_full_name(), ".vif"})
  endfunction

  virtual task run_phase(uvm_phase phase);
    super.run_phase(phase);
    reset_dut();                          // once DUT'u bilinen duruma getir
    forever begin
      seq_item_port.get_next_item(req);   // sequence'ten paket iste (bloklar)
      drive_item(req);                    // pinlere sur (2 saat cevrimi)
      seq_item_port.item_done();          // sequence'e "suruldu" de
    end
  endtask

  // Ders 39: ALU_Driver::reset() ile ayni
  virtual task reset_dut();
    `uvm_info("DRV", "Reset basliyor", UVM_MEDIUM)
    vif.rst_n = 1'b0;                     // asenkron reset: clocking disi, dogrudan
    vif.drv_cb.in_valid  <= 1'b0;
    vif.drv_cb.operand_a <= '0;
    vif.drv_cb.operand_b <= '0;
    vif.drv_cb.opcode    <= '0;
    repeat (5) @(vif.drv_cb);
    vif.rst_n = 1'b1;
    @(vif.drv_cb);
    `uvm_info("DRV", "Reset tamamlandi", UVM_MEDIUM)
  endtask

  // Ders 39: ALU_Driver::drive_transaction() ile ayni
  virtual task drive_item(alu_transaction tr);
    @(vif.drv_cb);                        // saat kenarini bekle
    vif.drv_cb.in_valid  <= 1'b1;
    vif.drv_cb.operand_a <= tr.a;
    vif.drv_cb.operand_b <= tr.b;
    vif.drv_cb.opcode    <= tr.opcode;
    @(vif.drv_cb);                        // bir cevrim gecerli tut
    vif.drv_cb.in_valid  <= 1'b0;
    `uvm_info("DRV", {"Suruldu: ", tr.convert2string()}, UVM_HIGH)
  endtask
endclass

Kodun Açıklaması

  • **get başarısızlığında `uvm_fatal:** Gün 4'te driver_delay için uvm_warning + varsayılan kullanmıştık; çünkü gecikmesiz de çalışabilirdik. vif olmadan ise tek bir satır bile sürülemez; sessizce devam etmek yalnızca daha geç, daha anlaşılmaz bir null-handle hatası üretir. Kural: isteğe bağlı konfigürasyon → warning, zorunlu kaynak → fatal.
  • Reset driver'ın run_phase'inin başında: SystemVerilog projesinde environment drv.reset() çağırıyordu. Burada driver, sequence'ten ilk paketi istemeden önce DUT'u resetler. Sequence bu sırada start_item'da bekler (Gün 4'teki el sıkışma); yani reset bitmeden hiçbir paket sürülmez; ek bir senkronizasyon gerekmez. Büyük projelerde reset ayrı bir agent ya da reset_phase ile yönetilir; ilke aynıdır.
  • vif.drv_cb.x <= ... ve @(vif.drv_cb): Ders 39'daki kurallar aynen geçerli: veri sinyalleri clocking block üzerinden nonblocking atanır, output #1 skew DUT ile yarışı önler. Her transaction tam iki saat çevrimi sürer; in_valid bir çevrim yüksek kalır.
  • get_next_item / item_done çifti değişmedi: Gün 2'deki iskelet ile tek fark drive_item'ın artık gerçekten zaman harcaması ve pin sürmesidir. item_done() çağrısı ikinci @(vif.drv_cb)'den sonra geldiği için sequence'in finish_item'ı, paket gerçekten pinlere uygulandıktan sonra döner.

Lab 6.4: Gerçek Monitor — out_valid Anında Örnekleme

Görev: Ders 41'deki ALU_Monitor mantığını UVM'e taşıyın; mailbox yerine analysis port kullanın.

class alu_monitor extends uvm_monitor;
  `uvm_component_utils(alu_monitor)

  virtual alu_if vif;
  uvm_analysis_port #(alu_transaction) ap;   // Gun 3: 1 -> N yayin
  int unsigned n_observed = 0;

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

  virtual function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    ap = new("ap", this);
    if (!uvm_config_db#(virtual alu_if)::get(this, "", "vif", vif))
      `uvm_fatal("NOVIF", {"Virtual interface bulunamadi: ", get_full_name(), ".vif"})
  endfunction

  virtual task run_phase(uvm_phase phase);
    alu_transaction tr;
    super.run_phase(phase);
    forever begin
      @(vif.mon_cb);                                    // her saat kenarinda ornekle
      if (vif.mon_cb.out_valid === 1'b1) begin          // === : reset oncesi X'e aldanma
        tr = alu_transaction::type_id::create("tr");    // her gozlem icin YENI nesne
        tr.opcode = opcode_e'(vif.mon_cb.opcode);
        tr.a      = vif.mon_cb.operand_a;
        tr.b      = vif.mon_cb.operand_b;
        tr.result = vif.mon_cb.result;
        tr.flags  = vif.mon_cb.flags;
        n_observed++;
        `uvm_info("MON", {"Gozlendi: ", tr.convert2string()}, UVM_MEDIUM)
        ap.write(tr);                                   // scoreboard/coverage'a yayinla
      end
    end
  endtask
endclass

Kodun Açıklaması

  • Monitor sürmez, yalnızca okur: vif.mon_cb yalnızca input içerir; monitor'ün bir sinyali yanlışlıkla sürmesi derleme hatasıdır. Bu, modport kullanmadan da pasifliği garanti eder.
  • out_valid === 1'b1: Reset öncesi out_valid X'tir; == ile karşılaştırmak X üretir ve if bunu yanlış sayar (zararsız), ama != 0 yazsaydınız yanlış pozitif alırdınız. Ders 41'deki alışkanlık: ===.
  • Zamanlama sözleşmesi: Driver operandları in_valid düştükten sonra da bir çevrim pinlerde tuttuğu için out_valid=1 olan kenarda operand_a/b ve opcode hâlâ aynı işleme aittir. Bu, Ders 41'de açıklanan driver–monitor sözleşmesidir; driver back-to-back sürmeye başlarsa monitor girişleri in_valid anında yakalayıp çıkışla eşleştirmek zorunda kalır (Kendinizi Deneyin #3).
  • Her gözlemde create: Gün 3'teki tuzak: tr'yi döngü dışında bir kez yaratırsanız scoreboard ve coverage aynı nesneyi paylaşır, bir sonraki örnekleme "beklenen" değeri değiştirir. ap.write(tr) handle yayınlar, kopya değil.
  • n_observed: Gün 7'de check_phase sürülen ve gözlenen sayıları karşılaştıracak; ilk sağlık kontrolü budur.

Lab 6.5: Agent, Environment, Sequence, Test ve tb_top

Görev: Parçaları birleştirin. Agent, monitor'ün portunu kendi ap'si üzerinden dışarı açsın.

// Bos alt sinif yerine typedef (Gun 2'deki my_sequencer'in kisa hali)
typedef uvm_sequencer #(alu_transaction) alu_sequencer;

class alu_agent extends uvm_agent;
  `uvm_component_utils(alu_agent)

  alu_sequencer sqr;
  alu_driver    drv;
  alu_monitor   mon;
  uvm_analysis_port #(alu_transaction) ap;   // monitor'un portunu disari acar

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

  virtual function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    ap  = new("ap", this);
    mon = alu_monitor::type_id::create("mon", this);          // her zaman
    if (get_is_active() == UVM_ACTIVE) begin                  // yalnizca aktifse
      sqr = alu_sequencer::type_id::create("sqr", this);
      drv = alu_driver::type_id::create("drv", this);
    end
  endfunction

  virtual function void connect_phase(uvm_phase phase);
    super.connect_phase(phase);
    mon.ap.connect(ap);                                       // port -> port (yukari)
    if (get_is_active() == UVM_ACTIVE)
      drv.seq_item_port.connect(sqr.seq_item_export);
  endfunction
endclass

class alu_env extends uvm_env;
  `uvm_component_utils(alu_env)
  alu_agent agent;                                            // Gun 7: + scoreboard, coverage

  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);
  endfunction
endclass

// Rastgele N islem: Gun 4'teki start_item/finish_item kalibi
class alu_random_seq extends uvm_sequence #(alu_transaction);
  `uvm_object_utils(alu_random_seq)
  int n_items = 20;

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

  virtual task body();
    repeat (n_items) begin
      req = alu_transaction::type_id::create("req");
      start_item(req);
      if (!req.randomize())
        `uvm_error("SEQ", "randomize basarisiz")
      finish_item(req);
    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

  virtual task run_phase(uvm_phase phase);
    alu_random_seq seq;
    phase.raise_objection(this, "alu_random_seq");
    seq = alu_random_seq::type_id::create("seq");
    seq.start(env.agent.sqr);
    #50;   // son islemin sonucu bir cevrim sonra cikar; monitor gorsun (bosaltma)
    phase.drop_objection(this, "alu_random_seq");
  endtask
endclass
// tb_top.sv — statik dunya
`timescale 1ns/1ps
module tb_top;
  import uvm_pkg::*;
  import alu_pkg::*;

  logic clk = 0;
  always #5 clk = ~clk;                      // 100 MHz

  alu_if aif(clk);                           // Ders 36

  alu dut (                                  // Ders 36-1
    .clk(aif.clk),           .rst_n(aif.rst_n),         .in_valid(aif.in_valid),
    .operand_a(aif.operand_a), .operand_b(aif.operand_b), .opcode(aif.opcode),
    .out_valid(aif.out_valid), .result(aif.result),       .flags(aif.flags)
  );

  initial begin
    // vif, run_test()'ten ONCE veritabanina konur: build_phase onu zaman 0'da okur
    uvm_config_db#(virtual alu_if)::set(null, "uvm_test_top.*", "vif", aif);
    run_test("alu_base_test");
  end

  initial begin
    $dumpfile("alu_uvm.vcd");
    $dumpvars(0, tb_top);
  end
endmodule

Beklenen çıktı (zamanlar yaklaşıktır):

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=0x12 b=0x34 -> result=0x0046 flags=1000 (P C N Z)
UVM_INFO @ 105: uvm_test_top.env.agent.mon [MON] Gozlendi: OP_SUB a=0x00 b=0x01 -> result=0xffff flags=0110 (P C N Z)
...
UVM_INFO @ 485: uvm_test_top.env.agent.mon [MON] Gozlendi: OP_SHL a=0xff b=0x07 -> result=0x7f80 flags=0000 (P C N Z)
--- UVM Report Summary ---
UVM_ERROR : 0

Kodun Açıklaması

  • Agent'ın kendi ap'si: mon.ap.connect(ap) bir port'u bir port'a bağlar; UVM'de bu, alt bileşenin portunu üst bileşene "yükseltmek" için standart yoldur. Env artık agent.ap.connect(...) der ve agent'ın içinde monitor var mı, adı ne, bilmek zorunda kalmaz. Agent'ın dış yüzü üç şeyden oluşur: sqr (stimulus girişi), ap (gözlem çıkışı), is_active.
  • typedef uvm_sequencer #(alu_transaction) alu_sequencer: Gün 2'deki boş my_sequencer sınıfının kısa hâli; davranış eklemeyecekseniz typedef yeterlidir ve factory'ye kayıt da otomatiktir (uvm_sequencer kendi *_param_utils makrosunu taşır).
  • Sequence start_item'da reset'i bekler: Test seq.start(sqr) der demez sequence start_item'a girer ve driver get_next_item çağırana kadar bloklanır; driver ise reset_dut() bitmeden get_next_item demez. Böylece "reset sırasında stimulus sürüldü" hatası yapısal olarak imkânsızdır.
  • #50 boşaltma: Son finish_item döndüğünde paket sürülmüştür ama sonucu bir çevrim sonra çıkar; objection hemen düşerse monitor son işlemi göremez. Gün 1'deki set_drain_time bunun daha temiz yoludur (Kendinizi Deneyin #5).
  • set(null, "uvm_test_top.*", "vif", aif): null bağlam "en tepeden" demektir; "uvm_test_top.*" test altındaki her bileşenin bu anahtarı görmesini sağlar. Driver ve monitor get(this, "", "vif", vif) ile kendi yollarına bakar ve eşleşir. Yol "env.*" olsaydı uvm_test_top.env.agent.drv ile eşleşmezdi (desen köke göre çözülür) ve NOVIF alırdınız.
  • Sıralama garantisi: set ile run_test() aynı initial bloğunda, art arda yazılmıştır. Ayrı initial bloklarına bölerseniz hangisinin önce çalışacağı standart tarafından belirlenmez; run_test önce koşarsa build_phase veritabanını boş bulur.
  • Opcode dönüşümleri: Driver vif.drv_cb.opcode <= tr.opcode yazar (enum → 3-bit, örtük); monitor opcode_e'(vif.mon_cb.opcode) ile geri çevirir (3-bit → enum, açık cast). Cast olmadan opcode.name() çalışmaz.

Önemli Noktalar

  • uvm_config_db#(virtual alu_if) tipi her iki uçta birebir aynı olmalı: virtual alu_if ile virtual alu_if.driver farklı tiplerdir; set ve get farklı yazılırsa eşleşme olmaz ve get sessizce 0 döner. `uvm_fatal kontrolü bu yüzden şarttır.
  • set run_test()'ten önce, get build_phase'de: run_phase'de get yapmak çalışır ama geç kalmış olur; vif gibi yapısal kaynaklar build_phase'de alınır.
  • Clocking block disiplini UVM'de de aynı: Veri sinyalleri vif.drv_cb.x <=, bekleme @(vif.drv_cb), okuma vif.mon_cb.x. Ham sinyale (vif.in_valid) yalnızca reset gibi bilinçli istisnalar için dokunun.
  • Monitor bağımsızdır: Driver'ın ne gönderdiğini bilmez, yalnızca pinlerde gördüğünü raporlar. Gün 7'deki scoreboard tamamen monitor'den beslenecek; bu, SystemVerilog projesindeki "driver kopyası" yaklaşımından daha güvenilirdir.
  • Her gözlemde yeni nesne, ap.write(tr) handle yayınlar: Alıcılar nesneyi saklayacaksa (scoreboard kuyruğu) kopya almak alıcının sorumluluğudur; ama monitor her seferinde taze nesne yarattığı için paylaşım sorunu çıkmaz.
  • Agent dış yüzü: sqr, ap, is_active: Env ve test yalnızca bu üçüyle konuşur; agent'ın içi (driver, monitor) değişse bile üst katmanlar etkilenmez.
  • Pasif agent = sürücüsüz gözlemci: is_active = UVM_PASSIVE yapıldığında yalnızca monitor kalır; aynı agent, başka bir bloğun sürdüğü arayüzü izlemek için yeniden kullanılır (Gün 2).

Sürüşün Çevrim Çevrim Akışı (Ders 39'dan Hatırlatma)

Kenar Driver (drv_cb, output #1) DUT (always_ff) Monitor (mon_cb, input #1)
E0 in_valid<=1, A, B, OP in_valid henüz 0 —
E1 in_valid<=0 in_valid=1 → result<=, out_valid<=1 out_valid henüz 0
E2 Sonraki paket: in_valid<=1 in_valid=0 → out_valid<=0, result korunur E2'den 1 ns önce out_valid=1, result ve operandlar okunur → ap.write

Monitor E2 kenarında okuma yaparken driver aynı kenarda yeni operandları sürer; input #1 / output #1 skew'leri sayesinde monitor eski (kararlı) değerleri görür. Bu tablo, clocking block'ların "neden" sorusunun pratik cevabıdır.

Sık Yapılan Hatalar

  • set yolunu yanlış yazmak: "env.*", "*.drv" gibi desenler null bağlamla köke göre çözülür; uvm_test_top.env.agent.drv ile eşleşmezse NOVIF. +UVM_CONFIG_DB_TRACE ile hangi set'in hangi get ile eşleştiğini izleyin.
  • set'i run_test()'ten sonra ya da ayrı initial'da yapmak: build_phase zaman 0'da veritabanını boş bulur. Aynı initial bloğunda, run_test'ten önce.
  • Tip parametresinde modport farkı: set virtual alu_if ile, get virtual alu_if.driver ile → eşleşmez. Tek tipte karar kılın.
  • Driver'da @(vif.drv_cb) olmadan sürmek: İlk atama saat kenarı beklemeden aynı zaman diliminde yapılır; ardışık paketler üst üste biner. Her drive_item saat kenarıyla başlar.
  • Monitor'de ham vif.out_valid okumak: Kenar anındaki NBA güncellemeleriyle yarışır; mon_cb.out_valid Preponed bölgede örneklenmiş kararlı değerdir.
  • Objection'ı son finish_item'dan hemen sonra düşürmek: Monitor son sonucu göremez, Gün 7'de "sürülen ≠ gözlenen" hatası alırsınız. Boşaltma süresi (#50 veya set_drain_time) bırakın.
  • tr'yi döngü dışında yaratmak: Gün 3'teki paylaşılan nesne tuzağı; scoreboard'un beklenen değeri monitor'ün bir sonraki örneklemesiyle değişir.
  • Enum cast'ini unutmak: tr.opcode = vif.mon_cb.opcode; bazı araçlarda hata, bazılarında sessiz uyarıdır; opcode_e'(...) yazın.

Kendinizi Deneyin

  1. tb_top'taki set yolunu "env.*" yapın. Hangi bileşen, hangi fazda NOVIF verdi? +UVM_CONFIG_DB_TRACE ile eşleşme denemelerini izleyin.
  2. set satırını #1; gecikmeli ayrı bir initial bloğuna taşıyın. Sonuç değişti mi? Peki #0 gecikmeyle? Neden bu davranışa güvenmemek gerekir?
  3. Driver'ı back-to-back sürecek şekilde değiştirin (ikinci @(vif.drv_cb); in_valid <= 0; adımını kaldırın). Monitor hangi operandları hangi sonuçla eşleştiriyor? Ders 41, Kendinizi Deneyin #3'teki küçük pipeline düzeltmesini UVM monitor'üne uygulayın.
  4. alu_random_seq.n_items'ı testten uvm_config_db#(int)::set(this, "*", "n_items", 50) ile ayarlayın; sequence body() içinde uvm_config_db#(int)::get(m_sequencer, "", "n_items", n_items) ile okusun. Neden bağlam olarak this değil m_sequencer verildi?
  5. #50'yi kaldırıp phase.phase_done.set_drain_time(this, 50); kullanın (raise_objection'dan hemen sonra). Log'daki son MON mesajı hâlâ görünüyor mu?
  6. Agent'ı UVM_PASSIVE yapın (uvm_config_db#(uvm_active_passive_enum)::set(this, "env.agent", "is_active", UVM_PASSIVE) testin build_phase'ine). Test seq.start(env.agent.sqr) satırında ne hata veriyor? Pasif agent'lı bir testin run_phase'i nasıl olmalı?
Hızlı Kontrol: uvm_config_db#(virtual alu_if)::set(...) neden run_test()'ten önce çağrılmalıdır?

Çünkü run_test() tüm fazları başlatır ve build_phase simülasyonun 0. anında, run_test çağrısının içinde çalışır. Driver ve monitor vif'i build_phase'de get eder; o anda veritabanında yoksa `uvm_fatal("NOVIF") ile simülasyon durur. set ile run_test aynı initial bloğunda art arda yazıldığında sıralama garanti altındadır.

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

Soru 1: Driver neden doğrudan tb_top.aif.drv_cb.in_valid <= 1 yazmıyor? Hiyerarşik yol daha basit değil mi?

Cevap: Çalışır ama iki büyük bedeli vardır. Birincisi, alu_pkg içindeki sınıf tb_top'ı tanımak zorunda kalır; aynı driver başka bir testbench'te (soc_tb.alu_inst.aif) derlenemez. İkincisi, paket modülden bağımsız derlenmek istenir; paketten modül hiyerarşisine referans vermek derleme sırasını ve yeniden kullanımı bozar. Virtual interface, "hangi interface" kararını tb_top'a bırakır; sınıf yalnızca "bir alu_if" bilir.

Soru 2: Modport'lu virtual interface (virtual alu_if.driver) kullanmalı mıyım?

Cevap: Güvenlik açısından caziptir: monitor'e virtual alu_if.monitor verirseniz sürmeye çalışan satır derlenmez. Bedeli, uvm_config_db tip parametresinin her bileşen için farklı olmasıdır (set iki kez, iki tiple). Bu derste tek tip (virtual alu_if) taşındı ve pasiflik mon_cb'nin tümü-input olmasıyla sağlandı. Ekip standardınız hangisiyse ona uyun; karıştırmayın.

Soru 3: Reset'i neden driver yapıyor? Test ya da bir "reset agent" yapsa daha doğru olmaz mı?

Cevap: Küçük bir ortamda driver'ın run_phase başında reset yapması en az kodla en güvenli yoldur: stimulus reset bitmeden sürülemez. Birden fazla agent'ın aynı reset'e bağlı olduğu sistemlerde reset ayrı bir bileşene (reset agent) alınır ve diğer driver'lar wait (vif.rst_n === 1'b1) ile bekler; ya da run_phase'in alt fazları (reset_phase, main_phase) kullanılır. İlke değişmez: stimulus üreten herkes reset'in bittiğini bilmeli.

Soru 4: set çağrısında null bağlam ne demek; this kullanabilir miyim?

Cevap: tb_top bir modüldür, uvm_component değildir; this burada tanımsızdır. null (veya eşdeğeri uvm_root::get()) "hiyerarşinin kökünden itibaren" demektir ve inst_name deseni köke göre yazılır: "uvm_test_top.*". Test sınıfının içinden aynı işi yapsaydınız this ile "env.*" yazardınız; iki yazım aynı bileşenleri hedefler.

Soru 5: Birden fazla interface (örneğin iki ALU, iki agent) olsaydı ne değişirdi?

Cevap: Ya alan adını ayırırsınız ("vif_a", "vif_b") ya da yolu daraltırsınız: set(null, "uvm_test_top.env.agent_a.*", "vif", aif_a) ve set(null, "uvm_test_top.env.agent_b.*", "vif", aif_b). İkinci yol daha iyidir; agent kodu "vif" anahtarını değiştirmeden iki kez kullanılır. Bu, Gün 5'teki instance override fikrinin config_db karşılığıdır: aynı sınıf, yola göre farklı kaynak.

Soru 6: Monitor'ün ap.write(tr) çağrısı zaman tüketmiyor; scoreboard uzun bir karşılaştırma yaparsa monitor yavaşlar mı?

Cevap: write() bir function'dır, alıcının write()'ı da function olmak zorundadır; zaman tüketemez ama hesaplama yapar ve bu hesap monitor'ün çağrı zincirinde koşar. Pratikte scoreboard karşılaştırmaları birkaç mikrosaniyelik yazılım işidir, simülasyon zamanı ilerlemez; monitor "yavaşlamaz". Alıcı gerçekten zaman harcamak istiyorsa (örneğin bir referans modelin task'ı) veriyi uvm_tlm_analysis_fifo'ya atıp kendi run_phase'inde işler (Gün 3, TLM port türleri tablosu; Gün 7'de uygulanacak).