本教學課程的目標是:
- 瞭解產生的 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.h
riscv32i_decoder.cc
riscv32i_enums.h
riscv32i_enums.cc
開啟第一個檔案 riscv32i_decoder.h
。我們有三個類別
來看看:
RiscV32IEncodingBase
RiscV32IInstructionSetFactory
RiscV32IInstructionSet
記下類別的名稱。所有類別都會根據
「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_alias
RiscV 整數陣列會註冊 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_));
};
}
如果你需要協助 (或想查看作業),完整答案為 請按這裡。
本教學課程到此結束。希望對您有所幫助。