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çinstate_
.- Geçerli değerin bulunduğu
uint32_t
türündekiinst_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 ®_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 ®_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.