Giriş

RISC-V Open Source ISA (Instruction Set Architecture) mimarisinde memory işlemlerini bildiğimce anlatmaya çalışacağım.

RISC-V mimarisinde memory işlemleri byte addressable (Bayt Adreslenebilir) şeklindedir. Yani her bir byte için bir adres vardır.

riscv memory

Bu yapıyı Instruction Memory (Buyruk Hafızası) iiçerisinde word (kelime) boyutunda kullanırız. Çünkü RISC-V32 mimarisinde her bir instruction (buyruk) 32bitlik bir kelime boyutunda saklanır.

Data Memory (Veri Hafızası) ise byte boyutunda kullanılır. Çünkü load byte, load half, store byte, store half gibi buyruklarla byte veya half boyutunda veri okuma veya yazma yapılır.

Bu konularda insanın kafası çokca karışabiliyor. Ben de bu kafa karışıklığını yaşadım ve siz okurlarıma bunu anlatmaya çalışacağım.

Önemli bir diğer husus ise RISC-V mimarisinde memory işlemlerinde little endian olarak veri erişimi yapılır. Yani lsb byte lsb adrese yazılır.

Ayrıca dikkat edilmesi gereken bir diğer husus ise Memory’de word aligment kod yazımı önerilir. Bazı mimarilerde word aligment yapılmadığında hata verirken, bazı mimarilerde yavaş çalışır. Ben word aligment yazmayı beyz alarak anlatacağım.

Byte Addressable, Word Addressable

riscv memory

Resimde gördüğünüz gibi her bir byte için bir adres vardır. Burada word olarak veri erişimi yapmak istediğimizde byte adresin son iki bitini shift ederek kullanırız. Bu işlem sonucunda bize word adresi elde ederiz.

BYTE_ADDRESS = 0x04
WORD_ADDRESS = 0x01

// yapılan işlem şudur:
WORD_ADDRESS = BYTE_ADDRESS >> 2

Örnek

addr_i = 0x00000004

Bu address’i binary olarak düşünelim:

addr_i = 0b0000_0000_0000_0000_0000_0000_0000_0100

Bu addresi word adres olarak kullanmak için son iki biti shift ederiz.

addr_i [12:0] = 0b0000_0000_0100 = 0x004 (byte address)
addr_i [12:2] = 0b00_0000_0001   = 0x001 (word address)

Sonuç olarak byte address üzerinden aldık, word address olarak işlem yaptık.


Instruction Memory Address

riscv memory

RISC-V’de instruction memory’de word boyutunda veri erişimi yapılıyor. Çünkü her bir instruction 32bitlik bir kelime boyutunda saklanır. Bundan dolayı instruction memory’de adres girişine verilen program counter değeri her clock vuruşunda 4 arttırılır.

riscv memory

4 artırılıma sebebi az önce anlattığımız byte addressable yapısından dolayıdır. Eğer word adres olarak düşünüp bir arttırsaydık 4 byte’lık bir veri okumuş olurduk. Bu da bir instruction’ın tamamını alamazdı.

Data Memory Address

Data Memory’de ise durum biraz daha farklıdır. Data Memory’de byte boyutunda veri erişimi yapılıyor. Çünkü load byte, load half, store byte, store half gibi buyruklarla byte veya half boyutunda veri okuma veya yazma yapılır. Bundan dolayı veri işlemlerinde byte half word ve word olarak işlemlerle uğraşırız.

Address işlemlerini daha iyi anlamak için Data Memory’de kullanılan RV32I buyruklarını inceleyelim.

Bu buyruklar 3’e ayrılır.

  1. Word İşlemleri
    • Load Word (LW)
    • Store Word (SW)
  2. Half İşlemleri
    • Load Half (LH)
    • Load Half Unsigned (LHU)
    • Store Half (SH)
  3. Byte İşlemleri
    • Load Byte (LB)
    • Load Byte Unsigned (LBU)
    • Store Byte (SB)

Bu komutların dışında bir de Load Upper Immediate (LUI) komutu vardır. İlk olarak bunu inceleyelim.

Bu komutları açıklarken RV32I ISA’sını barındıran RIPES simülatörünü kullanacağım.

riscv memory

RIPES’i kullanmak için BURAYA tıklayarak gidebilirsiniz.


Data Memory Komutları

Load Upper Immediate - LUI

Bu komut, 20 bitlik bir veriyi 32bitlik register’ın üst 20 bitine yazar. Kalan 12 bit ise 0 yapılır.

lui x5,0xaabbd        #x5 register'ın üst 20 bitine 0xAABBD yükler, sign işlemlerden dolayı C değil D yükledik.
addi x5,x5,-0x323     #x5 register'ın alt 12 bitine 0xCDD

Bu kod ile x5 register’ında oxAABBCCDD değerini yğkleyebilir olduk. Bit işlemleri olarak ne yaptığımıza bakalım:

x5 = 0xaabbd = 1010 1010 1011 1011 1101 0000 0000 0000
0x323        = 0000 0000 0000 0000 0000 0011 0010 0011
-0x323       = 1111 1111 1111 1111 1111 1100 1101 1101

x5 = 0xaabbd = 1010 1010 1011 1011 1101 0000 0000 0000
-0x323       = 1111 1111 1111 1111 1111 1100 1101 1101
+_____________________________________________________
x5           = 1010 1010 1011 1011 1100 1100 1101 1101
x5           = aabbccdd

Word İşlemleri

Word işlemlerinde kafa karıştırıcı bir durum yoktur. Çünkü word boyutunda veri erişimi yapılır.

Store Word - SW

  • 32 bitlik veriyi olduğu gibi word address’e yükler.
  • little endian olarak yükler. (lsb byte lsb adrese)
  • address 4 bytle hizalamalı olmalıdır, yoksa hata verir.
lui x1,0x10000    # x1 register'ına 0x10000000 memory adresini yükleriz.
addi x1,x1,0x000    

riscv memory

RIPES’ın memory kısmına baktığımızda little endian olarak 0xAABBCCDD değerinin 0x10000000 adresine yazıldığını görebiliriz.

Little Endian: Verinin en düşük değerli baytının en düşük adresli memory hücresine yazılmasıdır.

Load WORD - LW

  • belirtilen addresden başlayarak belirtilen register’a word’u yazar.
  • address 4 bytle hizalamalı olmalıdır, yoksa hata verir.
  • burda word olduğu için kafa karıştırıcı bir durum yoktur.

Şimdi yüklemiş olduğumuz 0xAABBCCDD değerini x20 register’ına kaydededlim.

lui x1,0x10000    # x1 register'ına 0x10000000 memory adresini yükleriz.
addi x1,x1,0x000    

sw x5,0(x1)       # x5 register'ındaki değeri x1 adresindeki ram'e yazarız.
    
lw x20,0(x1)      # x1 adresinde bulunan değeri x20 register'ına yükleriz.

riscv memory

Kodlarımızıda hep offset 0 olarak kullandık. bu offset değerini word işlemlerinde 4ün katı olarak kullanmak word aligment işlemler yapmamızı sağlar.

Şimdi daha farklı bir kod ile bu offset değerini anlamaya çalışalım.

lui x5,0xaabbd    # x5 register'ın üst 20 bitine 0xAABBD yükler, sign işlemlerden dolayı C değil D yükledik.
addi x5,x5,-0x323 # x5 register'ın alt 12 bitine 0xCDD

lui x6, 0xdead    # x6 register'ın üst 20 bitine 0xDEAD0 yükler.

lui x7, 0xABCD    # x7 register'ın üst 20 bitine 0xABCD0 yükler.

lui x1,0x10000    # x1 register'ına 0x10000000 memory adresini yükleriz.
addi x1,x1,0x000    

sw x5,0(x1)       # x5 register'ındaki değeri x1 + 0 adresindeki ram'e yazarız. (word)
sw x6,4(x1)       # x6 register'ındaki değeri x1 + 4 adresindeki ram'e yazarız (word)
sw x7,8(x1)       # x7 register'ındaki değeri x1 + 8 adresindeki ram'e yazarız (word)

Bu kod ile 0x10000000, 0x10000004, 0x10000008 adreslerine ardışık olarak yazma işlemi yaparız. Normalde ardışık ram adresleri için x1 register’ın değerini 4 artırmamız gerekiyordu. Bunu yapmak yerine bize kolaylık sağlayan offset değerini kullandık.

riscv memory

Half Word İşlemleri

Half word işlemleri word işlemlerine benzer. Tek farkı 16 bitlik veri ile işlem yapmamızdır. Bu 16 bitlik veriyi MSB mi yoksa LSB 16 bite mi yükleyeceğimizi offset değeri ile belirleyebiliriz.

Store Half - SH

  • Yazılacak 32-bit’lik değerin sadece en düşük 16 biti (LSB),belirtilen adresden başlayarak bellekteki iki ardışık bayta yazılır.
  • Veri, küçük-endian düzenine göre yerleştirilir.
  • Adres genellikle 2 baytlık hizalama gerektirir.
lui x5,0xaabbd    # x5 register'ın üst 20 bitine 0xAABBD yükler, sign işlemlerden dolayı C değil D yükledik.
addi x5,x5,-0x323 # x5 register'ın alt 12 bitine 0xCDD

lui x1,0x10000    # x1 register'ına 0x10000000 memory adresini yükleriz.
addi x1,x1,0x000  

sh x5,0,x1        # x1 adresindeki ram'in lsb 16 bitine x5 register'ındaki lsb 16 biti yükleriz.

Burada yapılanlar şudur. x5 adresine 0xAABBCCDD verisini yazdık. Daha sonra sh (store half) ile bereabe x1+0 adresinden başlayıp 16 bit veriyi little endian olarak yazdık.

x1+0 = 0xdd
x1+1 = 0xcc
x1+2 = (önceki değer)
x1+3 = (önceki değer)

riscv memory


Eğer offset'i iki yaparsak x1+2 adresinden başlayarak 16 bit yazmış oluruz.
```asm
lui x5,0xaabbd    # x5 register'ın üst 20 bitine 0xAABBD yükler, sign işlemlerden dolayı C değil D yükledik.
addi x5,x5,-0x323 # x5 register'ın alt 12 bitine 0xCDD

lui x1,0x10000    # x1 register'ına 0x10000000 memory adresini yükleriz.
addi x1,x1,0x000  

x1+0 = (önceki değer)
x1+1 = (önceki değer)
x1+2 = 0xdd
x1+3 = 0xcc

riscv memory

Load Half - LH

  • 32 bitlik verinin lsb veya msb 16 bitini alarak register’a sign extend olarak yazma işlemi yapar.
  • 2 byte offset yapılabilir.

Ne olursa olsun register’a yükleme lsb 16 bit olarak yapılır.

lui x5,0xaabbd    # x5 register'ın üst 20 bitine 0xAABBD yükler, sign işlemlerden dolayı C değil D yükledik.
addi x5,x5,-0x323 # x5 register'ın alt 12 bitine 0xCDD

lui x1,0x10000    # x1 register'ına 0x10000000 memory adresini yükleriz.
addi x1,x1,0x000  

sh x5,2,x1        # x1 adresindeki ram'in msb 16 bitine x5 register'ındaki lsb 16 biti yükleriz.

lh x6,2,x1        # x1+2 adresinde bulunan değeri Signed olarak x6 register'ına yükler.

Eğer üst 16 biti yüklemek istersek offset’i 2 fazla olacak şekilde yapmamız gereklidir.

lui x5,0xaabbd    # x5 register'ın üst 20 bitine 0xAABBD yükler, sign işlemlerden dolayı C değil D yükledik.
addi x5,x5,-0x323 # x5 register'ın alt 12 bitine 0xCDD

lui x1,0x10000    # x1 register'ına 0x10000000 memory adresini yükleriz.
addi x1,x1,0x000  

sh x5,2,x1        # x1 adresindeki ram'in msb 16 bitine x5 register'ındaki lsb 16 biti yükleriz.

lh x6,2,x1        # x1+2 adresinde bulunan değeri Signed olarak x6 register'ına yükler.


lui x5,0xf7f8f9
addi x5,x5,0x123  # x5 register'ına 0xF7F8F9123 yazılır

sh  x5,6,x1       # x1+6 adresine x5'in lsb 16 biti yüklenir

lui x5,0x0004
addi x5,x5,0x455 #x5 register'ına 0x000004455

sh x5 ,4,x1       #x1+4 adresine x5'in lsb 16 biti yüklenir

lh x7,6,x1        # x7 register'ına x1 + 6 adresindeki lsb 16 bit signed olarak yüklenir.
lh x8,4,x1        # x8 register'ına x1 + 4 adresindeki lsb 16 bit signed olarak yüklenir

riscv memory

register değerleri de şu şekildedir:

riscv memory

Burada yapılan şudur. x6 register’ına 0x10000002 (x1 +2) adresinde bulunan 16 bitlik veri signed olarak yüklenir, bu değer de 0xffffccdd olur.

Signed olduğu için ve değerimizin 16.bitinde 1 olduğu için üst 16 bit 1 ile doldurulur. Bu değerin negatif olduğunu varsayarak yapılan bir işlemdir.

Daha sonra x5 register’ına 0xF7F8F9123 değerini yükleriz. Bu değeri 0x10000006 (x1 +6) adresine yazarız.

x5 register’ına 0x000004455 değerini yükleriz. Bu değeri 0x10000004 (x1 +4) adresine yazarız.

x7 register’ına 0x10000006 (x1 +6) adresinde bulunan 16 bitlik veri signed olarak yüklenir, bu değer de 0xffff9123 olur.

x8 register’ına 0x10000004 (x1 +4) adresinde bulunan 16 bitlik veri signed olarak yüklenir, bu değer de 0x00004455 olur.

Yani x8 register’ına lsb16 bit değeri yüklenir. x7 register’ına ise msb16 bit değeri yüklenir.

Load Half Unsigned

  • Load Half’tan tek farkı sign extend yapmaz.
lui x5,0xaabbd    # x5 register'ın üst 20 bitine 0xAABBD yükler, sign işlemlerden dolayı C değil D yükledik.
addi x5,x5,-0x323 # x5 register'ın alt 12 bitine 0xCDD

lui x1,0x10000    # x1 register'ına 0x10000000 memory adresini yükleriz.
addi x1,x1,0x000  

sh x5,2,x1        # x1 adresindeki ram'in msb 16 bitine x5 register'ındaki lsb 16 biti yükleriz.

lhu x6,2,x1        # x1+2 adresinde bulunan değeri Signed olarak x6 register'ına yükler.

x6 = 0x0000CCDD değerini alır.

Byte İşlemleri

Store Byte -SB

  • 8 bitlik veriyi 4 byte offset ile hafızaya kaydeder.

Örnek:

lui x1,0x10000    # x1 register'ına 0x10000000 memory adresini yükleriz.
addi x1,x1,0x000  

addi x5,x0,0x012 # x5 register'ına 0x00000012 değerini yükler
sb, x5,0(x1)

addi x5,x0,0x034
sb, x5,1(x1)     # x5 register'ına 0x00000034 değerini yükler

addi x5,x0,0x056
sb, x5,2(x1)     # x5 register'ına 0x00000056 değerini yükler

addi x5,x0,0x078
sb, x5,3(x1)     # x5 register'ına 0x00000078 değerini yükler

x1 + 0 = 0x12
x1 + 1 = 0x34
x1 + 2 = 0x56
x1 + 3 = 0x78

riscv memory

Load Byte - LB

  • 32 bitlik bir değerin 4 byte offsetli herhangi bir byte değerini alıp register’a sign extend olarak yükler.

Örnek:

lui x1,0x10000    # x1 register'ına 0x10000000 memory adresini yükleriz.
addi x1,x1,0x000  

addi x5,x0,0x012 # x5 register'ına 0x000000012 değeri yüklenir
sb x5,0(x1)

addi x5,x0,0x034
sb x5,1(x1)     # x5 register'ına 0x00000034 değerini yükler

addi x5,x0,0x056
sb x5,2(x1)     # x5 register'ına 0x00000056 değerini yükler

addi x5,x0,0x078
sb x5,3(x1)     # x5 register'ına 0x00000034 değerini yükler

# Ardışık load byte işlemleri
lb x10,0(x1)
lb x11,1(x1)
lb x12,2(x1)
lb x13,3(x1)

addi x5,x0,0x0ff # x5 register'ına 0x00000ff yüklenir
sb x5,4(x1)      # x1 + 4 adresine x5 register'ının 8 biti yüklenir

lb x14,4(x1)     # x1+ 4 adresindeki değer x14 register'ına signed olarak yüklenir

Register çıktıları şu şekildedir:

riscv memory

Load Byte Unsignded - LBU

Load Byte’ın unsigned olan halidir. Değer negatif olsa dahi üst 24 bit 0 olarak kalır.

lui x1,0x10000    # x1 register'ına 0x10000000 memory adresini yükleriz.
addi x1,x1,0x000  


addi x5,x0,0x0ff # x5 register'ına 0x00000ff yüklenir
sb x5,4(x1)      # x1 + 4 adresine x5 register'ının 8 biti yüklenir

lbu x14,4(x1)     # x1+ 4 adresindeki değer x14 register'ına unsigned olarak yüklenir

Bu sefer unsigned yaptığımızdan dolayı x14’ün msb 24 biti 0 ile doldurularak çıkış verilir.

Register çıktısı şu şekildedir:

riscv memory

Sonuç

Evet elimden geldiğince hafıza işlemlerini anlatmaya çalıştım. Kafa karıştırıcı bir konu olduğundan tekrar tekrar uğraştırıcı olabiliyor. Umarım yazı faydalı olmuştur.

Sonraki yazılarda görüşene dek sağlıcakla kalın!


Yayınlanma:
RISC-V Memory Donanim