UVM 1.2'nin Mimarisi: Tasarımcıların İyi Yaptıkları

Blog · Kaynak Kod Okuması | 9 Ekim 2026

Giriş

UVM 1.2'nin asıl başarısı tek tek özelliklerinde değil, dilin eksiklerini kapatan birkaç tutarlı desende yatıyor: vekil nesneli factory, hiyerarşiye bağlı yapılandırma veritabanı, birbirine oklarla bağlı bir akış şeması olarak modellenmiş fazlar ve politika nesneleri. Bu yazıda Accellera'nın resmi deposundaki UVM_1_2_RELEASE etiketini indirip distrib/src altındaki yaklaşık 77.600 satırlık kütüphaneyi okudum.

Amaç UVM'in nasıl kullanılacağını anlatmak değil. Soru şu: SystemVerilog gibi kısıtlı bir nesne modeline sahip bir dilde, bu ekip yeniden kullanılabilir bir doğrulama altyapısını nasıl kurdu? Hangi kararlar 10 yılı aşkın süre ayakta kaldı, hangileri bedel ödetti?

Kod alıntıları doğrudan 1.2 kaynağından; yorum satırları kısaltıldı.

Tek paket, katmanlı include sırası

Kütüphanenin tamamı tek bir uvm_pkg paketidir ve uvm_pkg.sv neredeyse yalnızca include satırlarından oluşur. Include sırası aynı zamanda bağımlılık sırasıdır: her katman yalnızca öncekileri kullanır.

`include "uvm_macros.svh"

package uvm_pkg;
  `include "dpi/uvm_dpi.svh"
  `include "base/uvm_base.svh"
  `include "dap/uvm_dap.svh"
  `include "tlm1/uvm_tlm.svh"
  `include "comps/uvm_comps.svh"
  `include "seq/uvm_seq.svh"
  `include "tlm2/uvm_tlm2.svh"
  `include "reg/uvm_reg_model.svh"
endpackage
Dizin Satır İçerik
base 33.101 factory, config/resource DB, fazlar, objection, raporlama, politika sınıfları
reg 21.668 register soyutlama katmanı (RAL)
macros 6.884 utils, field, mesaj, TLM ve sequence makroları
seq 5.899 sequence, sequencer, sürücü el sıkışması
tlm1 2.650 port, export, imp, analysis port, FIFO
tlm2 2.601 generic payload, soket, zaman
dpi 2.451 regex, komut satırı, HDL backdoor erişimi
comps 1.499 driver, monitor, agent, env, test, scoreboard tabanı
dap 633 veri erişim politikaları (1.2'de yeni)
deprecated 234 geriye uyumluluk kalıntıları

Oranlar çok şey söylüyor. Çekirdek (base) kütüphanenin yarısına yakın; kullanıcının doğrudan miras aldığı comps ise 1.500 satırı bile bulmuyor. uvm_driver veya uvm_monitor neredeyse boş sınıflardır: asıl değer bileşenlerde değil, onları birbirine bağlayan altyapıdadır. Bu bilinçli bir seçim. Bileşen taban sınıfları bir metodoloji sözlüğü sağlar, davranışı dayatmaz.

Factory: dilin yapamadığını bir vekil sınıfla yapmak

Factory, UVM'in en iyi düşünülmüş parçası. SystemVerilog'da yansıtma (reflection) yoktur; bir sınıfı adıyla yaratamaz, new çağrısını da araya girerek değiştiremezsiniz. UVM bunu her sınıf için üretilen küçük bir vekil (proxy) sınıfla çözer: uvm_component_registry #(T, Tname).

Farka somut bakalım. Java, C#, Python ve Go gibi dillerde çalışma zamanı yansıtma vardır; bir sınıfı adını taşıyan bir string'den yaratabilirsiniz:

// Java: sınıf adı çalışma zamanında bir string, örneğin bir yapılandırma dosyasından
String name = "ErrDriver";
Driver d = (Driver) Class.forName(name).getDeclaredConstructor().newInstance();
# Python: sınıflar da birer nesnedir, ada göre bulunabilir
cls = globals()["ErrDriver"]
d = cls()

SystemVerilog'da bunun karşılığı yoktur. new her zaman derleme anında bilinen tipi yaratır. Ortam sürücüyü aşağıdaki gibi yaratırsa, test bu satıra dokunmadan başka bir sürücü koyamaz:

drv = new("drv", this);   // her zaman my_driver, test değiştiremez

UVM bu tabloyu elle kurar. Makro her sınıfa bir type_id vekili ekler; vekil kendini factory'nin string → vekil tablosuna kaydeder ve new çağrısı yalnızca vekilin içinde yapılır:

class my_driver extends uvm_driver #(my_item);
  `uvm_component_utils(my_driver)  // typedef uvm_component_registry #(my_driver,"my_driver") type_id;
endclass

class err_driver extends my_driver;
  `uvm_component_utils(err_driver)
endclass

// ortam: tipi factory'ye sorar
drv = my_driver::type_id::create("drv", this);

// test: ortama dokunmadan değiştirir
my_driver::type_id::set_type_override(err_driver::get_type());

// veya komut satırından, tamamen string ile:
// +uvm_set_type_override=my_driver,err_driver

Yani factory, dilin vermediği "adından sınıf bul ve yarat" yeteneğinin elle yazılmış, sınırlı bir yansıtma tablosudur. Yalnızca kayıtlı sınıflar için çalışır ve yalnızca yaratımı kapsar; alanları veya metotları ada göre sorgulama gibi tam yansıtma özellikleri yoktur.

class uvm_component_registry #(type T=uvm_component, string Tname="<unknown>")
                                           extends uvm_object_wrapper;
  virtual function uvm_component create_component (string name,
                                                   uvm_component parent);
    T obj;
    obj = new(name, parent);
    return obj;
  endfunction

  local static this_type me = get();

  static function this_type get();
    if (me == null) begin
      uvm_coreservice_t cs = uvm_coreservice_t::get();
      uvm_factory factory=cs.get_factory();
      me = new;
      factory.register(me);
    end
    return me;
  endfunction

Burada üç ince karar var:

  1. Statik ilklendirme ile kayıt. local static this_type me = get(); satırı, sınıfın factory'ye kaydını simülasyon başlamadan, elaborasyon sırasında yapar. Kullanıcı hiçbir kayıt fonksiyonu çağırmaz; `uvm_component_utils(my_driver) makrosu bu typedef'i (type_id) sınıfın içine koyar ve gerisi kendiliğinden olur.
  2. Tip güvenli yaratım. my_driver::type_id::create("drv", this) çağrısı factory'den bir nesne ister, sonra $cast ile beklenen tipe çevirir. Override uyumsuz bir tip döndürürse hata derleme anında değil ama ilk yaratımda, açık bir FCTTYP mesajıyla yakalanır.
  3. Bağlama duyarlı override. create çağrısı ebeveynin tam yolunu bağlam olarak taşır. Böylece aynı sınıf bir yerde orijinal, başka bir yerde türetilmiş haliyle yaratılabilir (set_inst_override). Test, ortamın kodunu değiştirmeden tek bir sürücüyü hata enjekte eden bir versiyonla değiştirebilir.

Parametreli sınıflar için `uvm_component_param_utils isim vermeden kaydeder. Bunun nedeni, parametreli bir sınıfın her özelleştirmesinin farklı bir tip olması ve tek bir string ada sahip olamamasıdır. Kütüphane burada esneklik yerine doğruluğu seçmiş: bu sınıflar yalnızca tip üzerinden override edilebilir.

1.2'nin katkısı: değiştirilebilir çekirdek servisler

1.1'de factory global bir uvm_pkg::factory değişkeniydi. 1.2 bunu soyut bir uvm_coreservice_t arkasına aldı. Factory, rapor sunucusu, varsayılan işlem veritabanı ve kök bileşen artık bu servisten istenir ve her biri set_* ile değiştirilebilir. Sürüm notlarındaki Mantis 3887 gerekçeyi açıkça yazıyor: factory'yi sarmalayıp create ve override çağrılarını izlemek ve hiç kullanılmayan override'ları bulmak. Mantis 4032 ile bir override'ı geri alma imkanı da geldi.

Bu, singleton'ı tamamen kaldırmadan test edilebilirliği geri kazanmanın pragmatik bir yolu. Global erişim noktası tek kaldı, ama arkasındaki uygulama artık kullanıcının.

Config DB: hiyerarşiyi öncelik kuralına çevirmek

uvm_config_db #(T) aslında ince bir cephedir; altında tip parametreli genel bir uvm_resource_db vardır. Bir ayarın anahtarı bir kapsam (örneğin env.agent*) ve bir alan adıdır. Kapsamlar glob olarak yazılır, içeride regex'e çevrilir ve DPI ile C tarafında eşleştirilir.

Asıl zekice kısım çakışmaların nasıl çözüldüğü. set çağrısı build fazında yapılıyorsa, kaynağın önceliği çağıran bileşenin derinliğine göre hesaplanır:

if(curr_phase != null && curr_phase.get_name() == "build")
  r.precedence = uvm_resource_base::default_precedence - (cntxt.get_depth());
else
  r.precedence = uvm_resource_base::default_precedence;

default_precedence 1000'dir. Testin derinliği 1, ortamın 2, ajanın 3 olduğu için testin ayarı 999 ile ajanın 997'sini yener. Sonuç basit bir kuraldır: build sırasında hiyerarşide yukarıda olan kazanır. Ortamın kendi varsayılanlarını koyması güvenlidir, çünkü test her zaman onları ezebilir. Build bittikten sonra herkes aynı öncelikle yazar ve son yazan kazanır.

Bu karar, "bileşen varsayılanı verir, test karar verir" ilkesini kullanıcıya hiçbir öncelik sayısı göstermeden uygular. Ayrıca wait_modified ile bir ayarın değişmesini bekleyebilirsiniz; yapılandırma aynı zamanda bir senkronizasyon aracı olur.

Fazlar: sabit bir liste değil, bir akış şeması

UVM fazlarını bir enum ya da sabit bir çağrı sırası olarak değil, düğümleri m_predecessors ve m_successors ile birbirine bağlanan bir akış şeması olarak modeller. Her faz bir kutu gibidir; oklar hangi fazın hangisinden sonra geleceğini söyler ve bir kutudan birden fazla ok çıkabilir. İki ayrı kavram bilinçli olarak ayrılmış:

  • Faz uygulaması (imp), örneğin uvm_build_phase, bir singleton'dır ve yalnızca "bu fazda bileşenin hangi metodunu çağır" bilgisini taşır: comp.build_phase(phase);.
  • Faz düğümü, bu uygulamanın bir zamanlama (schedule) içindeki bir örneğidir ve kendi durum makinesini taşır.

Gezinme yönü de ayrı sınıflarda: uvm_topdown_phase (build, final), uvm_bottomup_phase (connect, check, report vb.) ve uvm_task_phase (run ve 12 çalışma zamanı fazı). Task fazları her bileşen için fork ... join_none ile başlatılır ve faz bitince kalan thread'ler zorla öldürülür. Bu, kullanıcı kodunun sonsuz döngüler yazabilmesini mümkün kılar; bitirme kararı objection mekanizmasına bırakılır.

Bu akış şeması yaklaşımı üç şeyi bedavaya verir: kullanıcı tanımlı fazları araya eklemek (add ile before_phase/after_phase), farklı alt sistemleri ayrı domain'lerde çalıştırıp yalnızca istenen noktalarda sync etmek ve jump ile örneğin reset fazına geri dönmek. Her düğüm DORMANT, SCHEDULED, SYNCING, STARTED, EXECUTING, READY_TO_END, ENDED, CLEANUP/JUMPING, DONE durumlarından geçer. phase_ready_to_end geri çağrısı da bileşenlere "bitmek üzereyiz, son bir işin var mı?" diye sorar.

Çalışma zamanı fazları run ile paralel ikinci bir domain içinde akarcommon domain (uvm_domain.svh)aynı andabuildconnectend_of_elaborationstart_of_simulationrunextractcheckreportfinaltop-downbottom-upbottom-upbottom-uptask, zaman tüketirbottom-upbottom-upbottom-uptop-downuvm domain: 12 çalışma zamanı fazıpre_resetresetpost_resetpre_configureconfigurepost_configurepre_mainmainpost_mainpre_shutdownshutdownpost_shutdownjump() bu fazlardan birine, örneğin reset'e geri dönebilir;sync() ise iki domain'i yalnızca seçilen fazlarda hizalar.
UVM 1.2 varsayılan faz akışı · 9 ortak faz, 12 çalışma zamanı fazı

uvm_domain.svh dokuz ortak fazı sırayla ekler, sonra 12 çalışma zamanı fazını taşıyan zamanlamayı with_phase(run) ile run'a paralel bağlar.

Objection: dağıtık bir "henüz bitmedi" sayacı

Simülasyonun ne zaman biteceğine merkezi bir yer karar vermez. Her bileşen veya sequence raise_objection ile itiraz eder, işi bitince drop_objection ile geri çeker. Sayaçlar hiyerarşide yukarı doğru yayılır. Kökteki toplam sıfıra düşünce, isteğe bağlı bir drain_time beklenir ve faz biter.

Bu, gevşek bağlı bileşenlerin birbirini tanımadan ortak bir karar vermesini sağlar. 1.2, bu yayılmanın bir performans bedeli olduğunu kabul edip set_propagate_mode(0) ekledi (Mantis 3893). Bu modda ara düğümler atlanır, yalnızca kaynak ve kök sayacı güncellenir. Kaynak kodundaki açıklama bunu tablo ile gösteriyor:

                     | count | total |
uvm_top.parent.child |     1 |    1  |
uvm_top.parent       |     0 |    0  |
uvm_top              |     0 |    1  |

Sequence tarafında 1.2, starting_phase alanını doğrudan yazmayı kaldırıp set_automatic_phase_objection(1) ekledi. Sequence başlarken itirazı kendisi yükseltir, bitince düşürür. En sık yapılan hatalardan biri, unutulan drop_objection, böylece bir API kararıyla ortadan kalktı.

TLM: bağlantıyı arayüze, arayüzü porta taşımak

Bileşenler birbirini doğrudan çağırmaz; port, export ve imp nesneleri üzerinden konuşur. Port "bu işlemi istiyorum", imp "bu işlemi gerçekleştiriyorum" der, export ise hiyerarşi sınırından geçirir. Bağlantılar connect_phase sırasında kurulur, build bittikten sonra ve her bileşen var olduğunda.

uvm_analysis_port ise bir yayın kanalıdır. write çağrısı bağlı her abone için sırayla çağrılır ve bir fonksiyondur, yani zaman tüketemez. Monitör, kimin dinlediğini bilmeden scoreboard'a, kapsam toplayıcıya ve register tahmin edicisine aynı anda veri verebilir. Bu tek karar, monitor kodunun her projede yeniden kullanılabilmesinin temelidir.

Burada dilin bir sınırı da görünür. SystemVerilog 2012 öncesinde bir sınıf aynı isimli write metodunu iki kez uygulayamaz. Bir scoreboard iki analysis imp'e sahip olmak istediğinde UVM bir makro ile yeni bir sınıf üretir:

`define uvm_analysis_imp_decl(SFX) \
class uvm_analysis_imp``SFX #(type T=int, type IMP=int) \
  extends uvm_port_base #(uvm_tlm_if_base #(T,T)); \
  `UVM_IMP_COMMON(`UVM_TLM_ANALYSIS_MASK,`"uvm_analysis_imp``SFX`",IMP) \
  function void write( input T t); \
    m_imp.write``SFX( t); \
  endfunction \
endclass

`uvm_analysis_imp_decl(_exp) yazdığınızda write_exp metodunu çağıran bir imp sınıfı elde edersiniz. Şık değil ama dilin izin verdiği en temiz çözüm.

Sequence ve sequencer: uyarıyı sürücüden ayırmak

UVM'in belki de metodolojiye en çok katkı yapan kararı, uyarı üretimini (sequence) pin seviyesindeki sürümeden (driver) ayırmak. Sequence'lar bileşen değil, nesnedir; hiyerarşide yer kaplamaz, istediğiniz anda yaratılıp bir sequencer üzerinde başlatılır. Sürücü ise sabit bir el sıkışmasıyla ilerler: get_next_item, sür, item_done.

Sequencer, birden fazla sequence aynı sürücüye erişmek istediğinde hakem olur. Altı hakemlik modu (FIFO, WEIGHTED, RANDOM, STRICT_FIFO, STRICT_RANDOM, USER), öncelikler, lock ve grab ile bir kesme senaryosu kodun hiçbir yerine dokunmadan yazılabilir. Sanal sequence'lar birden fazla sequencer'ı koordine eder ve sistem seviyesindeki senaryolar böyle kurulur.

Raporlama: mesajı üretmek ile ne yapılacağına karar vermek ayrı işler

Bir `uvm_error çağrısı mesajın ne olacağına karar vermez. Mesaj sırayla report handler'a (bileşen bazında ayrıntı düzeyi ve eylem tablosu), report catcher'lara ve report server'a (sayma, biçimleme, sonlandırma) gider. uvm_report_catcher bir callback'tir ve catch() metodu THROW ya da CAUGHT döner. Bir test, başka bir ekibin VIP'sinden gelen beklenen bir hatayı kaynak koduna dokunmadan uyarıya çevirebilir veya yutabilir.

1.2 burada iki önemli adım attı. Mantis 3783 ile tüm mesajlar tek bir uvm_report_server üzerinden geçmeye başladı. Yeni uvm_report_message sınıfı da mesajı bir string olmaktan çıkarıp tamsayı, string ve nesne elemanları taşıyan yapılandırılmış bir nesneye dönüştürdü. Mesajlar artık bir veritabanına kaydedilebilir ve araçlar onları ayrıştırabilir.

Politika sınıfları ve şablon metot

uvm_object üzerindeki print, copy, compare, pack ve record metotları sanal değildir. Kullanıcı bunların yerine sanal do_print, do_copy, do_compare ve benzerlerini ezer:

extern function void print (uvm_printer printer=null);
extern virtual function void do_print (uvm_printer printer);
extern function bit compare (uvm_object rhs, uvm_comparer comparer=null);
extern virtual function bit do_compare (uvm_object rhs, uvm_comparer comparer);

Bu klasik şablon metot deseni. Kütüphane dış kabuğu (null kontrolleri, özyineleme derinliği, alan makrolarının çağrılması) kendi elinde tutar; kullanıcı yalnızca değişen kısmı yazar. "Nasıl" sorusu da ayrı nesnelere bırakılmış: uvm_table_printer, uvm_tree_printer ve uvm_line_printer aynı nesneyi farklı biçimlerde basar, uvm_comparer ise kaç uyumsuzluğun raporlanacağını belirler. Nesne veri modelini, politika nesnesi sunumu taşır.

1.2'deki uvm_tr_database, uvm_tr_stream ve uvm_recorder üçlüsü aynı fikri işlem kaydına uygular. Kayıt artık simülatöre özgü bir arka uca sıkışık değildir; varsayılan uvm_text_tr_database yerine kendi veritabanınızı koyabilirsiniz.

Register katmanı: modeli veri yolundan ayıran adaptör

reg dizini kütüphanenin ikinci büyük parçası (21.668 satır). Temel karar, register modelinin hangi veri yolu üzerinden erişildiğini hiç bilmemesi. Model soyut bir uvm_reg_bus_op üretir; protokole özgü çeviriyi yalnızca iki metodu olan bir adaptör yapar:

pure virtual function uvm_sequence_item reg2bus(const ref uvm_reg_bus_op rw);
pure virtual function void bus2reg(uvm_sequence_item bus_item,
                                   ref uvm_reg_bus_op rw);

Aynı register modeli APB üzerinden de AXI üzerinden de kullanılabilir. Tahmin (prediction) da ayrı bir bileşen: uvm_reg_predictor, bir analysis port'u dinleyerek modelin aynasını günceller ve böylece testin yapmadığı erişimleri de görür. Frontdoor (gerçek veri yolu) ve backdoor (DPI ile HDL yoluna doğrudan erişim) aynı read/write API'sinin arkasında bir parametre olarak seçilir.

Veri erişim politikaları (DAP)

1.2'nin daha az bilinen yeniliklerinden biri dap dizini: uvm_set_before_get_dap, uvm_get_to_lock_dap ve uvm_simple_lock_dap. Bunlar bir değerin ne zaman okunup yazılabileceğini kodlayan küçük sarmalayıcılardır. Örneğin bir sequence'ın başlangıç fazı artık bir uvm_get_to_lock_dap#(uvm_phase) içinde tutulur: bir kez okunduktan sonra kilitlenir ve sonradan yazılmaya çalışılırsa hata verir. Eski genel starting_phase alanının yarattığı yarış koşulları böyle bir tip ile kapatılmış.

Ödünleşimler: kaynak kodun da itiraf ettiği bedeller

Her iyi karar bir bedelle geldi ve bunların bir kısmı koda bakınca açıkça görünüyor.

  • *Alan makroları (`uvm_field_*).** macros/uvm_object_defines.svh tek başına 3.814 satır. Bu makrolar print, copy, compare ve pack için kodu otomatik üretir, ama açıldıklarında devasa bir case yapısı oluşur. Hata ayıklaması zordur ve elle yazılmış `do_` metotlarından belirgin şekilde yavaştır. Şablon metot deseni zaten doğru yolu sunuyordu; makrolar kısa yoldu ve pek çok ekip sonradan bu yoldan vazgeçti.
  • String tabanlı yapılandırma. Kapsam ve alan adları string olduğu için derleyici hiçbir yazım hatasını yakalamaz. Yanlış yazılmış bir uvm_config_db::get yalnızca 0 döner. Kütüphanede bir uvm_spell_chkr bile var ve kaynak havuzu en yakın isimleri önerebiliyor, ama bu yalnızca rpterr bayrağıyla yapılan aramalarda devreye giriyor; tipik get yolunda sessiz kalıyor.
  • Global durum. Coreservice, factory, uvm_root ve faz imp'leri singleton. 1.2 bunları değiştirilebilir yaptı, ama hepsi hala tek ve statik. Aynı simülasyonda iki bağımsız test çalıştırmak ya da kütüphaneyi birim test etmek zor.
  • Çalışma zamanı fazları. 12 ince taneli faz, domain'ler ve jump kuvvetli ama karmaşık. Bit maskesi olarak kodlanmış 11 faz durumu ve phase_ready_to_end yinelemeleri bunu gösteriyor. Pratikte çoğu ekip yalnızca run_phase kullanıp akışı sequence'larla yönetiyor.
  • Geriye uyumluluk yükü. Kaynakta 33 yerde UVM_NO_DEPRECATED koruması var. 1.2 hem eski API'yi taşıyor hem de bazı noktalarda bilinçli olarak kırıyor: set_config_* (Mantis 3472) ve global factory değişkeni (Mantis 3887) yeni sürümde geçiş maliyeti yarattı.
  • Objection performansı. Hiyerarşik yayılım her raise ve drop için köke kadar yürür. 1.2'nin set_propagate_mode eklemesi, ekibin bu bedeli ölçtüğünü ve kabul ettiğini gösteriyor.

Bu bedellerin çoğu tasarım hatası değil, SystemVerilog'un sınırlarının bir yansıması. Yansıtma olmayan, çoklu arayüz uygulaması 2012'ye kadar gelmeyen ve makro sisteminin C ön işlemcisi seviyesinde kaldığı bir dilde, ekip çoğu zaman en az kötü seçeneği seçti.

Sonuç: kütüphane tasarımcıları için dersler

UVM 1.2'nin kaynağını okuyunca ortaya çıkan ana ders şu: kalıcı olan özellikler değil, değişim noktalarının doğru yere konması. Factory, nesne yaratımını değişim noktası yaptı. Config DB yapılandırmayı, analysis port veri akışını, report catcher mesajları, adaptör de veri yolunu değişim noktasına çevirdi. Hepsinde ortak fikir aynı: test, ortamın kodunu değiştirmeden onun davranışını değiştirebilmeli.

Kendi kütüphanenizi tasarlarken UVM'den alabileceğiniz somut desenler:

  1. Dilin eksik olduğu yerde küçük, üretilmiş bir vekil sınıf, kullanıcıya yüklenen bir kayıt adımından iyidir (type_id).
  2. Çakışma kuralını yapının kendisinden türetin (derinlikten öncelik), kullanıcıya sayı seçtirmeyin.
  3. Adımları sabit bir sıra yerine oklarla bağlanan bir akış şeması olarak modelleyin; genişleme noktaları bedavaya gelir.
  4. Dış kabuğu sanal olmayan metotta tutun, kullanıcıya yalnızca do_* kancasını açın.
  5. Global erişim noktası gerekiyorsa, arkasındaki uygulamayı değiştirilebilir yapın (coreservice).
  6. Bir yanlış kullanım sık tekrarlanıyorsa, belgelemek yerine API ile imkansız kılın (otomatik faz itirazı, DAP).

Ve dikkat edilmesi gereken taraf: makrolarla üretilen kolaylık, sonradan bakım ve performans borcu olarak geri döner. UVM 1.2'nin en çok eleştirilen yanları tam da kısa yol sunduğu yerlerdir.

Bir sonraki yazı: UVM Python ile yazılsaydı?

Bu yazıdaki kararların çoğu, SystemVerilog'un eksiklerine verilmiş cevaplardı. Peki o eksikler hiç olmasaydı? Python'da sınıflar zaten birer nesne ve ada göre bulunabiliyor; factory belki bir sözlükten ibaret kalırdı. Makroların yerini dekoratörler alabilir, uvm_analysis_imp_decl gibi çözümlere hiç gerek kalmayabilirdi. Zaman tüketen fazlar ise async/await ile bambaşka bir görünüme kavuşabilirdi.

Ama her kazanımın bir bedeli olur. Derleme anındaki tip kontrolünün kaybı, simülatörle konuşmanın maliyeti ve performans da hesaba katılmalı. Bir sonraki yazımızda UVM'in Python ile yazılmış olsaydı nasıl görüneceğini inceleyeceğiz. Hangi tasarım kararları gereksiz hale gelirdi, hangileri dilden bağımsız olarak yine doğru kalırdı?

Kaynaklar