RiscV ISA kod çözücü

Bu eğitimin hedefleri şunlardır:

  • Talimatların MPACT-Sim simülatöründe nasıl gösterildiğini öğrenin.
  • ISA açıklama dosyasının yapısını ve söz dizimini öğrenin.
  • RiscV RV32I talimat alt kümesi için ISA açıklamalarını yazın

Genel Bakış

MPACT-Sim'de hedef talimatların kodu çözülür ve bilgilerinin daha kullanılabilir olması ve semantiklerin daha hızlı yürütülmesi için dahili bir temsilde depolanır. Bu talimat örnekleri, sık sık çalıştırılan talimatların çalıştırılma sayısını azaltmak için bir talimat önbelleği içinde önbelleğe alınır.

Talimat sınıfı

Başlamadan önce, talimatların MPACT-Sim'de nasıl sunulduğuna biraz bakmak yararlı olacaktır. Instruction sınıfı mpact-sim/mpact/sim/generic/instruction.h dosyasında tanımlanmıştır.

Instruction sınıfı örneği, "yürütüldüğünde" talimatı simüle etmek için gereken tüm bilgileri içerir. Örneğin:

  1. Talimat adresi, simüle edilmiş talimat boyutu (ör. .text dosyasında boyut).
  2. Talimat işlem kodu.
  3. Koşullar işlenen arayüz işaretçisi (varsa).
  4. Kaynak işlenen arayüz işaretçilerinin vektörü.
  5. Hedef işlenen arayüzü işaretçilerinin vektörü.
  6. Semantik işlev çağrılabilir.
  7. Mimari durum nesnesinin işaretçisi.
  8. Bağlam nesnesinin işaretçisi.
  9. Alt öğe ve sonraki Instruction örneklerine işaretçi.
  10. Söküm dizesi.

Bu örnekler genellikle bir talimatlar (örnek) önbelleği içinde depolanır ve talimat yeniden yürütüldüğünde yeniden kullanılır. Bu, çalışma zamanı boyunca performansı artırır.

Bağlam nesnesine işaretçi hariç tümü, ISA açıklamasından oluşturulan talimat kod çözücü tarafından doldurulur. Bu eğitimde, bu öğeleri doğrudan kullanamayacağımız için bu öğelerin ayrıntılarını bilmek gerekli değildir. Bunun yerine, bunların nasıl kullanıldığına dair genel bir bilgi sahibi olmanız yeterlidir.

Çağrılabilir anlamsal işlev, talimatın anlamını uygulayan C++ işlevi/yöntem/işlev nesnesidir (lambda'lar dahil). Örneğin, bir add talimatı için her bir kaynak işlenenini yükler, iki işlem göreni ekler ve sonucu tek bir hedef işlenene yazar. Anlamsal işlev eğitiminde, anlamsal işlevler konusu derinlemesine ele alınmaktadır.

Talimat işlenenleri

Talimat sınıfı, üç tür operand arayüzüne işaretçiler içerir: önerme, kaynak ve hedef. Bu arayüzler, anlamsal işlevlerin temel talimat işleneninin gerçek türünden bağımsız olarak yazılmasını sağlar. Örneğin, kaydedici ve anında değerlerine erişim aynı arayüz üzerinden yapılır. Bu, aynı işlemi farklı operatörlerde (ör. kaydediciler ve anında değerler) gerçekleştiren talimatların aynı semantik işlev kullanılarak uygulanabileceği anlamına gelir.

Koşullu talimatın yürütmesini destekleyen ISA'lar için (diğer ISA'lar için boştur) belirtilen koşul işlenen arayüzü, belirli bir talimatın koşulun boole değerine göre yürütülmesinin gerekip gerekmediğini belirlemek için kullanılır.

// The predicte operand interface is intended primarily as the interface to
// read the value of instruction predicates. It is separated from source
// predicates to avoid mixing it in with the source operands needed for modeling
// the instruction semantics.
class PredicateOperandInterface {
 public:
  virtual bool Value() = 0;
  // Return a string representation of the operand suitable for display in
  // disassembly.
  virtual std::string AsString() const = 0;
  virtual ~PredicateOperandInterface() = default;
};

Kaynak işlenen arayüzü, talimat anlamsal işlevinin temel işlem gören türüne bakılmaksızın talimat işlenenlerindeki değerleri okumasına olanak tanır. Arayüz yöntemleri hem skaler hem de vektör değerli işlenenleri destekler.

// The source operand interface provides an interface to access input values
// to instructions in a way that is agnostic about the underlying implementation
// of those values (eg., register, fifo, immediate, predicate, etc).
class SourceOperandInterface {
 public:
  // Methods for accessing the nth value element.
  virtual bool AsBool(int index) = 0;
  virtual int8_t AsInt8(int index) = 0;
  virtual uint8_t AsUint8(int index) = 0;
  virtual int16_t AsInt16(int index) = 0;
  virtual uint16_t AsUint16(int) = 0;
  virtual int32_t AsInt32(int index) = 0;
  virtual uint32_t AsUint32(int index) = 0;
  virtual int64_t AsInt64(int index) = 0;
  virtual uint64_t AsUint64(int index) = 0;

  // Return a pointer to the object instance that implements the state in
  // question (or nullptr) if no such object "makes sense". This is used if
  // the object requires additional manipulation - such as a fifo that needs
  // to be pop'ed. If no such manipulation is required, nullptr should be
  // returned.
  virtual std::any GetObject() const = 0;

  // Return the shape of the operand (the number of elements in each dimension).
  // For instance {1} indicates a scalar quantity, whereas {128} indicates an
  // 128 element vector quantity.
  virtual std::vector<int> shape() const = 0;

  // Return a string representation of the operand suitable for display in
  // disassembly.
  virtual std::string AsString() const = 0;

  virtual ~SourceOperandInterface() = default;
};

Hedef işlem arayüzü, DataBuffer örneklerini (kayıt değerlerini depolamak için kullanılan dahili veri türü) ayırmak ve yönetmek için yöntemler sunar. Hedef operandın da kendisiyle ilişkili bir gecikmesi vardır. Bu gecikme, talimat semantik işlevi tarafından ayrılan veri arabelleği örneğinin hedef yazmaç değerini güncellemek için kullanılmasına kadar bekleme döngüsü sayısıdır. Örneğin, bir add talimatının gecikme süresi 1, mpy talimatı için 4 olabilir. Bu konu, anlamsal işlevlerle ilgili eğitimde daha ayrıntılı olarak ele alınmaktadır.

// The destination operand interface is used by instruction semantic functions
// to get a writable DataBuffer associated with a piece of simulated state to
// which the new value can be written, and then used to update the value of
// the piece of state with a given latency.
class DestinationOperandInterface {
 public:
  virtual ~DestinationOperandInterface() = default;
  // Allocates a data buffer with ownership, latency and delay line set up.
  virtual DataBuffer *AllocateDataBuffer() = 0;
  // Takes an existing data buffer, and initializes it for the destination
  // as if AllocateDataBuffer had been called.
  virtual void InitializeDataBuffer(DataBuffer *db) = 0;
  // Allocates and initializes data buffer as if AllocateDataBuffer had been
  // called, but also copies in the value from the current value of the
  // destination.
  virtual DataBuffer *CopyDataBuffer() = 0;
  // Returns the latency associated with the destination operand.
  virtual int latency() const = 0;
  // Return a pointer to the object instance that implmements the state in
  // question (or nullptr if no such object "makes sense").
  virtual std::any GetObject() const = 0;
  // Returns the order of the destination operand (size in each dimension).
  virtual std::vector<int> shape() const = 0;
  // Return a string representation of the operand suitable for display in
  // disassembly.
  virtual std::string AsString() const = 0;
};

ISA açıklaması

Bir işlemcinin ISA'sı (Instruction Set Architecture - Talimat Grubu Mimarisi), yazılımın donanımla etkileşime geçtiği soyut modeli tanımlar. Mevcut talimat grubunu, talimatların çalıştığı veri türlerini, kayıtları ve diğer makine durumunu ve bunların davranışlarını (anlamsal) tanımlar. MPACT-Sim için, ISA, talimatların gerçek kodlamasını içermez. Bunlar ayrı olarak ele alınır.

İşleyen ISA'sı, soyut, kodlamadan bağımsız bir düzeyde verilen talimatı açıklayan bir açıklama dosyasında ifade edilir. Açıklama dosyası, mevcut talimatları sıralar. Her talimat için adını, operatörlerinin sayısını ve adlarını, ayrıca semantiklerini uygulayan bir C++ işlevine/çağrılabilir öğeye bağlamasını belirtmek zorunludur. Ayrıca, bir kod çözme biçimlendirme dizesi ve talimatın donanım kaynak adlarını kullanımı belirtilebilir. İlk örnek, hata ayıklama, izleme veya etkileşimli kullanım için talimatın metinsel sunumunu oluşturmak açısından yararlıdır. İkincisi, simülasyonda daha fazla döngü doğruluğu sağlamak için kullanılabilir.

ISA açıklama dosyası, gösterimden bağımsız talimat kod çözücü için kod oluşturan isa ayrıştırıcısı tarafından ayrıştırılır. Bu kod çözücü, talimat nesnelerinin alanlarını doldurmaktan sorumludur. Hedef kayıt numarası gibi belirli değerler, biçime özgü bir talimat kod çözücüden elde edilir. Bu kod çözücülerden biri, sonraki eğitimimizin odak noktası olan ikili kod çözücüdür.

Bu eğitimde, basit ve skaler bir mimari için ISA açıklama dosyasının nasıl yazılacağı ele alınmaktadır. Bunu göstermek için RiscV RV32I talimat setinin bir alt kümesini kullanacağız ve diğer eğiticilerle birlikte "Hello World" programını simüle edebilecek bir simülatör oluşturacağız. RiscV ISA hakkında daha fazla bilgi için Risc-V Spesifikasyonları başlıklı makaleyi inceleyin.

Dosyayı açarak başlayın: riscv_isa_decoder/riscv32i.isa

Dosyanın içeriği birden çok bölüme ayrılır. İlki ISA beyanı:

isa RiscV32I {
  namespace mpact::sim::codelab;
  slots { riscv32; }
}

Bu, RiscV32I değerinin ISA adı olduğunu belirtir ve kod oluşturucu, oluşturulan kod çözücünün işlem kodu ve operant bilgilerini almak için kullanacağı arayüzü tanımlayan RiscV32IEncodingBase adlı bir sınıf oluşturur. Bu sınıfın adı, ISA adı Pascal büyük/küçük harflerine dönüştürülerek ve ardından EncodingBase ile birleştirilerek oluşturulur. slots { riscv32; } beyanı, RiscV32I ISA'da (VLIW talimatındaki birden fazla yuvanın aksine) yalnızca tek bir talimat yuvası riscv32 olduğunu ve yalnızca riscv32 içinde çalıştırılmak üzere tanımlanan talimatların geçerli olduğunu belirtir.

// First disasm fragment is 15 char wide and left justified.
disasm widths = {-15};

Bu, herhangi bir ayrıştırma özelliklerinin ilk sökme parçasının (aşağıda daha fazlasını görebilirsiniz) 15 karakter genişliğindeki bir alanda iki yana yaslı olarak bırakılacağını belirtir. Sonraki parçalar, ek boşluk bırakmadan bu alana eklenir.

Bunun altında üç alan beyanı vardır: riscv32i, zicsr ve riscv32. Yukarıdaki isa tanımına göre, yalnızca riscv32 alanı için tanımlanan talimatlar RiscV32I isa'sının bir parçası olacaktır. Diğer iki yuva ne için?

Talimatları ayrı gruplara ayırmak için yuvalar kullanılabilir. Bu gruplar daha sonra tek bir yuva halinde birleştirilebilir. riscv32 yuvarı beyanında : riscv32i, zicsr notuna dikkat edin. Bu, riscv32 alanının zicsr ve riscv32i alanlarında tanımlanan tüm talimatları devraldığını belirtir. RiscV 32 bit ISA, bir dizi isteğe bağlı uzantının eklenmesi mümkün olan RV32I adlı bir temel ISA'dan oluşur. Yuva mekanizması, bu uzantılardaki talimatların ayrı ayrı belirtilmesine ve ardından genel ISA'yı tanımlamak için gerektiğinde birleştirilmesine olanak tanır. Bu durumda, RiscV "I" grubundaki talimatlar "zicsr" grubundaki talimatlardan ayrı olarak tanımlanır. İstenen nihai RiscV ISA için gereken şekilde "M" (çarpma/bölme), "F" (tek duyarlıklı kayan nokta), "D" (çift duyarlıklı kayan nokta), "C" (kompakt 16 bit talimatlar) vb. için ek gruplar tanımlanabilir.

// The RiscV 'I' instructions.
slot riscv32i {
  ...
}

// RiscV32 CSR manipulation instructions.
slot zicsr {
  ...
}

// The final instruction set combines riscv32i and zicsr.
slot riscv32 : riscv32i, zicsr {
  ...
}

zicsr ve riscv32 alan tanımlarının değiştirilmesi gerekmez. Ancak bu eğitimde, riscv32i yuvasına gerekli tanımları eklemeye odaklanacağız. Şu anda bu yuvada tanımlanan öğelere daha yakından bakalım:

// The RiscV 'I' instructions.
slot riscv32i {
  // Include file that contains the declarations of the semantic functions for
  // the 'I' instructions.
  includes {
    #include "learning/brain/research/mpact/sim/codelab/riscv_semantic_functions/solution/rv32i_instructions.h"
  }
  // These are all 32 bit instructions, so set default size to 4.
  default size = 4;
  // Model these with 0 latency to avoid buffering the result. Since RiscV
  // instructions have sequential semantics this is fine.
  default latency = 0;
  // The opcodes.
  opcodes {
    fence{: imm12 : },
      semfunc: "&RV32IFence"c
      disasm: "fence";
    ebreak{},
      semfunc: "&RV32IEbreak",
      disasm: "ebreak";
  }
}

Öncelikle, nihai ISA'da bu yuvaya doğrudan veya dolaylı olarak referans verildiğinde oluşturulan koda dahil edilmesi gereken başlık dosyalarını listeleyen bir includes {} bölümü vardır. Dahil et dosyaları ayrıca, genel kapsamlı includes {} bölümünde de listelenebilir. Bu durumda, her zaman dahil edilirler. Aynı dahil etme dosyasının her slot tanımına eklenmesi gerekiyorsa bu özellik yararlı olabilir.

default size ve default latency bildirimleri, aksi belirtilmedikçe bir talimatın boyutunun 4 olduğunu ve hedef operand yazma işleminin gecikmesinin 0 döngü olduğunu tanımlar. Burada belirtilen talimatın boyutunun, simüle edilen işlemcide yürütülecek bir sonraki sıralı talimatın adresini hesaplamak için program sayıcı artışı boyutu olduğunu unutmayın. Bu boyut, giriş yürütülebilir dosyasındaki talimat temsilinin bayt cinsinden boyutuyla aynı olabilir veya olmayabilir.

Alan tanımının merkezinde işlem kodu bölümü bulunur. Gördüğünüz gibi, şu ana kadar riscv32i içinde fence ve ebreak olmak üzere yalnızca iki işlem kodu (talimat) tanımlanmıştır. fence kodlayıcısı, ad (fence) ve operand spesifikasyonu ({: imm12 : }) ile birlikte isteğe bağlı olarak da disassemble biçimi ("fence") ve semantik işlev olarak bağlanacak çağrılabilir işlev ("&RV32IFence") belirtilerek tanımlanır.

Talimat işlenenleri, her bileşenin noktalı virgül ile ayrıldığı bir üçlü olarak belirtilir: kalıp ':' kaynak işlenen listesi ':' hedef işlenen listesi. Kaynak ve hedef işlenen listeleri, işlem gören adlarının virgülle ayrılmış listeleridir. Gördüğünüz gibi, fence talimatının talimat operatörleri hiçbir önerme operatörü içermez, yalnızca tek bir kaynak operatör adı imm12 ve hedef operatör içermez. RiscV RV32I alt kümesi, önerme yürütmeyi desteklemediğinden bu eğitimde önerme operanı her zaman boş olacaktır.

Anlamsal işlev, C++ işlevini belirtmek veya anlamsal işlevi çağırmak için kullanılacak çağrıyı yapmak için gereken dize olarak belirtilir. Anlamsal işlevin/çağrılanabilirin imzası void(Instruction *) şeklindedir.

Sökme spesifikasyonu, virgülle ayrılmış bir dize listesinden oluşur. Genellikle biri işlem kodu, diğeri işlenenler için olmak üzere yalnızca iki dize kullanılır. Biçimlendirildiğinde (Talimat'ta AsString() çağrısı kullanılarak) her dize, yukarıda açıklanan disasm widths spesifikasyonuna göre bir alan içinde biçimlendirilir.

Aşağıdaki alıştırmalar, riscv32i.isa dosyasına bir "Hello World" programını simüle etmek için yeterli olacak talimatları eklemenize yardımcı olur. Acele edenler için çözümleri riscv32i.isa ve rv32i_instructions.h dosyalarında bulabilirsiniz.


İlk Derlemeyi Gerçekleştirme

Dizini riscv_isa_decoder olarak değiştirmediyseniz bu işlemi şimdi yapın. Ardından projeyi aşağıdaki gibi derleyin. Bu derleme başarılı olacaktır.

$ cd riscv_isa_decoder
$ bazel build :all

Şimdi dizininizi depo köküne geri değiştirin ve oluşturulan kaynaklara göz atalım. Bunun için dizini bazel-out/k8-fastbuild/bin/riscv_isa_decoder olarak değiştirin (x86 ana makinesinde olduğunuz varsayıldığında; diğer ana makineler için k8-Fastbuild, başka bir dize olacaktır).

$ cd ..
$ cd bazel-out/k8-fastbuild/bin/riscv_isa_decoder

Bu dizinde, diğer dosyaların yanı sıra aşağıda oluşturulan C++ dosyaları bulunur:

  • riscv32i_decoder.h
  • riscv32i_decoder.cc
  • riscv32i_enums.h
  • riscv32i_enums.cc

Tarayıcıda riscv32i_enums.h'ü tıklayarak bu öğeye bakalım. Aşağıdaki gibi bir içerik göreceksiniz:

#ifndef RISCV32I_ENUMS_H
#define RISCV32I_ENUMS_H

namespace mpact {
namespace sim {
namespace codelab {
  enum class SlotEnum {
    kNone = 0,
    kRiscv32,
  };

  enum class PredOpEnum {
    kNone = 0,
    kPastMaxValue = 1,
  };

  enum class SourceOpEnum {
    kNone = 0,
    kCsr = 1,
    kImm12 = 2,
    kRs1 = 3,
    kPastMaxValue = 4,
  };

  enum class DestOpEnum {
    kNone = 0,
    kCsr = 1,
    kRd = 2,
    kPastMaxValue = 3,
  };

  enum class OpcodeEnum {
    kNone = 0,
    kCsrs = 1,
    kCsrsNw = 2,
    kCsrwNr = 3,
    kEbreak = 4,
    kFence = 5,
    kPastMaxValue = 6
  };

  constexpr char kNoneName[] = "none";
  constexpr char kCsrsName[] = "Csrs";
  constexpr char kCsrsNwName[] = "CsrsNw";
  constexpr char kCsrwNrName[] = "CsrwNr";
  constexpr char kEbreakName[] = "Ebreak";
  constexpr char kFenceName[] = "Fence";
  extern const char *kOpcodeNames[static_cast<int>(
      OpcodeEnum::kPastMaxValue)];

  enum class SimpleResourceEnum {
    kNone = 0,
    kPastMaxValue = 1
  };

  enum class ComplexResourceEnum {
    kNone = 0,
    kPastMaxValue = 1
  };

  enum class AttributeEnum {
    kPastMaxValue = 0
  };

}  // namespace codelab
}  // namespace sim
}  // namespace mpact

#endif  // RISCV32I_ENUMS_H

Gördüğünüz gibi, riscv32i.isa dosyasında tanımlanan her bir alan, işlem kodu ve işlenen, numaralandırma türlerinden birinde tanımlanır. Ayrıca, işlev kodu adlarının tümünü depolayan bir OpcodeNames dizisi vardır (riscv32i_enums.cc içinde tanımlanır). Diğer dosyalar, oluşturulan kod çözücüyü içerir. Bu konu, başka bir eğitimde daha ayrıntılı olarak ele alınacaktır.

Bazel Derleme Kuralı

Bazel'deki ISA kod çözücü hedefi, mpact-sim deposundaki mpact/sim/decoder/mpact_sim_isa.bzl kaynağından yüklenen mpact_isa_decoder adlı özel kural makrosu kullanılarak tanımlanır. Bu eğitim için riscv_isa_decoder/BUILD içinde tanımlanan derleme hedefi şu şekildedir:

mpact_isa_decoder(
    name = "riscv32i_isa",
    src = "riscv32i.isa",
    includes = [],
    isa_name = "RiscV32I",
    deps = [
        "//riscv_semantic_functions:riscv32i",
    ],
)

Bu kural, C++ kodunu oluşturmak için ISA ayrıştırıcı aracını ve oluşturucuyu çağırır, ardından oluşturulan kodu //riscv_isa_decoder:riscv32i_isa etiketini kullanarak diğer kuralların kullanabileceği bir kitaplığa derleyebilir. includes bölümü, kaynak dosyanın içerebileceği ek .isa dosyalarını belirtmek için kullanılır. isa_name, kod çözücünün oluşturulacağı kaynak dosyada hangi isa'nın (birden fazla belirtilirse) gerektiğini belirtmek için kullanılır.


Kayıt-Kayıt ALU Talimatları Ekleme

Şimdi riscv32i.isa dosyasına yeni talimatlar ekleme zamanı. İlk talimat grubu, add, and vb. gibi kaydedici-kaydedici ALU talimatlarından oluşur. RiscV32'de bunların tümü R türü ikili talimat biçimini kullanır:

31..25 24..20 19...15 14..12 11..7 6..0
7 5 5 3 5 7
func7 rs2 rs1 func3 . işlem kodu

.isa dosyası biçimden bağımsız bir kod çözücü oluşturmak için kullanılsa da girişleri yönlendirmek için ikili program biçimini ve düzenini göz önünde bulundurmak yararlı olur. Gördüğünüz gibi, talimat nesnelerini dolduran kod çözücüyle ilgili üç alan vardır: rs2, rs1 ve rd. Bu noktada, bu adları tüm talimatlarda aynı talimat alanlarında aynı şekilde kodlanan tam sayı kayıtları (bit dizileri) için kullanmayı seçeceğiz.

Ekleyeceğimiz talimatlar şunlardır:

  • add: Tam sayı ekleme.
  • and: Bit tabanlı ve.
  • or - bit tabanlı veya.
  • sll: Mantıksal sola kaydır.
  • sltu - "Küçüktür" olarak ayarla, imzasız.
  • sub: Tamsayı çıkarma işlemi.
  • xor: Bit tabanlı XOR.

Bu talimatların her biri, riscv32i yuvasının opcodes bölümüne eklenir. Her talimat için adı, kod komutlarını, kod çözme işlemini ve semantik işlevi belirtmemiz gerektiğini unutmayın. Ad kolaydır. Yukarıdaki kod adı adlarını kullanalım. Ayrıca hepsi aynı operatörü kullandığından operatör spesifikasyonu için { : rs1, rs2 : rd} kullanabiliriz. Bu, rs1 tarafından belirtilen kayıtlı kaynak operatörün, talimat nesnesinde kaynak operatör vektöründe 0 dizine sahip olacağı, rs2 tarafından belirtilen kayıtlı kaynak operatörün 1 dizine sahip olacağı ve rd tarafından belirtilen kayıtlı hedef operatörün, hedef operatör vektöründeki tek öğe olacağı (0 dizini) anlamına gelir.

Sırada, anlamsal fonksiyon spesifikasyonu var. Bu işlem, semfunc anahtar kelimesi ve bir std::function öğesine atamak için kullanılabilecek bir çağrılabilir olduğunu belirten C++ dizesi kullanılarak yapılır. Bu eğitimde işlevler kullanacağımız için çağrılabilir dize "&MyFunctionName" olacaktır. fence talimatında önerilen adlandırma düzeni kullanılarak bu öğeler "&RV32IAdd", "&RV32IAnd" vb. olmalıdır.

Son olarak da sökme spesifikasyonu yer alır. disasm anahtar kelimesiyle başlar ve ardından talimatın dize olarak nasıl yazdırılması gerektiğini belirten, virgülle ayrılmış bir dize listesi gelir. Bir operatör adının önünde % işareti kullanılması, söz konusu operatörün dize temsilinin kullanıldığı bir dize değiştirme işlemini gösterir. add talimatı için bu değer şu şekilde olacaktır: disasm: "add", "%rd, %rs1,%rs2". Bu, add talimatının girişinin aşağıdaki gibi görünmesi gerektiği anlamına gelir:

    add{ : rs1, rs2 : rd},
      semfunc: "&RV32IAdd",
      disasm: "add", "%rd, %rs1, %rs2";

riscv32i.isa dosyasını düzenleyin ve bu talimatların tümünü .isa açıklamasına ekleyin. Yardıma ihtiyacınız varsa (veya çalışmanızı kontrol etmek istiyorsanız) tam açıklama dosyasını buradan indirebilirsiniz.

Talimatlar riscv32i.isa dosyasına eklendikten sonra, "../semantic_functions/" içinde bulunan rv32i_instructions.h dosyasına başvurulan yeni anlamsal işlevlerin her biri için işlev bildirimleri eklemeniz gerekir. Yardıma ihtiyacınız olursa (veya çalışmalarınızı kontrol etmek isterseniz) burada yanıt bulabilirsiniz.

Tüm bu işlemler tamamlandığında riscv_isa_decoder dizinine geri dönüp yeniden oluşturun. Oluşturulan kaynak dosyaları inceleyebilirsiniz.


Anında ALU Talimatları Ekleme

Ekleyeceğimiz bir sonraki talimat grubu, kaydedicilerden biri yerine anında değer kullanan ALU talimatlarıdır. Bu talimatların üç grubu vardır (acil alana bağlı olarak): 12 bit imzalı anlık talimatlar, kaymalar için anında özel I-Type talimatları ve 20 bit imzasız anlık değere sahip U-Type. Biçimlerin listesi aşağıda verilmiştir:

I-Type'ın anlık biçimi:

31..20 19...15 14..12 11..7 6..0
12 5 3 5 7
imm12 rs1 func3 . işlem kodu

Özel I-Type hızlı biçimi:

31..25 24..20 19...15 14..12 11..7 6..0
7 5 5 3 5 7
func7 uimm5 rs1 func3 . işlem kodu

U-Type hızlı biçimi:

31...12 11..7 6..0
20 5 7
uimm20 . işlem kodu

Gördüğünüz gibi, rs1 ve rd operanı adları daha önce olduğu gibi aynı bit alanlarını ifade eder ve tam sayı kaydedicilerini temsil etmek için kullanılır. Bu nedenle bu adlar korunabilir. Doğrudan değer alanları farklı uzunluk ve konumdadır ve iki (uimm5 ve uimm20) imzasız, imm12 ise imzalıdır. Bunların her biri kendi adını kullanır.

Bu nedenle, I-Type talimatlarının operantları { : rs1, imm12 :rd } olmalıdır. Özel I-Type talimatları için { : rs1, uimm5 : rd} olmalıdır. U-Type talimat operanı spesifikasyonu { : uimm20 : rd } olmalıdır.

Eklememiz gereken I-Type talimatları şunlardır:

  • addi - Hemen ekleyin.
  • andi - Bit tabanlı ve anında.
  • ori: Bit tabanlı veya anında.
  • xori: Anlık değerle bit tabanlı XOR.

Eklememiz gereken özel I-Type talimatları şunlardır:

  • slli - Hemen sola mantıksal olarak kaydırır.
  • srai - Hemen değere göre sağa doğru aritmetik kaydır.
  • srli - Hemen sağa kaydırır.

Eklememiz gereken U-Type talimatları şunlardır:

  • auipc - Üst acil durumu PC'ye ekleyin.
  • lui - Hemen üst öğeyi yükle.

İşlemler için kullanılacak adlar, yukarıdaki talimat adlarından doğal bir şekilde gelir (yeni adlar bulmanıza gerek yoktur, hepsi benzersizdir). Anlamsal işlevleri belirtme konusunda, talimat nesnelerinin kaynak operatörlere arayüzleri kodladığını ve bu arayüzlerin temel operatör türüne bağlı olmadığını unutmayın. Bunun anlamı, aynı işlemi yapan ancak operand türlerinde farklılık gösterebilen talimatların aynı semantik işlevi paylaşabileceğidir. Örneğin, operand türü göz ardı edilirse addi talimatı add talimatıyla aynı işlemi gerçekleştirir. Bu nedenle, aynı semantik işlev spesifikasyonunu "&RV32IAdd" kullanabilirler. Benzer şekilde andi, ori, xori ve slli için de geçerlidir. Diğer talimatlar yeni anlamsal işlevler kullanır, ancak işlem görenlere göre değil işleme göre adlandırılmalıdır. Bu nedenle srai için "&RV32ISra" kullanın. U-Type talimatları auipc ve lui, kayıt eşdeğerlerine sahip olmadığından "&RV32IAuipc" ve "&RV32ILui" kullanılabilir.

Disassamble dizeleri önceki alıştırmadakilere çok benzer ancak tahmin edebileceğiniz gibi %rs2 referansları uygun şekilde %imm12, %uimm5 veya %uimm20 ile değiştirilmiştir.

Değişiklikleri yapıp derleyin. Oluşturulan çıkışı kontrol edin. Daha önce olduğu gibi çalışmanızı riscv32i.isa ve rv32i_instructions.h ile karşılaştırabilirsiniz.


Eklememiz gereken dal ve atlama ve bağlantı talimatları, yalnızca talimatın kendisinde ima edilen bir hedef işleneni, yani sonraki pc değerini kullanır. Bu aşamada, bunu next_pc adında uygun bir operatör olarak ele alacağız. Bu konu, daha sonraki bir eğitimde ayrıntılı olarak açıklanacaktır.

Şube Talimatları

Eklediğimiz tüm dallar B türü kodlamayı kullanır.

31 30,25 24..20 19...15 14..12 11..8 7 6..0
1 6 5 5 3 4 1 7
imm imm rs2 rs1 func3 imm imm işlem kodu

Farklı anında alanlar, 12 bitlik işaretli bir anında değere birleştirilir. Biçim gerçekten alakalı olmadığından bunu hemen 12 bit dal için bimm12 olarak adlandıracağız. Parçalara ayırma işlemi, ikili kod çözücü oluşturmayla ilgili bir sonraki eğitimde ele alınacaktır. Tüm dallanma talimatları, rs1 ve rs2 tarafından belirtilen tam sayı kaydedicilerini karşılaştırır. Koşul doğruysa anında değer, yürütülecek bir sonraki talimatın adresini oluşturmak için geçerli pc değerine eklenir. Bu nedenle, dallanma talimatlarının operantları { : rs1, rs2, bimm12 : next_pc } olmalıdır.

Eklememiz gereken dal talimatları şunlardır:

  • beq: Eşitse dal.
  • bge - Büyükse veya eşitse dal.
  • bgeu - Büyüktür veya eşittir imzalanmamışsa şube.
  • blt - Küçükse şubelere ayırın.
  • bltu: İmzasızsa dal.
  • bne: Eşit değilse dal.

Bu kod adı adlarının tümü benzersiz olduğundan .isaaçıklamasında yeniden kullanılabilir. Elbette yeni anlamsal işlev adları da eklenmelidir (ör. "&RV32IBeq" vb.

Talimatın adresi, hedefi hesaplamak için kullanıldığından, aslında talimat işlenenlerinin bir parçası olmadığı için sökme spesifikasyonu artık biraz daha karmaşıktır. Ancak, talimat nesnesinde depolanan bilgilerin bir parçası olduğu için kullanılabilir. Çözüm, disassemblasyon dizesinde ifade söz dizimini kullanmaktır. Ardından "%" ve işlem gören adı kullanmak yerine %(ifade: yazdırma biçimi) yazabilirsiniz. Yalnızca çok basit ifadeler desteklenir ancak adres artı ofseti bunlardan biridir ve geçerli talimat adresi için @ simgesi kullanılır. Yazdırma biçimi, C stilindeki baskı biçimlerine benzer ancak başında % bulunmaz. beq talimatının sökme biçimi şöyle olur:

    disasm: "beq", "%rs1, %rs2, %(@+bimm12:08x)"

Yalnızca iki atlama ve bağlantı talimatı eklenmesi gerekir: jal (atlama ve bağlantı) ve jalr (dolaylı atlama ve bağlantı).

jal talimatı J türü kodlamayı kullanır:

31 30...21 20 19..12 11..7 6..0
1 10 1 8 5 7
imm imm imm imm . işlem kodu

Dal talimatlarında olduğu gibi 20 bit'lik anlık dosya da birden çok alana bölünmüştür. Bu nedenle jimm20 olarak adlandıracağız. Parçalara ayırma işlemi şu anda önemli değildir ancak ikili kod çözücü oluşturmayla ilgili bir sonraki eğitimde ele alınacaktır. Operand spesifikasyonu { : jimm20 : next_pc, rd } olur. Sonraki bilgisayar değeri ve talimatta belirtilen bağlantı kaydı olmak üzere iki hedef işleneni olduğunu unutmayın.

Yukarıdaki dal talimatlarına benzer şekilde, ayırma biçimi şu şekilde olur:

    disasm: "jal", "%rd, %(@+jimm20:08x)"

Dolaylı atlama ve bağlantı, hemen 12 bit ile I-Type biçimini kullanır. Hedef talimat adresini oluşturmak için işaretli genişletilmiş anlık değeri rs1 tarafından belirtilen tam sayı kaydediciye ekler. Bağlantı kaydı, rd tarafından belirtilen tam sayı kaydıdır.

31..20 19...15 14..12 11..7 6..0
12 5 3 5 7
imm12 rs1 func3 . işlem kodu

Kalıbı görmüşseniz, jalr için işlenen spesifikasyonun { : rs1, imm12 : next_pc, rd } olması gerektiği ve sökme teknik özelliklerinin gerektiği sonucuna varabilirsiniz:

    disasm: "jalr", "%rd, %rs1, %imm12"

Değişiklikleri yapın ve ardından derleyin. Oluşturulan çıkışı kontrol edin. Daha önce olduğu gibi, çalışmanızı riscv32i.isa ve rv32i_instructions.h ile karşılaştırabilirsiniz.


Mağaza Talimatları Ekleyin

Mağaza talimatları çok basittir. Tümü S-Type biçimini kullanır:

31..25 24..20 19...15 14..12 11..7 6..0
7 5 5 3 5 7
imm rs2 rs1 func3 imm işlem kodu

Gördüğünüz gibi bu, parçalara ayrılmış 12 bit'in başka bir örneğidir. simm12 diyelim. Tüm depolama talimatları, rs2 tarafından belirtilen tam sayı kaydedicinin değerini, rs1 tarafından belirtilen tam sayı kaydedicinin değerine 12 bitlik anında değerin işaretli olarak genişletilmiş değeri eklenerek elde edilen bellekteki etkili adrese depolar. Tüm mağaza talimatları için operant biçimi { : rs1, simm12, rs2 } olmalıdır.

Uygulanması gereken mağaza talimatları şunlardır:

  • sb - Depolama baytı.
  • sh - Yarım kelime depolayın.
  • sw: Mağaza kelimesi.

sb için ayrıştırma spesifikasyonu aşağıdaki gibidir:

    disasm: "sb", "%rs2, %simm12(%rs1)"

Anlamsal işlev spesifikasyonları da beklediğiniz gibidir: "&RV32ISb" vb.

Değişiklikleri yapın ve sonra oluşturun. Oluşturulan çıkışı kontrol edin. Daha önce olduğu gibi çalışmanızı riscv32i.isa ve rv32i_instructions.h ile karşılaştırabilirsiniz.


Yükleme Talimatları Ekle

Yükleme talimatları, simülatördeki diğer talimatlardan biraz farklı şekilde modellenmiştir. Yükleme gecikmesinin belirsiz olduğu durumları modelleyebilmek için yükleme talimatları iki ayrı işleme ayrılır: 1) etkili adres hesaplaması ve bellek erişimi ve 2) sonuç geri alma. Simülatörde bu, yüklemenin anlamsal eylemi ana talimat ve alt talimat olmak üzere iki ayrı talimata bölünerek yapılır. Ayrıca, işlenenleri belirttiğimizde bunları hem ana hem de alt talimat için belirtmemiz gerekir. Bu işlem, operand spesifikasyonu üçlü liste olarak ele alınarak yapılır. Söz dizimi:

{(predicate : sources : destinations), (predicate : sources : destinations), ... }

Yükleme talimatlarının tümü, önceki talimatların çoğunda olduğu gibi I-Type biçimini kullanır:

31...20 19...15 14..12 11..7 6..0
12 5 3 5 7
imm12 rs1 func3 . işlem kodu

İşlem gören spesifikasyonu, adresi hesaplamak ve yükleme verileri için kayıt hedefinden bellek erişimini başlatmak için gereken işlem görenleri böler: {( : rs1, imm12 : ), ( : : rd) }.

Anlamsal işlem iki talimata bölündüğünden, anlamsal işlevlerin de iki çağrılabilir özelliği belirtmesi gerekir. lw (kelime yükle) için şu şekilde yazılır:

    semfunc: "&RV32ILw", "&RV32ILwChild"

Demonte etme spesifikasyonu daha gelenekseldir. Alt talimatlardan bahsedilmiyor. lw için:

    disasm: "lw", "%rd, %imm12(%rs1)"

Uygulanması gereken yükleme talimatları şunlardır:

  • lb - Bayt yükleme.
  • lbu - Yükleme baytı imzasız.
  • lh - Yarım kelime yükleme.
  • lhu - Yarım kelimeyi imzalanmamış olarak yükleyin.
  • lw - Kelime yükle.

Değişiklikleri yapın ve sonra oluşturun. Oluşturulan çıkışı kontrol edin. Daha önce olduğu gibi, çalışmanızı riscv32i.isa ve rv32i_instructions.h ile karşılaştırabilirsiniz.

Bu noktaya ulaştığınız için teşekkür ederiz. Bu bilgilerin işinize yarayacağını umuyoruz.