SystemVerilog'ta Makro Kullanımı
Ek Konu: Önişlemci ve Makrolar |
define,ifdef, parametreli ve çok satırlı makrolar, koşullu derleme
Bu derste SystemVerilog önişlemcisini (preprocessor) öğreneceğiz: kodunuz derlenmeden hemen önce çalışan ve metin düzeyinde değiştirme yapan bu katman, sabit tanımlamaktan koşullu derlemeye kadar birçok işte kullanılır. UVM'in `uvm_info, `uvm_component_utils gibi tüm "ters tırnaklı" yapıları da birer makrodur; bu yüzden makro mantığını anlamak UVM'e geçişin ön koşuludur.
Önişlemci Nedir?
Derleyici kodunuzu okumaya başlamadan önce önişlemci metni tarar, ters tırnak (`) ile başlayan direktifleri bulur ve bunları metin değiştirme kurallarına göre işler. Sonuçta ortaya çıkan düz SystemVerilog metni derleyiciye gider. Önemli sonuçlar:
- Makrolar tip bilmez, kapsam (scope) bilmez; yalnızca karakter dizisi değiştirir.
- Bir
`definetanımlandığı noktadan itibaren, o derleme biriminin sonuna kadar (ve aynı derleme komutundaki sonraki dosyalarda) geçerlidir; modül sınırlarını tanımaz. - Makro adları büyük/küçük harfe duyarlıdır ve geleneksel olarak BÜYÜK_HARF yazılır.
Benzetme: Makro, kelime işlemcideki "bul ve değiştir" komutudur. Belgenizi yazıcıya göndermeden (derlemeden) hemen önce her
`DATA_WIDTHyazan yere8yazılır. Yazıcı hiçbir zaman`DATA_WIDTH'i görmez; o yüzden "bu bir sayı mı, string mi?" diye de sormaz.
Temel Direktifler
| Direktif | Görevi | Örnek |
|---|---|---|
`define AD değer |
Sabit / kod parçası tanımla | `define OP_ADD 2'b00 |
`define AD(a,b) ... |
Parametreli (fonksiyon benzeri) makro | `define MAX(a,b) (((a)>(b))?(a):(b)) |
`undef AD |
Tanımı sil | `undef TEMP |
`ifdef AD / `ifndef AD |
Tanımlıysa / tanımlı değilse derle | — |
`elsif AD / `else / `endif |
Koşullu derleme dalları | — |
`include "dosya" |
Dosya içeriğini o noktaya yapıştır | `include "uvm_macros.svh" |
`__FILE__ / `__LINE__ |
O anki dosya adı / satır numarası | Hata mesajlarında |
`timescale 1ns/1ps |
Zaman birimi / çözünürlük | Dosya başında |
`default_nettype none |
Örtük wire oluşturmayı kapat |
Yazım hatalarını yakalar |
Sabit Tanımlama (`define ve `undef)
Koddaki "sihirli sayıları" (magic numbers) isimlendirmek için kullanılır. `undef ise daha önce tanımlanmış bir makroyu siler; aynı adı başka bir dosyanın farklı anlamda kullanmasına izin verir.
Varsayılan Değer (`ifndef)
"Tanımlı değilse" demektir. Bir makro komut satırından (+define+DATA_WIDTH=16) gelmemişse varsayılan bir değer atamak için kullanılır; kodun farklı projelerde değişiklik yapılmadan çalışmasını sağlar.
Parametreli Makrolar
Argüman alarak fonksiyon gibi davranırlar ama metin düzeyinde çalışırlar. Her argümanı ve sonucu parantez içine almak şarttır; aksi halde işlem önceliği hataları sinsice ortaya çıkar (`define KARE(x) x*x ile `KARE(a+1) → a+1*a+1).
Çok Satırlı Makrolar
Makrolar normalde tek satırlıktır. Satır sonuna ters bölü (\) koyarak alt satıra uzatabilirsiniz. Ters bölüden sonra hiçbir karakter (boşluk bile) olmamalıdır; en sık yapılan hata budur.
Koşullu Derleme (`ifdef / `elsif / `else / `endif)
Bir makronun tanımlı olup olmadığına göre kod bloklarını derlemeye dahil eder veya çıkarır. Simülasyon/sentez kodunu ayırmak, hata ayıklama mesajlarını açıp kapamak, farklı çip varyantlarını tek dosyada tutmak için kullanılır. Derlenmeyen blok derleyici tarafından hiç görülmez; içinde söz dizimi hatası olsa bile fark edilmez.
Kaynak Kod
Aşağıdaki örnek, yukarıdaki tüm makro türlerini küçük bir ALU tasarımı ve testbench'inde birleştirir.
// ============================================================================
// MAKRO TANIMLARI
// ============================================================================
// 1. Konfigurasyon makrolari (davranisi degistirmek icin yorumlayin/acin)
`define ENABLE_DEBUG // Hata ayiklama mesajlarini ac
`define ADVANCED_MODE // ALU'yu gelismis mimariye gecir
// 2. `ifndef ile varsayilan deger
// DATA_WIDTH disaridan (+define+DATA_WIDTH=16) verilmemisse 8 olsun
`ifndef DATA_WIDTH
`define DATA_WIDTH 8
`endif
// 3. Basit sabitler: opcode'lar
`define OP_ADD 2'b00
`define OP_SUB 2'b01
`define OP_MAX 2'b10
`define OP_MIN 2'b11
// 4. Parametreli makro (tek satir) - her argüman parantez icinde!
`define GET_MAX(a, b) (((a) > (b)) ? (a) : (b))
`define GET_MIN(a, b) (((a) < (b)) ? (a) : (b))
// 5. Parametreli, COK SATIRLI makro - satir sonlarinda '\' zorunlu,
// '\' karakterinden sonra bosluk bile olmamali
`define LOG_RESULT(op_name, val_a, val_b, val_res) \
`ifdef ENABLE_DEBUG \
$display("[%0t] DEBUG -> OP: %s | A: %3d | B: %3d | Sonuc: %3d", \
$time, op_name, val_a, val_b, val_res); \
`endif
// ============================================================================
// DONANIM MODULU: Kapsamli ALU
// ============================================================================
module comprehensive_alu (
input logic [`DATA_WIDTH-1:0] in_a,
input logic [`DATA_WIDTH-1:0] in_b,
input logic [1:0] opcode,
output logic [`DATA_WIDTH-1:0] out_res
);
always_comb begin
out_res = '0; // Latch olusmasin diye varsayilan
// 6. Kosullu derleme
`ifdef ADVANCED_MODE
// --- GELISMIS MIMARI: ADD, SUB, MAX, MIN ---
case (opcode)
`OP_ADD: begin
out_res = in_a + in_b;
`LOG_RESULT("ADD", in_a, in_b, out_res)
end
`OP_SUB: begin
out_res = in_a - in_b;
`LOG_RESULT("SUB", in_a, in_b, out_res)
end
`OP_MAX: begin
out_res = `GET_MAX(in_a, in_b); // Parametreli makro
`LOG_RESULT("MAX", in_a, in_b, out_res)
end
`OP_MIN: begin
out_res = `GET_MIN(in_a, in_b);
`LOG_RESULT("MIN", in_a, in_b, out_res)
end
endcase
`elsif BASIC_MODE
// --- TEMEL MIMARI: yalnizca ADD ve SUB ---
case (opcode)
`OP_ADD: out_res = in_a + in_b;
`OP_SUB: out_res = in_a - in_b;
default: out_res = '0; // MAX ve MIN desteklenmez
endcase
`LOG_RESULT("BASIC_OP", in_a, in_b, out_res)
`else
// --- YEDEK MIMARI: hic mod secilmediyse giris A dogrudan cikisa ---
out_res = in_a;
`endif
end
endmodule
// ============================================================================
// TESTBENCH
// ============================================================================
module tb_macro_lab;
logic [`DATA_WIDTH-1:0] tb_a;
logic [`DATA_WIDTH-1:0] tb_b;
logic [1:0] tb_op;
logic [`DATA_WIDTH-1:0] tb_res;
comprehensive_alu dut (
.in_a (tb_a),
.in_b (tb_b),
.opcode (tb_op),
.out_res(tb_res)
);
// 7. `undef: makroyu bu noktadan sonra gecersiz kil
`define TEMP_TEST_VAL 8'd42
logic [7:0] dummy_var = `TEMP_TEST_VAL;
`undef TEMP_TEST_VAL
// `TEMP_TEST_VAL bu satirdan sonra taninmaz; kullanilsa derleme hatasi verir
initial begin
$display("--- SIMULASYON BASLADI ---");
$display("Veri genisligi: %0d bit (dosya: %s, satir: %0d)",
`DATA_WIDTH, `__FILE__, `__LINE__);
#10 tb_a = 8'd50; tb_b = 8'd25; tb_op = `OP_ADD;
#10 tb_a = 8'd50; tb_b = 8'd25; tb_op = `OP_SUB;
#10 tb_a = 8'd15; tb_b = 8'd80; tb_op = `OP_MAX;
#10 tb_a = 8'd15; tb_b = 8'd80; tb_op = `OP_MIN;
#10;
$display("undef oncesi atanan dummy_var = %0d", dummy_var);
$display("--- SIMULASYON BITTI ---");
$finish;
end
endmodule
Kodun Açıklaması
- Konfigürasyon makroları:
`define ENABLE_DEBUGve`define ADVANCED_MODEdeğer taşımaz; yalnızca "tanımlı" olmaları anlamlıdır.`ifdefile sorgulanırlar. - **
`ifndef DATA_WIDTH:** Komut satırından+define+DATA_WIDTH=16verilmemişse8atanır. Testbench çalıştığında "Veri genisligi: 8 bit" görürsünüz; aynı kodu 16-bit için değiştirmeden derleyebilirsiniz. - Opcode sabitleri:
`OP_ADDgibi makrolarcaseetiketlerinde ve testbench'te kullanılır;2'b00yazmak yerine isim kullanmak okunabilirliği artırır. (Gerçek projelerde bu iş içinenumtercih edilir; aşağıdaki karşılaştırma tablosuna bakın.) - **
`GET_MAX(a, b):** Metin düzeyinde(((in_a) > (in_b)) ? (in_a) : (in_b))ifadesine dönüşür. Her argümanın parantezli olması,`GET_MAX(x+1, y)gibi çağrılarda önceliğin bozulmamasını sağlar. - **
`LOG_RESULT(...):** Çok satırlı ve kendi içinde`ifdefbarındıran bir makro.ENABLE_DEBUGtanımlıysa bir$displayüretir; tanımlı değilse hiçbir koda dönüşmez. Satır sonlarındaki\karakterleri makroyu alt satırlara taşır. Makro çağrısının sonunda;olmamasına dikkat edin: noktalı virgül makronun içindedir. - Koşullu mimari:
ADVANCED_MODEtanımlı olduğu içincasebloğunun dört işlemli sürümü derlenir.ADVANCED_MODEsatırını yorumlayıp`define BASIC_MODEeklerseniz yalnızca ADD/SUB kalır; ikisi de tanımlı değilse`elsedalındaki yedek mimari derlenir. - **
`undef:**TEMP_TEST_VALtanımlanır,dummy_var'a atanır ve hemen silinir.dummy_vardeğerini korur çünkü atama derleme anında yapılmıştır; ama bu satırdan sonra`TEMP_TEST_VALyazmak derleme hatasıdır. `__FILE__ve`__LINE__: Önişlemci bunları o anki dosya adı ve satır numarasıyla değiştirir; UVM'in`uvm_infomakrosu hata mesajlarına dosya/satır bilgisini bu yolla ekler.
Makro mu, Parametre mi, Enum mu?
Makro her sorunun cevabı değildir. SystemVerilog, çoğu durumda daha güvenli alternatifler sunar:
| İhtiyaç | Makro | Daha iyi alternatif | Neden? |
|---|---|---|---|
| Veri yolu genişliği | `define DW 8 |
parameter int DW = 8 |
Modül bazında farklı değer alabilir, tip denetimi vardır |
| Opcode / durum sabitleri | `define OP_ADD 2'b00 |
typedef enum logic [1:0] {OP_ADD, ...} |
.name() ile log, geçersiz değere kapalı |
| Sınıf içi sabit | `define MAX_LEN 64 |
localparam veya static const int |
Kapsamı sınıf/modülle sınırlı |
| Basit hesap | `define MAX(a,b) |
function automatic int max(...) |
Öncelik hatası yok, hata ayıklanabilir |
| Hata ayıklama kodunu açıp kapama | `ifdef DEBUG |
— | Makro doğru seçim: kod tamamen derlemeden çıkar |
| Tekrarlı kalıp (UVM utils, field macros) | `uvm_object_utils(...) |
— | Makro doğru seçim: metin çoğaltması gerekir |
| Dosya/satır bilgisi | `__FILE__ / `__LINE__ |
— | Yalnızca makro ile mümkün |
| Derleme zamanı koşulu | `ifdef |
generate if (parametre tabanlı) |
generate elaboration'da çalışır, modül başına farklılaşabilir |
Pratik kural: Değer tanımlıyorsanız parameter/localparam/enum, kod varlığını kontrol ediyorsanız `ifdef, metin çoğaltıyorsanız makro kullanın.
UVM'de Makrolar
UVM'e geçtiğinizde her sınıfta göreceğiniz yapılar birer makrodur:
`include "uvm_macros.svh" // Tum UVM makrolarini tanimlar
import uvm_pkg::*;
class my_item extends uvm_sequence_item;
`uvm_object_utils(my_item) // Factory kaydi + yardimci metodlar (metin cogaltma)
...
endclass
`uvm_info("ID", "mesaj", UVM_LOW) // Icinde `__FILE__, `__LINE__ ve verbosity kontrolu var
`uvm_error("ID", "hata")
`uvm_info'nun bir makro olması sayesinde mesaj metni yalnızca verbosity eşiği geçildiğinde **hesaplanır**; bir fonksiyon olsaydı $sformatf her çağrıda çalışır ve performans düşerdi. `include "uvm_macros.svh" satırını unutmak, UVM'e yeni başlayanların aldığı ilk derleme hatasıdır.
Önemli Noktalar
- Makrolar metin değiştirir; tip, kapsam ve öncelik bilmez. Parametreli makrolarda her argümanı ve tüm ifadeyi parantezleyin.
- Çok satırlı makrolarda
\satırın son karakteri olmalıdır; arkasındaki görünmez bir boşluk makroyu orada bitirir ve anlaşılmaz derleme hataları üretir. `definedosya sınırı tanımaz: bir dosyada tanımlanan makro, aynı derleme komutundaki sonraki tüm dosyalarda görünür. Ad çakışmalarını önlemek için proje öneki kullanın (`MYPROJ_DATA_WIDTH) ve gerekirse`undefile temizleyin.`ifdefile derlemeden çıkarılan kod hiç denetlenmez; uzun süre kapalı kalan bloklar "çürür". Her konfigürasyonu regresyonda en az bir kez derleyin.- Derleme komutundan makro vermek (
+define+ENABLE_DEBUG,+define+DATA_WIDTH=16) kod değiştirmeden konfigürasyon seçmenin standart yoludur;`ifndefile varsayılan değer bu akışı tamamlar. - Tasarım kodunda
$displaymakro ile sarılı olsa bile sentez araçları zaten göz ardı eder; ama simülasyon hızı içinENABLE_DEBUG'ı regresyonlarda kapalı tutun.
Sık Yapılan Hatalar
**
\sonrası boşluk:** Çok satırlı makro ilk satırda biter; derleyici "beklenmeyen`ifdef" gibi ilgisiz hatalar verir.Parantezsiz argüman:
`define DOUBLE(x) x*2ile`DOUBLE(a+b)→a+b*2. Doğrusu((x)*2).Makro çağrısından sonra fazladan
;: Makro zaten;ile bitiyorsa`LOG_RESULT(...);iki noktalı virgül üretir; çoğu yerde zararsız amaifdallarındaelse'i koparır.`includeile aynı dosyayı iki kez dahil etmek: Sınıf iki kez tanımlanır → hata. Dosya başına include guard koyun:`ifndef MY_PKG_SVH `define MY_PKG_SVH ... dosya icerigi ... `endifMakroyu
enumyerine kullanmak:`OP_ADDdeğerini%sile yazdıramazsınız;enum.name()sunar. Tasarımın sözleşmesini oluşturan sabitler içinenum/parameterkullanın.**
`ifdefiçinde yazım hatası:**`ifdef ENABLE_DEBUGG(çift G) sessizce "tanımlı değil" sayılır; kod derlenmez ve siz fark etmezsiniz. Simülatörün "tanımsız makro" uyarılarını açık tutun.
Kendinizi Deneyin
`LOG_RESULTmakrosunun ilk satırındaki\'den sonra bir boşluk ekleyin ve derleyin. Aldığınız hata mesajını not alın; bu mesajı tanımak ileride çok zaman kazandırır.`define ADVANCED_MODEsatırını yorumlayıp`define BASIC_MODEekleyin.MAXveMINişlemlerinin çıktısı ne oldu? Her ikisini de kaldırırsanız?- Derleme komutuna
+define+DATA_WIDTH=16ekleyin (EDA Playground'da "Compile Options" alanı).`ifndefbloğu devre dışı kaldı mı? Testbench'teki8'd50sabitlerinin 16-bit sinyale atanması neden sorun çıkarmadı? `define KARE(x) x*xyazıp`KARE(2+3)değerini yazdırın (beklenen 25, gerçek ?). Sonra((x)*(x))ile düzeltin.- Opcode makrolarını bir
typedef enum logic [1:0] {OP_ADD, OP_SUB, OP_MAX, OP_MIN} op_e;ile değiştirin ve$display'de.name()kullanın. Hangi yaklaşım daha okunaklı çıktı verdi?
Hızlı Kontrol: Çok satırlı bir makroda satır sonundaki \ karakterinden sonra boşluk olursa ne olur?
Ters bölü, kendisinden sonra gelen karakteri (boşluğu) "kaçırır", satır sonunu değil. Makro o satırda biter; sonraki satırlar sıradan kod olarak derlenir ve genellikle ilgisiz görünen söz dizimi hataları ortaya çıkar. \ her zaman satırın son karakteri olmalıdır.