Fence.i Instruction Implementation for Dcache¶
Overview¶
Bu dokümantasyon, RISC-V fence.i instruction'ının dcache tarafında nasıl implemente edildiğini açıklar. fence.i instruction'ı, instruction cache ile data cache arasında tutarlılık sağlamak için kullanılır.
Problem Tanımı¶
RISC-V mimarisinde self-modifying code (kendini değiştiren kod) desteklenmektedir. Bir program, memory'ye yeni instruction yazabilir ve ardından bu instruction'ları execute edebilir. Bunun için:
- Data cache'e yazılan yeni instruction'lar memory'ye flush edilmeli
- Instruction cache invalidate edilmeli
- Pipeline temizlenmeli
Implementasyon Detayları¶
1. Dirty Array Register Yapısı¶
Önceki Durum: Dirty bit'ler SRAM içinde tutuluyordu, bu da tek cycle'da tüm dirty durumlarını görmeyi imkansız kılıyordu.
Çözüm: Dirty array'i register olarak tuttuk:
// Dirty bits as register array for instant access
logic [NUM_WAY-1:0] dirty_reg_q [NUM_SET];
logic [NUM_WAY-1:0] dirty_reg_d [NUM_SET];
Bu sayede: - Tek cycle'da herhangi bir set'in dirty durumunu okuyabiliyoruz - Fence.i state machine'i için gerekli dirty taramasını hızlı yapabiliyoruz
2. Fence.i State Machine¶
Dcache için 8 state'li bir FSM implemente ettik:
FI_IDLE → FI_SCAN → FI_CHECK_WAY → FI_WRITEBACK_REQ → FI_WRITEBACK_WAIT → FI_MARK_CLEAN → FI_NEXT_WAY → FI_DONE
↑ ↓ ↓
└─────────────────────┴──────────────────────────────────────────────────────────────────────────────┘
| State | Açıklama |
|---|---|
FI_IDLE |
Bekleme durumu, fence.i sinyali beklenir |
FI_SCAN |
Set index'i ayarla, SRAM'a adres gönder |
FI_CHECK_WAY |
Mevcut set'te dirty way var mı kontrol et |
FI_WRITEBACK_REQ |
LowX interface üzerinden memory'ye yazma isteği gönder |
FI_WRITEBACK_WAIT |
Memory yazma işleminin tamamlanmasını bekle |
FI_MARK_CLEAN |
Dirty bit'i temizle |
FI_NEXT_WAY |
Sonraki way'e geç veya sonraki set'e ilerle |
FI_DONE |
İşlem tamamlandı, stall sinyalini kaldır |
3. Pipeline Stall Mekanizması¶
Fence.i işlemi sırasında pipeline'ı durdurmak için yeni bir stall tipi ekledik:
// ceres_param.sv
typedef enum logic [2:0] {
NO_STALL = 0,
DMEM_STALL = 1,
IMEM_STALL = 2,
MUL_STALL = 3,
DIV_STALL = 4,
FENCEI_STALL = 5 // Yeni eklendi
} stall_e;
Stall önceliği: FENCEI_STALL en yüksek önceliğe sahip (diğer stall'lardan önce kontrol edilir).
4. Flush Sinyali Yönetimi¶
Problem: Fence.i geldiğinde hem icache flush hem dcache dirty writeback gerekiyor. Ancak ortak flush sinyali her iki cache'i de etkiliyor ve dcache tag'lerini hemen sıfırlıyordu - dirty writeback tamamlanmadan!
Çözüm: Dcache için fence.i aktifken flush yazımlarını engelledik:
// Tag array write - fence.i sırasında flush yazımını engelle
for (int i = 0; i < NUM_WAY; i++)
tsram.way[i] = (flush && !fi_active) ? '1 : (cache_wr_way[i] && tag_array_wr_en);
5. Fence.i Başlatma Koşulu¶
Rising edge detection ile fence.i'yı güvenilir şekilde tespit ettik:
logic flush_i_prev;
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) flush_i_prev <= 1'b0;
else flush_i_prev <= flush_i;
end
wire fencei_rising_edge = flush_i && !flush_i_prev;
6. Pipe2 Flush Koşulu¶
Problem: Fence.i instruction'ı pipe2'de iken fencei_flush sinyali pipe2'yu flush ediyordu, bu da fence.i'nın kendisinin kaybolmasına neden oluyordu.
Çözüm: Pipe2 flush koşulundan fencei_flush kaldırıldı:
// Önce (hatalı):
if (!rst_ni || ex_flush_en || priority_flush == 3 || priority_flush == 2 || fencei_flush)
// Sonra (düzeltilmiş):
if (!rst_ni || ex_flush_en || priority_flush == 3 || priority_flush == 2)
Kullanılan Test¶
rv32ui-p-fence_i¶
Bu test, fence.i instruction'ının doğru çalışıp çalışmadığını kontrol eder:
- Memory'den instruction okur (örn:
0x80002000adresinden) - Aynı adrese yeni instruction yazar (store)
fence.iexecute eder- Yeni yazılan adrese jump eder
- Yeni instruction'ın doğru execute edilip edilmediğini kontrol eder
Test Akışı:
PC=0x80000144: sh x13, 0x80002004 # Yeni instruction yaz (0x8693)
PC=0x8000014c: sh x11, 0x80002006 # Yeni instruction yaz (0x14d6)
PC=0x80000150: fence.i # Dcache flush + Icache invalidate
PC=0x8000015c: jalr x6, x15 # 0x80002004'e jump
PC=0x80002004: addi x13, x13, 0x14d6 # Yeni yazılan instruction execute
Kullanılmayan/Alternatif Yaklaşımlar¶
1. SRAM-based Dirty Array (Kullanılmadı)¶
Sebep: SRAM'dan okuma 1 cycle latency'ye sahip. Fence.i sırasında tüm set'leri taramak için çok fazla cycle gerekiyordu.
Tercih edilen: Register-based dirty array - O(1) erişim süresi.
2. Blocking Cache During Fence.i (Kısmen Kullanıldı)¶
Pipeline stall ile cache'i bloke ettik ama normal cache işlemlerinin state machine'i bozmadığından emin olduk.
3. Write-Through Cache (Kullanılmadı)¶
Sebep: Write-through cache'de her yazma işlemi doğrudan memory'ye gider, bu da fence.i'yı basitleştirir ama performansı düşürür.
Tercih edilen: Write-back cache - daha iyi performans, fence.i'da dirty writeback gerekir.
4. Invalidate-Only Approach (Kullanılmadı)¶
Sebep: Sadece cache invalidate etmek veri kaybına neden olur. Dirty data'lar memory'ye yazılmadan kaybolur.
Tercih edilen: Dirty writeback + invalidate.
Dosya Değişiklikleri¶
| Dosya | Değişiklik |
|---|---|
rtl/core/mmu/cache.sv |
Fence.i state machine, dirty register array, fencei_stall_o port |
rtl/core/stage04_memory/memory.sv |
fencei_stall_o port passthrough |
rtl/core/cpu.sv |
FENCEI_STALL entegrasyonu, pipe2 flush düzeltmesi |
rtl/pkg/ceres_param.sv |
FENCEI_STALL enum değeri |
rtl/include/writeback_log.svh |
FENCEI_STALL logging koşulu |
Test Sonucu¶
✅ MATCH | PC=0x80002004 INST=0x14d68693 x13 0x000001bc | PC=0x80002004 INST=0x14d68693 x13 0x000001bc
Test başarıyla geçti. Dcache dirty data'yı memory'ye yazdı ve icache yeni instruction'ı doğru şekilde fetch etti.
Gelecek İyileştirmeler¶
- Parallel Dirty Scan: Tüm set'lerdeki dirty bit'leri paralel tarayarak daha hızlı writeback
- Priority Encoder: Birden fazla dirty way varsa öncelik sırası belirleme
- Writeback Buffer: Ardışık writeback'leri buffer'layarak memory bandwidth kullanımını optimize etme