EDA Playground'da Dene

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 `define tanı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_WIDTH yazan yere 8 yazı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_DEBUG ve `define ADVANCED_MODE değer taşımaz; yalnızca "tanımlı" olmaları anlamlıdır. `ifdef ile sorgulanırlar.
  • **`ifndef DATA_WIDTH:** Komut satırından +define+DATA_WIDTH=16 verilmemişse 8 atanı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_ADD gibi makrolar case etiketlerinde ve testbench'te kullanılır; 2'b00 yazmak yerine isim kullanmak okunabilirliği artırır. (Gerçek projelerde bu iş için enum tercih 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 `ifdef barındıran bir makro. ENABLE_DEBUG tanı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_MODE tanımlı olduğu için case bloğunun dört işlemli sürümü derlenir. ADVANCED_MODE satırını yorumlayıp `define BASIC_MODE eklerseniz yalnızca ADD/SUB kalır; ikisi de tanımlı değilse `else dalındaki yedek mimari derlenir.
  • **`undef:** TEMP_TEST_VAL tanımlanır, dummy_var'a atanır ve hemen silinir. dummy_var değerini korur çünkü atama derleme anında yapılmıştır; ama bu satırdan sonra `TEMP_TEST_VAL yazmak 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_info makrosu 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.
  • `define dosya 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 `undef ile temizleyin.
  • `ifdef ile 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; `ifndef ile varsayılan değer bu akışı tamamlar.
  • Tasarım kodunda $display makro ile sarılı olsa bile sentez araçları zaten göz ardı eder; ama simülasyon hızı için ENABLE_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*2 ile `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 ama if dallarında else'i koparır.

  • `include ile 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 ...
    `endif
    
  • Makroyu enum yerine kullanmak: `OP_ADD değerini %s ile yazdıramazsınız; enum .name() sunar. Tasarımın sözleşmesini oluşturan sabitler için enum/parameter kullanın.

  • **`ifdef iç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

  1. `LOG_RESULT makrosunun 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.
  2. `define ADVANCED_MODE satırını yorumlayıp `define BASIC_MODE ekleyin. MAX ve MIN işlemlerinin çıktısı ne oldu? Her ikisini de kaldırırsanız?
  3. Derleme komutuna +define+DATA_WIDTH=16 ekleyin (EDA Playground'da "Compile Options" alanı). `ifndef bloğu devre dışı kaldı mı? Testbench'teki 8'd50 sabitlerinin 16-bit sinyale atanması neden sorun çıkarmadı?
  4. `define KARE(x) x*x yazıp `KARE(2+3) değerini yazdırın (beklenen 25, gerçek ?). Sonra ((x)*(x)) ile düzeltin.
  5. 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.