本教學課程的目標是:
- 瞭解產生的 ISA 和二進位解碼器如何相輔相成。
- 編寫必要的 C++ 程式碼,建立 RiscV 的完整指示解碼器 結合 ISA 和二進位解碼器的 RV32I。
瞭解指令解碼器
指示解碼器會提供指示地址
記憶體中的指示字詞,並傳回完全初始化的
代表該指示的 Instruction。
頂層解碼器會實作以下所示的 generic::DecoderInterface:
// 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;
};
如您所見,只需要導入一個方法:cpp
virtual Instruction *DecodeInstruction(uint64_t address);
接著,我們來看看產生的程式碼需要的功能,以及產生的程式碼需要哪些資訊。
首先,請考慮檔案中的頂層類別 RiscV32IInstructionSet
riscv32i_decoder.h,這是在本課程結束時產生的
ISA 解碼器。如要查看新內容,請前往
您會學到許多
$ cd riscv_isa_decoder/solution
$ bazel build :all
...<snip>...
現在將目錄改回存放區根目錄,接著來看看
所產生的來源為此,請將目錄變更為
bazel-out/k8-fastbuild/bin/riscv_isa_decoder (假設您使用 x86 格式)
host - 如果是其他主機,k8-fastbuild 會是另一個字串)。
$ cd ../..
$ cd bazel-out/k8-fastbuild/bin/riscv_isa_decoder
您會看到含有所產生 C++ 程式碼的四個來源檔案:
riscv32i_decoder.hriscv32i_decoder.ccriscv32i_enums.hriscv32i_enums.cc
開啟第一個檔案 riscv32i_decoder.h。我們有三個類別
來看看:
RiscV32IEncodingBaseRiscV32IInstructionSetFactoryRiscV32IInstructionSet
記下類別的名稱。所有類別都會根據
「isa」中指定名稱的 Pascal 版本名稱宣告:
isa RiscV32I { ... }
我們先從 RiscVIInstructionSet 類別開始談起。如下所示:
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_;
};
這個類別不含任何虛擬方法,因此這是獨立類別,但
會發現兩件事首先,建構函式會指向
RiscV32IInstructionSetFactory 類別。如果這就是先前產生的類別
解碼器會使用此方法建立 RiscV32Slot 類別的例項,
按照slot RiscV32
riscv32i.isa 檔案。第二,Decode 方法需要額外的參數
屬於 RiscV32IEncodingBase 類型的指標,這個類別會提供
第一個教學課程中產生的 isa 解碼器介面
編碼器-解碼器模型
RiscV32IInstructionSetFactory 類別是我們用來
必須自行實作完整的解碼器在大多數情況下
類別是精簡的:您只需要提供一個方法,為每個
.isa 檔案中定義的版位類別。在這個範例中,步驟非常簡單
只是單一類別:Riscv32Slot (名稱 riscv32 的純量案例)
以 Slot 串連)。系統不會為您產生這個方法
可能適用於衍生子類別的常見用途
並改為呼叫其建構函式。
我們稍後會介紹最終類別 RiscV32IEncodingBase
教學課程,因為這是另一個練習的主題
定義頂層指示解碼器
定義工廠類別
如果您已在第一個教學課程中重新建構專案,請務必改回使用
riscv_full_decoder 目錄。
開啟檔案 riscv32_decoder.h。所有必要 include 檔案中
,且已設定命名空間。
在標示為 //Exercise 1 - step 1 的註解之後,定義類別
RiscV32IsaFactory 繼承自 RiscV32IInstructionSetFactory。
class RiscV32IsaFactory : public RiscV32InstructionSetFactory {};
接著,定義 CreateRiscv32Slot 的覆寫值。由於我們未使用任何
我們只會使用衍生的 Riscv32Slot 類別來分配新的例項
std::make_unique。
std::unique_ptr<Riscv32Slot> CreateRiscv32Slot(ArchState *) override {
return std::make_unique<Riscv32Slot>(state);
}
如果你需要協助 (或想查看作業),完整答案為 請按這裡。
定義解碼器類別
建構函式、解構與方法宣告
接下來,請定義解碼器類別。在同一個檔案中,前往
RiscV32Decoder 宣告。將宣告展開為類別定義
其中 RiscV32Decoder 沿用自 generic::DecoderInterface。
class RiscV32Decoder : public generic::DecoderInterface {
public:
};
接著,讓我們快速熟悉
我們在第二個有關二進位檔解碼器的教學課程中
出現了不錯的選擇除了所有
Extract 函式,具有 DecodeRiscVInst32 函式:
OpcodeEnum DecodeRiscVInst32(uint32_t inst_word);
這個函式會使用需要解碼的指令字詞,然後將其傳回
符合指示的運算程式碼另一方面,
RiscV32Decoder 實作的 DecodeInterface 類別只會傳入
讓我們看看 DNS 解析
進一步探索內部和外部位址因此,RiscV32Decoder 類別必須能存取記憶體來
讀取要傳遞至 DecodeRiscVInst32() 的指示字詞。這項專案
存取記憶體的方式,就是透過
名為 util::MemoryInterface 的 .../mpact/sim/util/memory,如下所示:
// 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;
此外,我們也必須能將 state 類別例項傳遞至
其他解碼器類別的建構函式。適當的狀態類別為
riscv::RiscVState 類別,衍生自 generic::ArchState
RiscV 的功能也就是說,我們必須宣告建構函式
可以指向 state 和 memory:
RiscV32Decoder(riscv::RiscVState *state, util::MemoryInterface *memory);
刪除預設建構函式並覆寫解構函式:
RiscV32Decoder() = delete;
~RiscV32Decoder() override;
接著,請宣告需要覆寫的 DecodeInstruction 方法
generic::DecoderInterface。
generic::Instruction *DecodeInstruction(uint64_t address) override;
如果你需要協助 (或想查看作業),完整答案為 請按這裡。
資料成員定義
RiscV32Decoder 類別需要私人資料成員來儲存
建構函式參數和 Factory 類別的指標。
private:
riscv::RiscVState *state_;
util::MemoryInterface *memory_;
此方法也需要指標,指向衍生自
RiscV32IEncodingBase,接著要呼叫 RiscV32IEncoding (我們會
(在練習 2 中)。此外,它需要指標指向
RiscV32IInstructionSet,因此請新增:
RiscV32IsaFactory *riscv_isa_factory_;
RiscV32IEncoding *riscv_encoding_;
RiscV32IInstructionSet *riscv_isa_;
最後,我們需要定義資料成員,以便與記憶體介面搭配使用:
generic::DataBuffer *inst_db_;
如果你需要協助 (或想查看作業),完整答案為 請按這裡。
定義解碼器類別方法
接下來是實作建構函式、解構函式和
DecodeInstruction 方法。開啟檔案 riscv32_decoder.cc。空白
檔案中已有 方法,以及命名空間宣告和一些
共 using 個宣告。
建構函式定義
建構函式只需要初始化資料成員。首先,初始化
state_ 和 memory_:
RiscV32Decoder::RiscV32Decoder(riscv::RiscVState *state,
util::MemoryInterface *memory)
: state_(state), memory_(memory) {
接下來,為每個解碼器相關類別分配例項,傳入 適當的參數
// 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);
最後,分配 DataBuffer 執行個體。這會使用工廠進行分配
可透過 state_ 成員存取。我們會分配大小來儲存資料緩衝區
單一 uint32_t,因為這是指令字詞的大小。
inst_db_ = state_->db_factory()->Allocate<uint32_t>(1);
解構定義
解構函式非常簡單,只需釋放建構函式中配置的物件。
但會有一個巧妙的轉折點資料緩衝區例項會計入參考,因此
在該指標上呼叫 delete 時,我們會 DecRef() 物件:
RiscV32Decoder::~RiscV32Decoder() {
inst_db_->DecRef();
delete riscv_isa_;
delete riscv_isa_factory_;
delete riscv_encoding_;
}
方法定義
在這個案例中,這個方法的實作方式非常簡單。我們會假設 地址正確對齊,且沒有額外的錯誤檢查 這通常代表交易 不會十分要求關聯語意
首先,必須使用記憶體容量從記憶體中擷取指令字詞
介面和 DataBuffer 例項。
memory_->Load(address, inst_db_, nullptr, nullptr);
uint32_t iword = inst_db_->Get<uint32_t>(0);
接下來,我們會呼叫 RiscVIEncoding 例項來剖析指令字詞,
你必須先完成這項程序,才能呼叫 ISA 解碼器回想一下 ISA
解碼器會直接呼叫 RiscVIEncoding 執行個體,以取得運算程式碼
和運算元。我們先前並未採用
但現在讓我們使用 void ParseInstruction(uint32_t) 做為該方法。
riscv_encoding_->ParseInstruction(iword);
最後,我們呼叫 ISA 解碼器,傳入地址和編碼類別。
auto *instruction = riscv_isa_->Decode(address, riscv_encoding_);
return instruction;
如果你需要協助 (或想查看作業),完整答案為 請按這裡。
編碼類別
編碼類別會導入解碼器類別所用的介面 取得指示運算程式碼、其來源和目的地運算元,以及 資源運算元這些物件全都依附於二進位檔中的資訊 例如解碼器、 指令字詞等等。這是與解碼器類別分開的 可跨越編碼,支援多種不同的編碼配置
RiscV32IEncodingBase 是抽象類別。我們之所以能
下方顯示了實際函式中的實作方式
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;
};
乍看之下,這看起來有點複雜,尤其是 但對於 RiscV 等簡單的架構,我們實際上會忽略 參數的值,因為系統會默示。
接下來將逐一介紹各個方法。
OpcodeEnum GetOpcode(SlotEnum slot, int entry);
GetOpcode 方法會傳回目前設定的 OpcodeEnum 成員
指令並識別指示運算程式碼OpcodeEnum 類別為
在產生的 isa 解碼器檔案 riscv32i_enums.h 中定義的這個方法
第一個
這些是版位類型 (riscv32i_enums.h 中也定義的列舉類別),
由於 RiscV 只有一個版位,因此只有一個可能的值:
SlotEnum::kRiscv32。第二個為運算單元的例項編號 (如果是的話)
該版位會有多個執行個體,在某些 VLIW 中可能會發生
架構)。
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);
下兩種方法可用來建立處理器中的硬體資源模型
,以增進月經週期準確度。在教學課程練習中,我們不會使用
因此在實作中,這些變數會經過虛設處理,並傳回 nullptr。
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);
這三個方法會傳回指標,指向在
指令語意函式可用來存取任何指示的值
述詞運算元、每個指示來源運算元,然後寫入新的
的值對應至指示目的地運算元。由於 RiscV 並未使用
指令述詞,這個方法只需要傳回 nullptr。
這些函式中的參數模式都很類似。首先,就像
GetOpcode 位置和項目會傳入。接著是針對
必須建立運算元的指示。只有在廣告代碼的
不同運算碼需要針對相同運算元傳回不同的運算元物件
而這不是 RescV 模擬工具的例子
接著是述詞、來源和目的地
運算元列舉項目
識別要建立的運算元這些是
riscv32i_enums.h 中的 OpEnums,如下所示:
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,
};
如果回顧一下
riscv32.isa敬上
您會注意到,這些內容會與來源和目的地的組合
每個指示的宣告中使用的運算元名稱。使用
代表不同 bitfields 和運算元的運算元名稱
因此能更輕鬆地編寫編碼類別,因為列舉成員獨有的
決定要傳回的確切運算元類型,但不需要
請考量運算單元、項目或 opcode 參數的值
最後,對於來源和目的地運算元來說, 運算元會傳入 (同樣可以忽略此),而對於目的地 運算元,在指示時間之間相隔的延遲時間 (以週期為單位) 發出,後續指令將提供目的地結果。 模擬工具中的延遲時間為 0,表示指令會寫入 並立即傳回結果
int GetLatency(SlotEnum slot, int entry, OpcodeEnum opcode,
DestOpEnum dest_op, int dest_no);
最後一個函式則用於取得特定目的地的延遲時間
運算元 (如果 .isa 檔案中已指定為 *)。這並不常見
不會用於這個 RiscV 模擬工具
只會傳回 0
定義編碼類別
標頭檔案 (.h)
方法
開啟檔案 riscv32i_encoding.h。所有必要 include 檔案中
,且已設定命名空間。所有新增的程式碼皆為
已追蹤註解 // Exercise 2.
首先,定義繼承自RiscV32IEncoding
產生的介面
class RiscV32IEncoding : public RiscV32IEncodingBase {
public:
};
接下來,建構函式應採用指標至狀態例項,在這個案例中
指向 riscv::RiscVState 的指標。應使用預設解構函式。
explicit RiscV32IEncoding(riscv::RiscVState *state);
~RiscV32IEncoding() override = default;
加入所有介面方法之前,讓我們先在呼叫
RiscV32Decoder 即可剖析指示:
void ParseInstruction(uint32_t inst_word);
接下來,我們要在那些含有複雜覆寫值的方法中新增 未使用參數的名稱:
// 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; }
最後加入公用介面的剩餘方法覆寫值,但 則會延後到 .cc 檔案
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;
為了簡化每個運算元 getter 方法的實作。
我們會建立兩個 callable (函式物件) 陣列,由
SourceOpEnum 和 DestOpEnum 成員的數值。
如此一來,這些主體會縮減為呼叫
所傳入列舉值的函式物件,並傳回其傳回結果
值。
為整理這兩個陣列的初始化作業,我們定義了兩個不公開的 建構函式呼叫的方法如下:
private:
void InitializeSourceOperandGetters();
void InitializeDestinationOperandGetters();
資料成員
所需的資料成員如下:
state_可保留riscv::RiscVState *值。uint32_t類型的inst_word_,其中包含目前的值 的說明文字opcode_用於保存目前更新ParseInstruction方法。此類型具有OpcodeEnum類型。source_op_getters_陣列用於儲存用來取得來源的呼叫型 運算元物件。陣列元素的類型為absl::AnyInvocable<SourceOperandInterface *>()>。dest_op_getters_陣列用於儲存用於取得的呼叫端 目的地運算元物件陣列元素的類型為absl::AnyInvocable<DestinationOperandInterface *>()>。xreg_aliasRiscV 整數陣列會註冊 ABI 名稱,例如「零」和 「ra」而不是「x0」和「x1」
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"};
如果你需要協助 (或想查看作業),完整答案為 請按這裡。
來源檔案 (.cc)。
開啟檔案 riscv32i_encoding.cc。所有必要 include 檔案中
,且已設定命名空間。所有新增的程式碼皆為
已追蹤註解 // Exercise 2.
輔助函式
我們會先撰寫幾項輔助函式
來源和目的地登錄運算元。這些模型會在
註冊類型,並會呼叫 RiscVState 物件來取得
註冊物件,然後在註冊物件中呼叫運算元工廠方法。
首先從目的地運算元輔助程式開始:
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);
}
如您所見,有兩個輔助函式。第二,多添一層防護
可讓運算元使用不同的名稱或字串的 op_name 參數
而非基礎暫存器的表示法
來源運算元輔助程式類似:
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;
}
建構函式與介面函式
建構函式和介面函式非常簡單。建構函式 您只需呼叫兩個初始化方法,即可初始化 callables 陣列 運算元 getter
RiscV32IEncoding::RiscV32IEncoding(RiscVState *state) : state_(state) {
InitializeSourceOperandGetters();
InitializeDestinationOperandGetters();
}
ParseInstruction 會儲存指令字詞,然後儲存其運算程式碼
值。
// 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_);
}
最後,運算元 getter 會從其呼叫的 getter 函式傳回值 根據目的地/來源運算元列舉值進行陣列查詢。
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)]();
}
陣列初始化方法
您可能已經猜到,大部分的工作都是初始化 getter
但別擔心,只要使用簡單且重複的模式即可完成我們
先從 InitializeDestinationOpGetters() 開始,因為只有
兩個目的地運算元
回顧從 riscv32i_enums.h 產生的 DestOpEnum 類別:
enum class DestOpEnum {
kNone = 0,
kCsr = 1,
kNextPc = 2,
kRd = 3,
kPastMaxValue = 4,
};
針對 dest_op_getters_,我們需要初始化 4 個項目,每個項目各一個 kNone、
kCsr、kNextPc和kRd。為了方便起見,每個項目都會使用
lambda,但您也可以使用任何其他形式的呼叫。簽章
lambda 的結尾是 void(int latency)。
到目前為止,我們還沒深入探討不同種類的目的地
以及 MPACT-Sim 中定義的運算元。針對本練習,我們只會使用兩個
類型:定義為 generic::RegisterDestinationOperand
register.h,
和 generic::DevNullOperand 定義於
devnull_operand.h。
這些運算元的細節目前並不重要,除了
前者是用於寫入暫存器,後者則忽略所有寫入作業。
第一個 kNone 項目相當簡單 - 只需傳回空值,並視需要傳回
記錄錯誤。
void RiscV32IEncoding::InitializeDestinationOperandGetters() {
// Destination operand getters.
dest_op_getters_[static_cast<int>(DestOpEnum::kNone)] = [](int) {
return nullptr;
};
下一則是由「kCsr」提供我們要稍微作弊。「Hello World」節目
不必仰賴任何實際的 CSR 更新,但其實一些樣板程式碼
執行 CSR 指示解決方法就是使用
名稱為「CSR」的一般暫存器執行所有這類寫入作業
dest_op_getters_[static_cast<int>(DestOpEnum::kCsr)] = [this](int latency) {
return GetRegisterDestinationOp<RV32Register>(state_, "CSR", latency);
};
接著是 kNextPc,代表「電腦」註冊。做為目標
及跳轉指示名稱在 RiscVState 中定義為
kPcName。
dest_op_getters_[static_cast<int>(DestOpEnum::kNextPc)] = [this](int latency) {
return GetRegisterDestinationOp<RV32Register>(state_, RiscVState::kPcName, latency);
}
最後是 kRd 目的地運算元。在 riscv32i.isa 運算元中
rd 只會用來參照「rd」中編碼的整數暫存器欄位
所以我們無法明確指出它所指的意思。有
只是其中一種小工具註冊 x0 (Aabi 名稱 zero) 的有線編碼為 0,
因此,我們使用 DevNullOperand 來註冊該註冊。
因此在這個 getter 中,我們會先使用rd
從 .bin_fmt 檔案產生的 Extract 方法。如果值為 0,系統則會
會傳回「DevNull」運算元,否則我們會傳回正確的暫存運算元。
務必使用適當的暫存別名做為運算元名稱。
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]);
)
}
}
現在前往 InitializeSourceOperandGetters() 方法,
兩者相似,但細節稍有不同
首先來看看產生的 SourceOpEnum
第一個教學課程中的 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,
};
檢查成員和 kNone 時,他們也會分為兩組。一
為立即運算元:kBimm12、kImm12、kJimm20、kSimm12、kUimm20、
和 kUimm5。另一個是註冊運算元:kCsr、kRs1 和 kRs2。
kNone 運算元的處理方式與目的地運算元相同 - 會傳回
nullptr。
void RiscV32IEncoding::InitializeSourceOperandGetters() {
// Source operand getters.
source_op_getters_[static_cast<int>(SourceOpEnum::kNone)] = [] () {
return nullptr;
};
接下來,我們要處理暫存運算元。我們會處理類似的 kCsr
建議您直接呼叫
使用「CSR」的輔助函式做為註冊名稱
// Register operands.
source_op_getters_[static_cast<int>(SourceOpEnum::kCsr)] = [this]() {
return GetRegisterSourceOp<RV32Register>(state_, "CSR");
};
運算元 kRs1 和 kRs2 的處理方式與 kRd 相同,除非是
我們不想更新 x0 (或 zero),但我們想確保
我們一律從該運算元讀取 0為此,我們會使用
已定義 generic::IntLiteralOperand<> 類別
literal_operand.h。
這個運算元是用來儲存常值 (而非模擬的)
即時價值)。其他模式也相同:請先擷取
指示字詞的 rs1/rs2 值,如果為零,則會傳回常值
帶有範本參數的運算元,否則會傳回一般暫存器
使用輔助函式的來源運算元,以 abi 別名做為運算元
名稱。
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]);
};
最後,我們會處理不同的立即運算元。即時值為
儲存在名為 generic::ImmediateOperand<> 類別的執行個體
immediate_operand.h。
不同 getter 之間直接運算元的唯一差異
是使用擷取器函式,而儲存體類型
不會加上符號。
// 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_));
};
}
如果你需要協助 (或想查看作業),完整答案為 請按這裡。
本教學課程到此結束。希望對您有所幫助。