EDA Playground'da Dene

typedef, enum ve struct

Gün 1: SystemVerilog'a Giriş ve Veri Tipleri | Kullanıcı tanımlı tipler: typedef, enum, struct, union

Bu derste kendi veri tiplerimizi nasıl tanımlayacağımızı öğreniyoruz: typedef ile takma adlar, enum ile anlamlı isimli sabitler, struct ile ilişkili verilerin gruplanması ve union ile aynı belleğin farklı görünümleri.

typedef: Tiplere İsim Vermek

typedef, mevcut bir tipe yeni bir ad vererek kodu hem okunabilir hem de bakımı kolay hâle getirir. Örneğin bir adres tipini her yerde logic [15:0] yazmak yerine addr_t olarak tanımlarsanız, genişliği değiştirmeniz gerektiğinde tek bir yeri düzenlemeniz yeter.

enum: Anlamlı Sabitler

enum, bir değişkenin alabileceği değerleri isimli sabitlerle sınırlar. Durum makineleri (FSM) ve opcode'lar için idealdir; 2'b01 yerine READ yazmak hem okunur hem de hataya kapalıdır. Enum tipleri zengin yerleşik metodlar sunar:

  • .name() → değerin metin karşılığını verir
  • .first() / .last() → ilk ve son elemanı verir
  • .next() / .prev() → komşu elemanlara geçer
  • .num() → eleman sayısını verir

struct ve packed struct

struct, farklı tiplerdeki ilişkili alanları tek bir yapıda toplar (örneğin bir transaction'ın adres, veri ve geçerlilik bilgisi). Varsayılan struct unpacked'tir. struct packed ise alanları bitişik bitler halinde saklar; tek bir vektör gibi davrandığı için sentezlenebilir ve bit düzeyinde erişilebilir.

union: Aynı Belleğin Farklı Görünümleri

union packed, aynı bellek alanını farklı tipler üzerinden yorumlamaya izin verir; örneğin 16-bit bir değere hem tek vektör hem de iki ayrı byte olarak erişmek gibi.

Kaynak Kod

// =============================================================================
// GUN 1 - Konu 4: Kullanici Tanimli Tipler: typedef, enum, struct
// =============================================================================

module typedef_enum_struct;

  // -------------------------------------------------------------------------
  // TYPEDEF: Mevcut tiplere yeni isim verme
  // -------------------------------------------------------------------------
  typedef logic [7:0]  byte_t;
  typedef logic [31:0] word_t;
  typedef logic [15:0] addr_t;

  // -------------------------------------------------------------------------
  // ENUM: Numaralandirilmis tipler
  // -------------------------------------------------------------------------
  typedef enum logic [1:0] {
    IDLE   = 2'b00,
    READ   = 2'b01,
    WRITE  = 2'b10,
    ERROR  = 2'b11
  } state_e;

  typedef enum logic [2:0] {
    ADD, SUB, AND, OR, XOR, NOT, SHL, SHR
  } opcode_e;

  // -------------------------------------------------------------------------
  // STRUCT: Yapilar - iliskili verileri gruplar
  // -------------------------------------------------------------------------
  typedef struct {
    addr_t     addr;
    word_t     data;
    opcode_e   op;
    bit        valid;
  } transaction_t;

  // PACKED STRUCT: Bitler ardisik saklanir, sentezlenebilir
  typedef struct packed {
    logic [3:0] tag;
    logic [7:0] payload;
    logic       parity;
  } packet_t;

  // -------------------------------------------------------------------------
  // UNION: Ayni bellegi paylasan tipler
  // -------------------------------------------------------------------------
  typedef union packed {
    logic [15:0] word;
    struct packed {
      logic [7:0] high;
      logic [7:0] low;
    } bytes;
  } reg16_t;

  // Degisken tanimlamalari
  state_e       current_state;
  opcode_e      alu_op;
  transaction_t txn;
  packet_t      pkt;
  reg16_t       register_val;

  initial begin
    $display("=== typedef, enum, struct Ornekleri ===\n");

    // --- ENUM kullanimi ---
    current_state = IDLE;
    $display("[ENUM] Durum  = %s (deger=%0b)", current_state.name(), current_state);
    
    current_state = READ;
    $display("[ENUM] Durum  = %s (deger=%0b)", current_state.name(), current_state);

    // Enum metodlari
    alu_op = ADD;
    $display("\n[ENUM Metodlari]");
    $display("  Ilk    = %s", alu_op.first().name());
    $display("  Son    = %s", alu_op.last().name());
    $display("  Sonraki= %s", alu_op.next().name());
    $display("  Eleman = %0d", alu_op.num());

    // --- STRUCT kullanimi ---
    txn.addr  = 16'hABCD;
    txn.data  = 32'hDEAD_BEEF;
    txn.op    = ADD;
    txn.valid = 1;
    $display("\n[STRUCT] Transaction:");
    $display("  Adres = 0x%04h", txn.addr);
    $display("  Veri  = 0x%08h", txn.data);
    $display("  Islem = %s", txn.op.name());
    $display("  Gecerli = %0b", txn.valid);

    // Struct literal (toplu atama)
    txn = '{addr: 16'h0010, data: 32'hCAFE_BABE, op: SUB, valid: 1};
    $display("\n[STRUCT Literal] Transaction:");
    $display("  Adres = 0x%04h, Veri = 0x%08h", txn.addr, txn.data);

    // --- PACKED STRUCT ---
    pkt = '{tag: 4'hA, payload: 8'hFF, parity: 1'b1};
    $display("\n[PACKED STRUCT] Paket:");
    $display("  Toplam bit = %0d", $bits(pkt));
    $display("  Bit dizisi = %013b", pkt);
    $display("  Tag=%h, Payload=%h, Parity=%b", pkt.tag, pkt.payload, pkt.parity);

    // --- UNION ---
    register_val.word = 16'hA5F0;
    $display("\n[UNION] Register:");
    $display("  Word = 0x%04h", register_val.word);
    $display("  High = 0x%02h, Low = 0x%02h", register_val.bytes.high, register_val.bytes.low);

    $display("\n=== typedef, enum, struct Sonu ===");
    $finish;
  end

endmodule

Kodun Açıklaması

  • typedef logic [7:0] byte_t;, word_t ve addr_t satırları sık kullanılan genişlikler için takma adlar üretir. Bu adlar daha sonra transaction_t içinde alan tipi olarak kullanılır.
  • state_e enum'u IDLE, READ, WRITE, ERROR durumlarını logic [1:0] tabanında açık değerlerle (2'b00...2'b11) tanımlar. opcode_e ise değer atamadan tanımlandığı için elemanlar 0'dan başlayarak otomatik numaralandırılır (ADD=0, SUB=1 ...).
  • transaction_t bir unpacked struct'tır ve addr, data, op, valid alanlarını birleştirir. packet_t ise struct packed olduğu için tag, payload, parity alanları toplam 13 bit olarak bitişik saklanır.
  • reg16_t bir union packed'tir: word (16-bit) ile içteki bytes yapısının high/low alanları aynı belleği paylaşır.
  • initial bloğunda önce current_state = IDLE; ve READ atanır, current_state.name() ile durumun ismi yazdırılır.
  • Enum metodları alu_op = ADD; üzerinde gösterilir: alu_op.first().name(), .last().name(), .next().name() ve .num() sırasıyla ilk/son elemanı, sonrakini ve toplam eleman sayısını verir.
  • txn.addr = 16'hABCD; gibi alan alan atama ile txn = '{addr: ..., data: ..., op: SUB, valid: 1}; şeklindeki struct literal (toplu atama) karşılaştırılır.
  • pkt = '{tag: 4'hA, payload: 8'hFF, parity: 1'b1}; packed struct'a değer atar; $bits(pkt) toplam genişliği (13) verir ve %013b ile bit dizisi basılır.
  • register_val.word = 16'hA5F0; union'a yazıldığında, aynı bellek register_val.bytes.high (A5) ve register_val.bytes.low (F0) olarak okunabilir.

Önemli Noktalar

  • enum değerlerini .name() ile yazdırmak, dökümlerde 2'b10 yerine WRITE görmenizi sağlar; bu da hata ayıklamayı çok kolaylaştırır.
  • Sentezlenebilir ve bit düzeyinde paketlenmiş veri için struct packed kullanın; salt testbench/modelleme amaçlı esnek gruplamalarda unpacked struct yeterlidir.
  • struct literal ('{...}) ile toplu atama, alanları tek tek atamaktan daha okunaklı ve hataya kapalıdır; alan adlarını yazmak sıralama hatalarını önler.
  • union packed güçlüdür ama dikkatli kullanılmalıdır: aynı belleğin farklı yorumları, alan genişlikleri ve bit sıralaması (endianness) konusunda net olmayı gerektirir.
  • typedef'leri tek bir paket (package) ya da ortak dosyada toplamak, takım genelinde tutarlı tip kullanımını sağlar ve genişlik değişikliklerini tek noktadan yönetir.
  • Enum metodlarında .next() son elemandayken başa döner (sarmalama); bu davranışı durum makinelerinde göz önünde bulundurun.
  • Bir enum değişkenine tam sayı atamak doğrudan yapılamaz: current_state = 2; derleme hatasıdır. Dönüşüm için açık cast gerekir: current_state = state_e'(2);. Bu katılık, geçersiz durum kodlarının sessizce makineye sızmasını engeller. Tersi yön (enum → tam sayı) ise serbesttir.
  • packet_t içindeki alanların bit sırası bildirim sırasını izler: ilk yazılan alan (tag) en anlamlı bitlerde, son yazılan (parity) bit 0'da yer alır. %013b çıktısını okurken bunu hatırlayın: 1010_11111111_1.

Hangi Yapı Ne Zaman?

İhtiyaç Doğru araç Neden
Aynı genişliği birçok yerde kullanıyorum typedef Tek noktadan değişiklik
Sınırlı sayıda anlamlı değer (durum, komut) enum Okunabilirlik, .name() ile log, geçersiz değere kapalı
İlişkili alanları birlikte taşımak (testbench içi) struct (unpacked) Esnek, alanlar farklı tipte olabilir
Bitlere erişilecek / sentezlenecek / bus'a sürülecek struct packed Tek vektör gibi davranır, $bits() tanımlı
Aynı veriye iki farklı pencereden bakmak union packed Bellek paylaşımı (ör. word ↔ byte'lar)
Davranış da taşıyacak, kalıtım gerekecek class (Gün 2) Metodlar, virtual, dinamik ömür

Benzetme: struct, üstünde etiketli bölmeleri olan bir alet çantasıdır: her bölmenin adı (addr, data) ve içeriği farklı olabilir. struct packed ise aynı çantanın bölmelerinin yan yana, boşluksuz dizilmiş hâlidir; böylece çantayı tek bir uzun şerit gibi de okuyabilirsiniz. union ise aynı çantayı iki farklı etiket şablonuyla okumaktır.

Sık Yapılan Hatalar

  • Unpacked struct'ı bit vektörü gibi kullanmak: transaction_t unpacked olduğundan $bits() sonucu araca göre değişebilir ve bir porta doğrudan bağlanamaz. Sinyale sürülecek yapılar için packed kullanın.
  • Enum'a sayı atamak: state_e s = 1; hata verir. Ya sembolik ismi (READ) ya da açık cast (state_e'(1)) kullanın.
  • '{...} ile {...}'yı karıştırmak: Tek tırnaklı '{} atama kalıbıdır (assignment pattern) ve struct/dizi doldurur; tırnaksız {} ise birleştirme (concatenation) yapar ve bit vektörü üretir. İkisi aynı şey değildir.
  • Enum'un taban tipini unutmak: typedef enum {A, B, C} taban tipi int'tir (32-bit). 2-bitlik bir durum sinyaline sürmek istiyorsanız enum logic [1:0] {...} yazın.

Kendinizi Deneyin

  1. state_e enum'una current_state = state_e'(3); ile değer atayıp .name() yazdırın. Ardından state_e'(5) deneyin: hangi tool davranışını gözlemlediniz? (İpucu: taban tipi logic [1:0] olduğundan 5 sığmaz.)
  2. opcode_e üzerinde alu_op = alu_op.last(); alu_op = alu_op.next(); yaparak sarmalama davranışını kanıtlayın.
  3. reg16_t union'ına register_val.bytes.low = 8'h11; yazıp register_val.word değerini yazdırın. Hangi yarısı değişti?
  4. packet_t'ye 3 bitlik bir priority alanı ekleyin, $bits(pkt) sonucunun nasıl değiştiğini gözlemleyin.
Hızlı Kontrol: struct packed ile unpacked struct arasındaki temel fark nedir?

struct packed alanları bitişik bitler olarak saklar; tek bir vektör gibi davranır, $bits() kesin tanımlıdır, bit dilimleme (pkt[12:9]) yapılabilir ve sentezlenebilir. Unpacked struct ise alanları bağımsız saklar; alanlar string, kuyruk gibi paketlenemeyen tipler de olabilir ama vektör gibi kullanılamaz.