RiscV Entegre Kod Çözücü

Bu eğitimin hedefleri şunlardır:

  • Oluşturulan ISA ve ikili kod çözücülerin birlikte nasıl çalıştığını öğrenin.
  • RiscV için tam talimat kod çözücü oluşturmak üzere gerekli C++ kodunu yazma ISA ve ikili kod çözücüleri birleştiren RV32I.

Talimat kod çözücüyü anlama

Talimat kod çözücü, bir talimat adresi verilir ve komutun tam olarak başlatılmış bir örneğini döndürür. Instruction belirtir.

Üst düzey kod çözücü, aşağıda gösterilen generic::DecoderInterface parametresini uygular:

// This is the simulator's interface to the instruction decoder.
class DecoderInterface {
 public:
  // Return a decoded instruction for the given address. If there are errors
  // in the instruciton decoding, the decoder should still produce an
  // instruction that can be executed, but its semantic action function should
  // set an error condition in the simulation when executed.
  virtual Instruction *DecodeInstruction(uint64_t address) = 0;
  virtual ~DecoderInterface() = default;
};

Gördüğünüz gibi, uygulanması gereken yalnızca bir yöntem vardır: cpp virtual Instruction *DecodeInstruction(uint64_t address);

Şimdi, oluşturulan kod için nelerin sağlandığına ve nelerin gerektiğine bakalım.

Öncelikle, dosyada üst düzey RiscV32IInstructionSet sınıfını düşünün riscv32i_decoder.h ISA kod çözücü. İçeriği yeniden görmek için çözüm dizinine gidin: yeniden oluşturmaya alışkın.

$ cd riscv_isa_decoder/solution
$ bazel build :all
...<snip>...

Şimdi dizininizi depo köküyle değiştirin ve bir bakalım. oluşturulan kaynaklarda gösterilir. Bunun için, dizini bazel-out/k8-fastbuild/bin/riscv_isa_decoder (x86 kullandığınız varsayılır) ana makine - diğer ana makineler için k8-Fastbuild, başka bir dize olacaktır).

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

Oluşturulan C++ kodunu içeren dört kaynak dosyasının listelendiğini göreceksiniz:

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

İlk dosyayı (riscv32i_decoder.h) açın. Yalnızca üç tür şunlara bakmalısınız:

  • RiscV32IEncodingBase
  • RiscV32IInstructionSetFactory
  • RiscV32IInstructionSet

Sınıfların adlarını not edin. Tüm sınıflar aşağıdakilere göre adlandırılır: "Isa" adında belirtilen adın pascal büyük harf sürümü beyanda bulun: isa RiscV32I { ... }.

İlk olarak RiscVIInstructionSet sınıfıyla başlayalım. Aşağıda gösterilmiştir:

class RiscV32IInstructionSet {
 public:
  RiscV32IInstructionSet(ArchState *arch_state,
                         RiscV32IInstructionSetFactory *factory);
  Instruction *Decode(uint64 address, RiscV32IEncodingBase *encoding);

 private:
  std::unique_ptr<Riscv32Slot> riscv32_decoder_;
  ArchState *arch_state_;
};

Bu sınıfta sanal yöntemler bulunmadığından bu tek başına bir sınıftır ancak iki şeye dikkat etmek gerekir. İlk olarak, oluşturucu bir işaretçiyi RiscV32IInstructionSetFactory sınıf. Bu sınıf, oluşturulan kod çözücü, RiscV32Slot sınıfının bir örneğini oluşturmak için kullanır. Bu örnek, slot RiscV32 için tanımlanmış tüm talimatların kodunu riscv32i.isa dosyası. İkinci olarak, Decode yöntemi ek bir parametre alır türü RiscV32IEncodingBase ise bu, ilk eğiticide oluşturulan isa kod çözücü ve ikili program arasındaki arayüz kod çözücüyü elde edeceksiniz.

RiscV32IInstructionSetFactory sınıfı, soyut bir sınıftır. tam kod çözücü için kendi uygulamamızı türetmemiz gerekir. Çoğu durumda bu çok önemlidir: her öğe için kurucuyu çağıracak bir yöntem sağlayın .isa dosyamızda tanımlanan alan sınıfını kullanın. Bizim örneğimizde, bu türde tek bir sınıftır: Riscv32Slot (riscv32 adının büyük/küçük harf düzeni) Slot ile birleştirilmelidir). Bu yöntem şu nedenle sizin için oluşturulmadı: alt sınıf türetmenin yararlı olabileceği bazı gelişmiş kullanım durumları ve kurucusu çağrılıyor.

RiscV32IEncodingBase başlıklı son dersi bu kursun ilerleyen bölümlerinde inceleyeceğiz. çünkü bu, başka bir alıştırmanın konusu.


Üst düzey talimat kod çözücüyü tanımlama

Fabrika sınıfını tanımlama

İlk eğitim için projeyi yeniden oluşturduysanız riscv_full_decoder dizini.

riscv32_decoder.h dosyasını açın. Gerekli tüm dahil etme dosyaları, ve ad alanları ayarlandı.

//Exercise 1 - step 1 olarak işaretlenen yorumdan sonra sınıfı tanımlayın. RiscV32IsaFactory, RiscV32IInstructionSetFactory kaynağından devralıyor.

class RiscV32IsaFactory : public RiscV32InstructionSetFactory {};

Sonra, CreateRiscv32Slot için geçersiz kılmayı tanımlayın. Herhangi bir türetilmiş Riscv32Slot sınıflarını içeriyorsa yeni bir örnek ayırmak için std::make_unique.

std::unique_ptr<Riscv32Slot> CreateRiscv32Slot(ArchState *) override {
  return std::make_unique<Riscv32Slot>(state);
}

Yardıma ihtiyacınız olursa (veya çalışmalarınızı kontrol etmek isterseniz) tam yanıt burada bulabilirsiniz.

Kod çözücü sınıfını tanımlama

Oluşturucu, yıkıcı ve yöntem bildirimleri

Şimdi sırada kod çözücü sınıfı var. Yukarıdakiyle aynı dosyada RiscV32Decoder beyanı. Beyanı bir sınıf tanımına genişletme Burada RiscV32Decoder, şuradan devralınır: generic::DecoderInterface.

class RiscV32Decoder : public generic::DecoderInterface {
  public:
};

Ardından, oluşturucuyu yazmadan önce, koda hızlıca göz atalım konulu videomuzu izleyin. YouTube'da Extract işlevleri varsa DecodeRiscVInst32 fonksiyonu vardır:

OpcodeEnum DecodeRiscVInst32(uint32_t inst_word);

Bu işlev, kodu çözülmesi gereken talimat kelimesini alır ve o talimatla eşleşen işlem kodunu içerir. Diğer yandan, RiscV32Decoder tarafından uygulanan DecodeInterface sınıf yalnızca girin. Bu nedenle, RiscV32Decoder sınıfının DecodeRiscVInst32() cihazına iletmek için talimat kelimesini okuyun. Bu projede belleğe erişmenin yolu, bu sayfada tanımlanan basit bir bellek arayüzü Uygun şekilde util::MemoryInterface olarak adlandırılmış .../mpact/sim/util/memory (aşağıda görüldüğü gibi):

  // Load data from address into the DataBuffer, then schedule the Instruction
  // inst (if not nullptr) to be executed (using the function delay line) with
  // context. The size of the data access is based on size of the data buffer.
  virtual void Load(uint64_t address, DataBuffer *db, Instruction *inst,
                    ReferenceCount *context) = 0;

Buna ek olarak, bir state sınıfı örneğini oluşturucu sınıflarının kurucuları arasında yer alır. Uygun eyalet sınıfı generic::ArchState alanından türetilen riscv::RiscVState sınıfı ve eklenmiş özellikleri hakkında daha fazla bilgi edinin. Bu, oluşturucuyu bildirmemiz gerektiği anlamına gelir. state ve memory için bir işaretçi alabilir:

RiscV32Decoder(riscv::RiscVState *state, util::MemoryInterface *memory);

Varsayılan oluşturucuyu silin ve yıkıcıyı geçersiz kılın:

RiscV32Decoder() = delete;
~RiscV32Decoder() override;

Sonra, geçersiz kılmamız gereken DecodeInstruction yöntemini tanımlayın generic::DecoderInterface.

generic::Instruction *DecodeInstruction(uint64_t address) override;

Yardıma ihtiyacınız olursa (veya çalışmalarınızı kontrol etmek isterseniz) tam yanıt burada bulabilirsiniz.


Veri Üyesi Tanımları

RiscV32Decoder sınıfının depolaması için gizli veri üyelerinin olması gerekir. oluşturucu parametrelerini ve fabrika sınıfını gösteren bir işaretçiyi içerir.

 private:
  riscv::RiscVState *state_;
  util::MemoryInterface *memory_;

Ayrıca, RiscV32IEncodingBase, buna RiscV32IEncoding ( ). Ayrıca, bu öğenin bir RiscV32IInstructionSet için şunu ekleyin:

  RiscV32IsaFactory *riscv_isa_factory_;
  RiscV32IEncoding *riscv_encoding_;
  RiscV32IInstructionSet *riscv_isa_;

Son olarak, bellek arayüzümüzle kullanılacak bir veri üyesi tanımlamamız gerekir:

  generic::DataBuffer *inst_db_;

Yardıma ihtiyacınız olursa (veya çalışmalarınızı kontrol etmek isterseniz) tam yanıt burada bulabilirsiniz.

Kod Çözücü Sınıfı Yöntemlerini Tanımlama

Ardından sıra, oluşturucuyu, yıkıcıyı ve projeyi DecodeInstruction yöntemini çağırın. riscv32_decoder.cc dosyasını açın. Boş yöntemlerinin yanı sıra ad alanı beyanları ve birkaç using beyan.

Oluşturucu Tanımı

Oluşturucunun yalnızca veri üyelerini ilk kullanıma hazırlaması gerekir. Önce state_ ve memory_:

RiscV32Decoder::RiscV32Decoder(riscv::RiscVState *state,
                               util::MemoryInterface *memory)
    : state_(state), memory_(memory) {

Daha sonra, kod çözücüyle ilişkili sınıfların her birinin örneklerini ayırın ve uygun parametreler olur.

  // Allocate the isa factory class, the top level isa decoder instance, and
  // the encoding parser.
  riscv_isa_factory_ = new RiscV32IsaFactory();
  riscv_isa_ = new RiscV32IInstructionSet(state, riscv_isa_factory_);
  riscv_encoding_ = new RiscV32IEncoding(state);

Son olarak, DataBuffer örneğini ayırın. Bir fabrika kullanılarak tahsis edilmiştir state_ üyesi üzerinden erişilebilir. Mağazaya aynı boyutta bir veri tamponu ayırırız tek bir uint32_t; talimat kelimesinin boyutu bu kadardır.

  inst_db_ = state_->db_factory()->Allocate<uint32_t>(1);

Yıkıcı Tanımı

Yıkıcı basittir; yalnızca oluşturucuda ayırdığımız nesneleri serbest bırakırız, bir sürprizimiz var. Veri arabellek örneği referans sayılır, dolayısıyla bunun yerine delete öğesini çağırarak nesneyi DecRef().

RiscV32Decoder::~RiscV32Decoder() {
  inst_db_->DecRef();
  delete riscv_isa_;
  delete riscv_isa_factory_;
  delete riscv_encoding_;
}

Yöntem tanımı

Bizim örneğimizde bu yöntemin uygulanması oldukça basittir. Bir önceki videoda olduğundan ve ek bir hata kontrolünün yapılmadığından gereklidir.

Öncelikle, talimat kelimesinin bellek kullanılarak bellekten alınması gerekir arayüzü ve DataBuffer örneği.

  memory_->Load(address, inst_db_, nullptr, nullptr);
  uint32_t iword = inst_db_->Get<uint32_t>(0);

Daha sonra, talimat kelimesini ayrıştırmak için RiscVIEncoding örneğini çağırırız. Bu işlem, ISA kod çözücüyü çağrılmadan önce yapılmalıdır. ISA'nın, kod çözücü çağrıları, işlem kodunu almak için doğrudan RiscVIEncoding örneğine ve işlem görenleri ile gösterilir. Bunu sınıfını girelim, ama bu yöntem olarak void ParseInstruction(uint32_t) kullanalım.

  riscv_encoding_->ParseInstruction(iword);

Son olarak, adresi ve Kodlama sınıfını ileten ISA kod çözücüyü diyoruz.

  auto *instruction = riscv_isa_->Decode(address, riscv_encoding_);
  return instruction;

Yardıma ihtiyacınız olursa (veya çalışmalarınızı kontrol etmek isterseniz) tam yanıt burada bulabilirsiniz.


Kodlama sınıfı

Kodlama sınıfı, kod çözücü sınıfı tarafından kullanılan bir arayüz uygular işlem kodunu, kaynak ve hedef işlenenlerini ve kaynak işlenenleridir. Bu nesnelerin tümü ikili programdan alınan bilgilere kod çözücü biçimini; işlem kodu gibi, Talimat kelimesi vb. bulunur. Bu, kod çözücü sınıfından ayrılır. kodlamadan bağımsız olarak ve birden fazla farklı kodlama şemasının desteklenmesini sağlar daha avantajlı bir konumda olursunuz.

RiscV32IEncodingBase, soyut bir sınıf. Yapmamız gereken yöntemler, aşağıdaki gösterilmiştir.

class RiscV32IEncodingBase {
 public:
  virtual ~RiscV32IEncodingBase() = default;

  virtual OpcodeEnum GetOpcode(SlotEnum slot, int entry) = 0;

  virtual ResourceOperandInterface *
              GetSimpleResourceOperand(SlotEnum slot, int entry, OpcodeEnum opcode,
                                       SimpleResourceVector &resource_vec, int end) = 0;

  virtual ResourceOperandInterface *
              GetComplexResourceOperand(SlotEnum slot, int entry, OpcodeEnum opcode,
                                        ComplexResourceEnum resource_op,
                                        int begin, int end) = 0;

  virtual PredicateOperandInterface *
              GetPredicate(SlotEnum slot, int entry, OpcodeEnum opcode,
                           PredOpEnum pred_op) = 0;

  virtual SourceOperandInterface *
              GetSource(SlotEnum slot, int entry, OpcodeEnum opcode,
                        SourceOpEnum source_op, int source_no) = 0;

  virtual DestinationOperandInterface *
              GetDestination(SlotEnum slot, int entry, OpcodeEnum opcode,
                             DestOpEnum dest_op, int dest_no, int latency) = 0;

  virtual int GetLatency(SlotEnum slot, int entry, OpcodeEnum opcode,
                         DestOpEnum dest_op, int dest_no) = 0;
};

İlk bakışta biraz karmaşık görünüyor, özellikle de ancak RiscV gibi basit bir mimari için çoğu değerleri belirtileceği için bu parametrelerin parametrelerini değiştirebilirsiniz.

Bu yöntemlerin her birini sırayla inceleyelim.

OpcodeEnum GetOpcode(SlotEnum slot, int entry);

GetOpcode yöntemi, geçerli öğe için OpcodeEnum üyesini döndürür. talimat, talimat işlem kodunu tanımlama. OpcodeEnum sınıfı oluşturulan riscv32i_enums.h kod çözücü dosyasında tanımlanmıştır. Yöntem, ve bunların her ikisi de amaçlarımız doğrultusunda yoksayılabilir. İlk alan türüdür (riscv32i_enums.h içinde de tanımlanan bir enum sınıfı), Bu, RiscV'de yalnızca tek bir yuva olduğu için tek bir olası değer vardır: SlotEnum::kRiscv32. İkincisi, alanın örnek numarasıdır ( slotun birden fazla örneği vardır ve bu, bazı VLIW'larda görülebilir mimariler).

ResourceOperandInterface *
    GetSimpleResourceOperand(SlotEnum slot, int entry, OpcodeEnum opcode,
                                     SimpleResourceVector &resource_vec, int end)

ResourceOperandInterface *
    GetComplexResourceOperand(SlotEnum slot, int entry, OpcodeEnum opcode,
                                      ComplexResourceEnum resource_op,
                                      int begin, int end);

Diğer iki yöntem, işlemcideki donanım kaynaklarını modellemek için kullanılır kullanın. Eğitim alıştırmalarımız için Bu nedenle, uygulamada bunlar soğur ve nullptr olarak döndürülür.

PredicateOperandInterface *
            GetPredicate(SlotEnum slot, int entry, OpcodeEnum opcode,
                         PredOpEnum pred_op);

SourceOperandInterface *
            GetSource(SlotEnum slot, int entry, OpcodeEnum opcode,
                      SourceOpEnum source_op, int source_no);

DestinationOperandInterface *
            GetDestination(SlotEnum slot, int entry, OpcodeEnum opcode,
                           DestOpEnum dest_op, int dest_no, int latency);

Bu üç yöntem, içinde kullanılan işlenen nesnelere işaretçiler döndürür talimatın semantik fonksiyonlarını kullanarak bir talimatın değerine erişimi koşul işleneni, talimat kaynağı işlenenlerinin her biri ve yeni veri işleme değerlerini talimatın hedef işlem görenlerine bağlayın. RiscV, bu yöntemin yalnızca nullptr döndürmesi gerekir.

Bu işlevlerde parametrelerin kalıbı benzerdir. İlk olarak, GetOpcode alanı ve giriş iletilir. Ardından, işlenenin oluşturulması gereken talimat. Bu yalnızca farklı işlem kodlarının aynı işlem gören için farklı işlenen nesneler döndürmesi gerekir. bu RiscV simülatöründe böyle bir durum yoktur.

Ardından, yüklem, kaynak ve hedef, işlem gören numaralandırma oluşturulması gereken işleneni tanımlar. Bunlar üç farklı platformdan riscv32i_enums.h içindeki OpEnums aşağıda gösterildiği gibi:

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

  enum class SourceOpEnum {
    kNone = 0,
    kBimm12 = 1,
    kCsr = 2,
    kImm12 = 3,
    kJimm20 = 4,
    kRs1 = 5,
    kRs2 = 6,
    kSimm12 = 7,
    kUimm20 = 8,
    kUimm5 = 9,
    kPastMaxValue = 10,
  };

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

İlk olarak riscv32.isa dosyası alırsanız bunların, kaynak ve hedef kümelerine karşılık geldiğini işlem gören adları için kullanılabilir. Farklı farklı bit alanlarını ve işleneni temsil eden işlenenler için işlem gören adları enum üyesi benzersiz bir şekilde eşlendiğinden kodlama sınıfının yazılmasını kolaylaştırır. döndürülecek tam işlenen türünü belirler ve slot, giriş veya işlem kodu parametrelerinin değerlerini göz önünde bulundurun.

Son olarak, kaynak ve hedef işlenenler için işleneni (tekrar, bunu göz ardı edebiliriz) ve hedef için işlenen, talimatın çalıştırıldığı zaman dilimi arasında geçen gecikme (döngüler halinde) ve hedef sonuç sonraki talimatlarda kullanılabilir. Simülasyon aracımızda bu gecikme 0 olacaktır. Yani talimat, kayıt içine alır.

int GetLatency(SlotEnum slot, int entry, OpcodeEnum opcode,
                         DestOpEnum dest_op, int dest_no);

Son fonksiyon, belirli bir hedefin gecikmesini almak için kullanılır .isa dosyasında * olarak belirtilmişse işlenen. Bu nadir bir durumdur ve bu RiscV simülatörü için kullanılmaz. Bu nedenle, değeri 0 değerini döndürür.


Kodlama sınıfını tanımlama

Başlık dosyası (.h)

Yöntemler

riscv32i_encoding.h dosyasını açın. Gerekli tüm dahil etme dosyaları, ve ad alanları ayarlandı. Yapılan tüm kod eklemeleri // Exercise 2. yorumu takip edildi

İlk olarak, RiscV32IEncoding geliştirilmiştir.

class RiscV32IEncoding : public RiscV32IEncodingBase {
 public:

};

Ardından, kurucu, bu örnekte durum örneğine bir işaretçi almalıdır. riscv::RiscVState için bir işaretçi. Varsayılan yıkıcı kullanılmalıdır.

explicit RiscV32IEncoding(riscv::RiscVState *state);
~RiscV32IEncoding() override = default;

Tüm arayüz yöntemlerini eklemeden önce, Talimatı ayrıştırmak için RiscV32Decoder:

void ParseInstruction(uint32_t inst_word);

Daha sonra, önemsiz geçersiz kılmalara sahip olan bu yöntemleri eklerken kullanılmayan parametrelerin adları:

// Trivial overrides.
ResourceOperandInterface *GetSimpleResourceOperand(SlotEnum, int, OpcodeEnum,
                                                   SimpleResourceVector &,
                                                   int) override {
  return nullptr;
}

ResourceOperandInterface *GetComplexResourceOperand(SlotEnum, int, OpcodeEnum,
                                                    ComplexResourceEnum ,
                                                    int, int) override {
  return nullptr;
}

PredicateOperandInterface *GetPredicate(SlotEnum, int, OpcodeEnum,
                                        PredOpEnum) override {
  return nullptr;
}

int GetLatency(SlotEnum, int, OpcodeEnum, DestOpEnum, int) override { return 0; }

Son olarak herkese açık arayüzün kalan yöntem geçersiz kılmalarını ekleyin, .cc dosyasına ertelenen uygulamaları içerir.


OpcodeEnum GetOpcode(SlotEnum, int) override;

SourceOperandInterface *GetSource(SlotEnum , int, OpcodeEnum,
                                  SourceOpEnum source_op, int) override;

DestinationOperandInterface *GetDestination(SlotEnum, int, OpcodeEnum,
                                            DestOpEnum dest_op, int,
                                            int latency) override;

Her bir işlenen alıcı yöntemlerinin uygulanmasını basitleştirmek için tarafından dizine eklenen iki çağrılanabilir (işlev nesneleri) dizisi oluşturacağız. sırasıyla SourceOpEnum ve DestOpEnum üyelerinin sayısal değeridir. Böylece, bu kişilerin organları yöntemlere indirgenerek, Geçirilen ve dönüşünü döndüren enum değeri için işlev nesnesi değer.

Bu iki dizinin ilk kullanıma hazırlanmasını düzenlemek için iki özel diziyi yöntemini çağırın:

 private:
  void InitializeSourceOperandGetters();
  void InitializeDestinationOperandGetters();

Veri üyeleri

Gerekli veri üyeleri aşağıdaki gibidir:

  • riscv::RiscVState * değerini korumak için state_.
  • Geçerli değerin bulunduğu uint32_t türündeki inst_word_ talimat kelimesi.
  • opcode_ ParseInstruction yöntemini kullanır. Bu, OpcodeEnum türünde.
  • Kaynağı almak için kullanılan çağrılanabilir öğelerin depolanacağı bir diziyi source_op_getters_ işlenen nesneleri içerir. Dizi öğelerinin türü: absl::AnyInvocable<SourceOperandInterface *>()>
  • dest_op_getters_ çağrılanabilir öğeleri depolamak için kullanılan bir dizi hedef işlenen nesneleri. Dizi öğelerinin türü: absl::AnyInvocable<DestinationOperandInterface *>()>
  • xreg_alias, RiscV tam sayı kaydı ABI adları dizisi, ör. "sıfır" ve "ra" "x0" yerine ve "x1" gibi.

  riscv::RiscVState *state_;
  uint32_t inst_word_;
  OpcodeEnum opcode_;

  absl::AnyInvocable<SourceOperandInterface *()>
      source_op_getters_[static_cast<int>(SourceOpEnum::kPastMaxValue)];
  absl::AnyInvocable<DestinationOperandInterface *(int)>
      dest_op_getters_[static_cast<int>(DestOpEnum::kPastMaxValue)];

  const std::string xreg_alias_[32] = {
      "zero", "ra", "sp", "gp", "tp",  "t0",  "t1", "t2", "s0", "s1", "a0",
      "a1",   "a2", "a3", "a4", "a5",  "a6",  "a7", "s2", "s3", "s4", "s5",
      "s6",   "s7", "s8", "s9", "s10", "s11", "t3", "t4", "t5", "t6"};

Yardıma ihtiyacınız olursa (veya çalışmalarınızı kontrol etmek isterseniz) tam yanıt burada bulabilirsiniz.

Kaynak dosya (.cc).

riscv32i_encoding.cc dosyasını açın. Gerekli tüm dahil etme dosyaları, ve ad alanları ayarlandı. Yapılan tüm kod eklemeleri // Exercise 2. yorumu takip edildi

Yardımcı işlevler

Görev oluşturmak için kullanacağımız birkaç yardımcı fonksiyon yazarak hedef ve hedef kayıt işlenenleri. Bunlar, kayıt türünü belirler ve RiscVState nesnesini çağırarak kayıt nesnesinde bir işlenen fabrika yöntemini çağırın.

Hedef işlem gören yardımcılarıyla başlayalım:

template <typename RegType>
inline DestinationOperandInterface *GetRegisterDestinationOp(
    RiscVState *state, const std::string &name, int latency) {
  auto *reg = state->GetRegister<RegType>(name).first;
  return reg->CreateDestinationOperand(latency);
}

template <typename RegType>
inline DestinationOperandInterface *GetRegisterDestinationOp(
    RiscVState *state, const std::string &name, int latency,
    const std::string &op_name) {
  auto *reg = state->GetRegister<RegType>(name).first;
  return reg->CreateDestinationOperand(latency, op_name);
}

Gördüğünüz gibi iki yardımcı işlev vardır. İkincisi, alan adının işlenenin farklı bir ada veya dizeye sahip olmasına izin veren op_name parametresi temsil eder.

Benzer şekilde, kaynak işlenen yardımcıları için:

template <typename RegType>
inline SourceOperandInterface *GetRegisterSourceOp(RiscVState *state,
                                                   const std::string &reg_name) {
  auto *reg = state->GetRegister<RegType>(reg_name).first;
  auto *op = reg->CreateSourceOperand();
  return op;
}

template <typename RegType>
inline SourceOperandInterface *GetRegisterSourceOp(RiscVState *state,
                                                   const std::string &reg_name,
                                                   const std::string &op_name) {
  auto *reg = state->GetRegister<RegType>(reg_name).first;
  auto *op = reg->CreateSourceOperand(op_name);
  return op;
}

Oluşturucu ve arayüz işlevleri

Oluşturucu ve arayüz işlevleri çok basittir. Oluşturucu yalnızca şunun için callables dizilerini başlatmak üzere iki başlatma yöntemini çağırır: işlenenler.

RiscV32IEncoding::RiscV32IEncoding(RiscVState *state) : state_(state) {
  InitializeSourceOperandGetters();
  InitializeDestinationOperandGetters();
}

ParseInstruction, talimat kelimesini ve ardından komutun işlem kodunu depolar kod çözücü olarak oluşturulan ikili koda çağrıdan elde edilir.

// Parse the instruction word to determine the opcode.
void RiscV32IEncoding::ParseInstruction(uint32_t inst_word) {
  inst_word_ = inst_word;
  opcode_ = mpact::sim::codelab::DecodeRiscVInst32(inst_word_);
}

Son olarak, işlenen alıcılar, çağırdığı alıcı işlevindeki değeri döndürür. hedef/kaynak işlem göreni numaralandırma değeri kullanılarak dizi aramasına göre yapılır.


DestinationOperandInterface *RiscV32IEncoding::GetDestination(
    SlotEnum, int, OpcodeEnum, DestOpEnum dest_op, int, int latency) {
  return dest_op_getters_[static_cast<int>(dest_op)](latency);
}

SourceOperandInterface *RiscV32IEncoding::GetSource(SlotEnum, int, OpcodeEnum,
                                                    SourceOpEnum source_op, int) {
  return source_op_getters_[static_cast<int>(source_op)]();
}

Dizi başlatma yöntemleri

Tahmin edebileceğiniz gibi işin büyük kısmı alıcı ama merak etmeyin. Bu işlem, kolay ve yinelenen bir kalıp kullanılarak yapılır. Hadi önce InitializeDestinationOpGetters() ile başlayın, çünkü yalnızca birkaç hedef işleneni.

riscv32i_enums.h öğesinden oluşturulan DestOpEnum sınıfını geri çağır:

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

dest_op_getters_ için, her biri kNone ve kCsr, kNextPc ve kRd. Kolaylık sağlaması açısından her giriş bir lambda araması yapabilirsiniz. İmza lambda void(int latency).

Şu ana kadar gidilen farklı yer türlerinden çok fazla işlem göreler. Bu alıştırmada yalnızca iki türler: generic::RegisterDestinationOperand şurada tanımlanıyor: register.h, ve generic::DevNullOperand şurada tanımlandı: devnull_operand.h. Bu işlenenlerin ayrıntıları şu anda çok önemli değildir, yalnızca ilki kayıtlara yazmak için kullanılır ve ikincisi tüm yazma işlemlerini yok sayar.

kNone için ilk giriş önemsiz. Bir nullptr ve isteğe bağlı olarak döndürmeniz yeterlidir bir hata kaydedin.

void RiscV32IEncoding::InitializeDestinationOperandGetters() {
  // Destination operand getters.
  dest_op_getters_[static_cast<int>(DestOpEnum::kNone)] = [](int) {
    return nullptr;
  };

Sonra kCsr. Burada biraz hile yapacağız. "Merhaba dünya" programın herhangi bir CSR güncellemesine dayanmaz, ancak MT talimatlarını yürütmek. Çözüm, bunu bir alt düzey "CSR" adlı normal kayıt ve kanalize etme şansı veriyor.

  dest_op_getters_[static_cast<int>(DestOpEnum::kCsr)] = [this](int latency) {
    return GetRegisterDestinationOp<RV32Register>(state_, "CSR", latency);
  };

Sırada kNextPc, "pc" anlamına geliyor kaydolun. Hedef olarak kullanılır inceleyebilirsiniz. Ad RiscVState içinde şu şekilde tanımlanmaktadır: kPcName.

  dest_op_getters_[static_cast<int>(DestOpEnum::kNextPc)] = [this](int latency) {
    return GetRegisterDestinationOp<RV32Register>(state_, RiscVState::kPcName, latency);
  }

Son olarak, kRd hedef işleneni vardır. riscv32i.isa içinde işlenen rd, yalnızca "rd" içinde kodlanmış tam sayı kaydını belirtmek için kullanılır. alan anlamına gelir; dolayısıyla da neye atıfta bulunduğu konusunda herhangi bir muğlaklık yoktur. Orada komplikasyonlardan yalnızca biridir. x0 kaydı (abi adı zero) 0'a kablolu, Bu nedenle, bu kayıt için DevNullOperand kullanıyoruz.

Dolayısıyla, bu alıcıda ilk olarak rd alanındaki değeri .bin_fmt dosyasından Extract yöntemi oluşturuldu. Değer 0 ise bir "DevNull" döndür işleneni, aksi takdirde doğru kayıt işlenenini döndürürüz. işlem adı olarak uygun kayıt takma adını kullanmaya özen gösterin.

  dest_op_getters_[static_cast<int>(DestOpEnum::kRd)] = [this](int latency) {
    // First extract register number from rd field.
    int num = inst32_format::ExtractRd(inst_word_);
    // For register x0, return the DevNull operand.
    if (num == 0) return new DevNullOperand<uint32_t>(state, {1});
    // Return the proper register operand.
    return GetRegisterDestinationOp<RV32Register>(
      state_, absl::StrCat(RiscVState::kXRegPrefix, num), latency,
      xreg_alias_[num]);
    )
  }
}

Şimdi InitializeSourceOperandGetters() yöntemine geçelim. Burada desen hemen hemen aynı olsa da ayrıntılar biraz farklılık gösterir.

Öncelikle,SourceOpEnum İlk eğitimde riscv32i.isa:

  enum class SourceOpEnum {
    kNone = 0,
    kBimm12 = 1,
    kCsr = 2,
    kImm12 = 3,
    kJimm20 = 4,
    kRs1 = 5,
    kRs2 = 6,
    kSimm12 = 7,
    kUimm20 = 8,
    kUimm5 = 9,
    kPastMaxValue = 10,
  };

Üyeler, kNone dışında iki gruba ayrılıyor. Bir acil işlenenler: kBimm12, kImm12, kJimm20, kSimm12, kUimm20, ve kUimm5. Diğeri ise kayıt işlenenleridir: kCsr, kRs1 ve kRs2.

kNone işleneni, hedef işlenenlerle aynı şekilde kullanılır. nullptr.

void RiscV32IEncoding::InitializeSourceOperandGetters() {
  // Source operand getters.
  source_op_getters_[static_cast<int>(SourceOpEnum::kNone)] = [] () {
    return nullptr;
  };

Şimdi, kayıt işlenenleri üzerinde çalışalım. kCsr benzer e-posta ile daha ayrıntılı bir şekilde ele alabilirsiniz. Bunun için "CSR" kullanan yardımcı işlev girin.

  // Register operands.
  source_op_getters_[static_cast<int>(SourceOpEnum::kCsr)] = [this]() {
    return GetRegisterSourceOp<RV32Register>(state_, "CSR");
  };

kRs1 ve kRs2 işlemleri, kRd ile eş değer şekilde işlenir, ancak bunun dışında x0 (veya zero) ürününü güncellemek istemesek de, bu işlenenden her zaman 0 okuruz. Bunun için Şurada generic::IntLiteralOperand<> sınıf tanımlanıyor: literal_operand.h Bu işlenen, gerçek bir değeri depolamak için kullanılır (simüle edilmiş anlık değer). Aksi takdirde kalıp aynı kalır: İlk olarak talimat kelimesindeki rs1/rs2 değeri; sıfırsa değişmez değeri döndürür 0 şablon parametresiyle işlenen, aksi takdirde normal bir kayıt döndürür işlem gören olarak abi takma adının kullanıldığı, yardımcı işlevi kullanan kaynak işleneni dokunun.

  source_op_getters_[static_cast<int>(SourceOpEnum::kRs1)] =
      [this]() -> SourceOperandInterface * {
    int num = inst32_format::ExtractRs1(inst_word_);
    if (num == 0) return new IntLiteralOperand<0>({1}, xreg_alias_[0]);
    return GetRegisterSourceOp<RV32Register>(
        state_, absl::StrCat(RiscVState::kXregPrefix, num), xreg_alias_[num]);
  };
  source_op_getters_[static_cast<int>(SourceOpEnum::kRs2)] =
      [this]() -> SourceOperandInterface * {
    int num = inst32_format::ExtractRs2(inst_word_);
    if (num == 0) return new IntLiteralOperand<0>({1}, xreg_alias_[0]);
    return GetRegisterSourceOp<RV32Register>(
        state_, absl::StrCat(RiscVState::kXregPrefix, num), xreg_alias_[num]);
  };

Son olarak, farklı acil işlem görenler ele alınır. Hemen değerler şunlardır: şurada tanımlanan generic::ImmediateOperand<> sınıfının örneklerinde depolanır: immediate_operand.h Hemen işlem görenler için farklı alıcılar arasındaki tek fark hangi Ayıklayıcı işlevinin kullanıldığını ve depolama türünün imzalı veya emin olmanız gerekir.

  // Immediates.
  source_op_getters_[static_cast<int>(SourceOpEnum::kBimm12)] = [this]() {
    return new ImmediateOperand<int32_t>(
        inst32_format::ExtractBImm(inst_word_));
  };
  source_op_getters_[static_cast<int>(SourceOpEnum::kImm12)] = [this]() {
    return new ImmediateOperand<int32_t>(
        inst32_format::ExtractImm12(inst_word_));
  };
  source_op_getters_[static_cast<int>(SourceOpEnum::kUimm5)] = [this]() {
    return new ImmediateOperand<uint32_t>(
        inst32_format::ExtractUimm5(inst_word_));
  };
  source_op_getters_[static_cast<int>(SourceOpEnum::kJimm20)] = [this]() {
    return new ImmediateOperand<int32_t>(
        inst32_format::ExtractJImm(inst_word_));
  };
  source_op_getters_[static_cast<int>(SourceOpEnum::kSimm12)] = [this]() {
    return new ImmediateOperand<int32_t>(
        inst32_format::ExtractSImm(inst_word_));
  };
  source_op_getters_[static_cast<int>(SourceOpEnum::kUimm20)] = [this]() {
    return new ImmediateOperand<uint32_t>(
        inst32_format::ExtractUimm32(inst_word_));
  };
}

Yardıma ihtiyacınız olursa (veya çalışmalarınızı kontrol etmek isterseniz) tam yanıt burada bulabilirsiniz.

Bu eğitimin sonuna geldik. Umarız sizin için faydalı olmuştur.