ALU Tasarımı (DUT)
Gün 6: Bitirme Projesi - Bölüm 1 | Doğrulanacak tasarım: 8-bit pipeline'lı ALU
Bitirme Projesine Genel Bakış
Gün 6 ve Gün 7 boyunca, Gün 1–5'te öğrendiğiniz her parçayı kullanarak tam bir sınıf tabanlı testbench inşa edeceksiniz. Derslerin sırası, bir doğrulama ortamının gerçek hayatta kurulma sırasını izler:
Gün 6 Gün 7
───────────────────────────────────── ──────────────────────────────────────────
36-1 ALU (DUT) → neyi test edeceğiz?
36 Interface → DUT'a nasıl bağlanacağız?
37 Transaction → veriyi nasıl soyutlayacağız?
38 Generator → uyarıcıyı kim üretecek?
39 Driver → pinlere kim sürecek?
40 Ara Test → gen + drv çalışıyor mu? 41 Monitor → DUT ne yaptı?
42 Scoreboard → doğru mu yaptı?
43 Environment → hepsini kim bağlar?
44 Test → hangi senaryo?
45 Coverage → yeterince denedik mi?
46 Final Top → hepsini çalıştır
Her ders tek bir dosyaya karşılık gelir ve final testbench bu dosyaları `include ile birleştirir. Bu dersin EDA Playground bağlantısı tüm projeyi içerir; ilgili dersin dosyasını orada bulup inceleyebilirsiniz.
Bu bölümde, tüm doğrulama projemizin merkezinde yer alan DUT (Design Under Test / Test Edilen Tasarım) olan 8-bit ALU modülünü inceliyoruz. Testbench mimarisinde DUT, doğruluğunu kanıtlamaya çalıştığımız asıl donanımdır; driver onu uyarır, monitor onu gözler ve scoreboard onun çıkışlarını referans modelle karşılaştırır.
DUT Nedir ve Mimarideki Yeri
DUT (Design Under Test), doğrulama ortamının kalbidir. Testbench'in tüm diğer bileşenleri (interface, driver, monitor, scoreboard...) yalnızca bu modülün doğru çalışıp çalışmadığını anlamak için vardır.
Bu ALU şu temel sözleşmeyi (contract) sunar:
- Girişler:
clk,rst_n,in_valid,operand_a,operand_b,opcode - Çıkışlar:
out_valid,result,flags - DUT, dış dünya ile yalnızca bu portlar üzerinden konuşur. Testbench içindeki
alu_ifarayüzü tam olarak bu portlara bağlanır.
Senkron Tasarım ve Pipeline Mantığı
ALU iki ayrı bloktan oluşur:
- Kombinasyonel blok (
always_comb): Seçilenopcode'a göre aritmetik/mantıksal işlemi yapar ve sonucunext_resultile bayraklarınext_flagsara sinyallerinde üretir. - Sıralı blok (
always_ff): Saat kenarında bu ara sonuçları çıkış register'larına (result,flags,out_valid) yazar. Bu yapı bir pipeline (boru hattı) oluşturur: bir girişin sonucu bir saat çevrimi gecikmeyle çıkışta görünür.
Bu gecikme, testbench tarafında çok önemlidir: monitor, çıkışın hangi saat kenarında geçerli olacağını (out_valid) bilmek zorundadır.
Opcode ve Bayrak Sözleşmesi
Tasarımdaki localparam opcode tanımları (OP_ADD, OP_SUB, ...), ALU_Transaction sınıfındaki opcode_e enum değerleriyle birebir aynı olacak şekilde seçilmiştir. Bu uyum, transaction'ı doğrudan DUT pinlerine sürebilmemizi sağlar. Benzer şekilde bayrak bitleri (FLAG_Z, FLAG_N, FLAG_C, FLAG_P) sabit pozisyonlarda tanımlanmıştır.
Kaynak Kod
module alu (
input logic clk,
input logic rst_n,
input logic in_valid,
input logic [7:0] operand_a,
input logic [7:0] operand_b,
input logic [2:0] opcode,
output logic out_valid,
output logic [15:0] result,
output logic [3:0] flags
);
// Internal signals for combinational logic outputs
logic [15:0] next_result;
logic [3:0] next_flags;
// Opcode definitions perfectly matched with Testbench ALU_Transaction enum
localparam bit [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;
// Flag bit positions
localparam int FLAG_Z = 0; // Zero
localparam int FLAG_N = 1; // Negative
localparam int FLAG_C = 2; // Carry (for 8-bit operations)
localparam int FLAG_P = 3; // Parity
// =========================================================================
// Combinational Block: Arithmetic & Logic Operations
// =========================================================================
always_comb begin
// Default initializations to prevent latches
next_result = 16'd0;
next_flags = 4'd0;
// Perform the operation based on the synced opcode
case (opcode)
OP_ADD: next_result = operand_a + operand_b;
OP_SUB: next_result = operand_a - operand_b;
OP_AND: next_result = {8'h00, operand_a & operand_b};
OP_OR: next_result = {8'h00, operand_a | operand_b};
OP_XOR: next_result = {8'h00, operand_a ^ operand_b};
OP_NOT: next_result = {8'h00, ~operand_a};
OP_SHL: next_result = {8'h00, operand_a} << operand_b[2:0];
OP_SHR: next_result = {8'h00, operand_a} >> operand_b[2:0];
default: next_result = 16'd0;
endcase
// Calculate status flags
next_flags[FLAG_Z] = (next_result == 16'd0);
next_flags[FLAG_N] = next_result[15];
// Carry flag makes sense mainly for ADD/SUB out of the 8th bit
if (opcode == OP_ADD || opcode == OP_SUB) begin
next_flags[FLAG_C] = next_result[8];
end else begin
next_flags[FLAG_C] = 1'b0;
end
// Parity flag (XOR reduction of the result)
next_flags[FLAG_P] = ^next_result;
end
// =========================================================================
// Sequential Block: Output Registers (Pipelining)
// =========================================================================
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
// Reset state
out_valid <= 1'b0;
result <= 16'd0;
flags <= 4'd0;
end else begin
// Pipeline the valid signal
out_valid <= in_valid;
// Only update outputs when input data is valid
if (in_valid) begin
result <= next_result;
flags <= next_flags;
end
end
end
endmodule
Kodun Açıklaması
- Modül arayüzü:
module alu (...)8-bitoperand_a/operand_bgirişlerini, 3-bitopcode'u vein_validel sıkışma sinyalini alır; 16-bitresult, 4-bitflagsveout_validüretir. Sonucun 16-bit olması toplama/çarpma benzeri işlemlerde taşmayı temsil edebilmek içindir. - Opcode sabitleri:
localparam bit [2:0] OP_ADD = 3'b000 ... OP_SHR = 3'b111ile 8 işlem kodlanır. Bu kodlamanın transaction enum'u ile aynı olması, doğrulamanın temel taşıdır. - Bayrak pozisyonları:
FLAG_Z(sıfır),FLAG_N(negatif),FLAG_C(carry),FLAG_P(parity) bayraklarının bit konumlarını sabitler. always_combbloğu: Latch oluşmasını engellemek için öncenext_resultvenext_flagsvarsayılan olarak sıfırlanır. Ardındancase (opcode)ile ilgili işlem yapılır. Mantıksal işlemlerde (OP_AND,OP_OR,OP_XOR,OP_NOT) 8-bit sonuç{8'h00, ...}ile 16-bit'e genişletilir; kaydırma işlemleri (OP_SHL,OP_SHR)operand_b[2:0]kadar kaydırır.- Bayrak hesabı:
FLAG_Zsonucun sıfır olup olmadığını,FLAG_Nen üst bitnext_result[15]'i izler.FLAG_CyalnızcaOP_ADD/OP_SUBiçinnext_result[8]'den alınır, diğer işlemlerde0'dır.FLAG_P,^next_resultile sonucun parite (XOR indirgeme) değerini verir. always_ffbloğu: Asenkron aktif-düşük reset (!rst_n) durumunda çıkışlar temizlenir. Normal çalışmadaout_valid <= in_validile geçerli sinyali bir çevrim geciktirilerek pipeline'a sokulur; çıkış register'ları yalnızcain_validyüksekken güncellenir.
Önemli Noktalar
always_combiçinde varsayılan atama yapmak, istenmeyen latch'lerin oluşmasını engeller; sentezlenebilir ve simülasyonda tutarlı tasarımın temel kuralıdır.- Çıkış register'ları bir saat çevrimi gecikme getirir (
out_valid <= in_valid). Driver ve monitor bu gecikmeyi dikkate alacak şekilde tasarlanmalıdır; aksi halde yanlış zamanda örnekleme yapılır. - Carry bayrağı yalnızca aritmetik işlemlerde anlamlıdır; mantıksal işlemlerde bilinçli olarak
0yapılmıştır. Referans model (scoreboard) bu davranışı birebir taklit etmelidir. - DUT'taki opcode kodlaması ile transaction enum'u senkron tutulmalıdır; biri değişirse diğeri de değişmeli, yoksa tüm karşılaştırmalar bozulur.
- Reset asenkron olarak
negedge rst_nile tetiklenir; testbench reset sırasında en az birkaç saat çevrimirst_n'i düşük tutmalıdır. resultyalnızcain_validyüksekken güncellenir, amaout_validher çevrimin_valid'i izler. Yaniin_validdüştüğünderesultson geçerli değeri tutar; monitor bu değeri yalnızcaout_valid=1iken okumalıdır, aksi halde eski sonucu yeni işlem sanır.- Bayrak sözleşmesi tam olarak şudur ve scoreboard'daki referans model bunu birebir izlemelidir:
| Bit | Bayrak | Tanım | Hangi işlemlerde anlamlı? |
|---|---|---|---|
| 0 | Z (Zero) |
result == 0 |
Hepsi |
| 1 | N (Negative) |
result[15] |
Pratikte yalnızca SUB (borrow ile 16-bit sonuç negatif görünür) |
| 2 | C (Carry) |
result[8] (8-bit işlemde 9. bit) |
Yalnızca ADD/SUB; diğerlerinde 0 |
| 3 | P (Parity) |
^result (tek sayıda 1 → 1) |
Hepsi |
Bu DUT'ta Bilinçli Bırakılan "İlginç" Noktalar
İyi bir doğrulama mühendisi, tasarımı okurken "burası bir hata üretebilir" dediği yerleri not alır. Bu ALU'da dikkat çeken noktalar:
SUBve işaret: Operandlar 8-bit işaretsiz, sonuç 16-bit.0 - 1işlemi16'hFFFFüretir;Nbayrağı 1,C(borrow) bayrağı 1,Zbayrağı 0 olur. Referans model bu genişlemeyi aynı biçimde yapmalıdır.- Kaydırma miktarı: Yalnızca
operand_b[2:0]kullanılır;operand_b = 8ileoperand_b = 0aynı sonucu verir. Transaction sınıfındakic_shift_rangekısıtı bu yüzdenoperand_b'yi 0–7'ye sınırlar; ama bir hata avcısı olarak 8 ve üstünü de denemek isteyebilirsiniz. SHLtaşması:{8'h00, 8'hFF} << 7sonucu16'h7F80'dir; 8-bit'e sığmaz ama 16-bit sonuçta kaybolmaz.cp_result'takimediumbin'i bu durumu yakalar.- Carry'nin mantıksal işlemlerde sıfırlanması: Kombinasyonel blokta açıkça yazılmıştır. Bunu unutan bir referans model,
ANDsonucundaresult[8]zaten 0 olduğu için tesadüfen doğru çalışır; ama sözleşmeyi bilinçli yazmak her zaman daha güvenlidir.
Kendinizi Deneyin
- Kâğıt üzerinde hesaplayın:
operand_a = 8'h80, operand_b = 8'h80, opcode = OP_ADDiçinresultve dört bayrak ne olur? (result = 16'h0100,Z=0,N=0,C=1,P=1.) Aynı işlemiOP_SUBiçin yapın. - Tasarıma bilinçli bir hata enjekte edin:
OP_SUBsatırınıoperand_b - operand_ayapın. Gün 7'de scoreboard'un bunu yakaladığını göreceksiniz; şimdilik hangi test girdilerinin bu hatayı ortaya çıkarmayacağını düşünün (ipucu:a == b). always_ffiçindekiif (in_valid)koşulunu kaldırın.resultartık ne zaman değişiyor? Monitor'ünout_validfiltresi bu değişiklikten etkilenir mi?
Hızlı Kontrol: in_valid düştükten sonra result çıkışında ne görürsünüz?
Son geçerli işlemin sonucu. result yalnızca in_valid=1 iken güncellenir; out_valid ise in_valid'i bir çevrim gecikmeyle izler ve 0 olur. Monitor bu yüzden result'ı yalnızca out_valid=1 iken örneklemelidir.