本教學課程的目標是:
- 瞭解如何使用語意函式實作指令語意。
- 瞭解語意函式與 ISA 解碼器說明之間的關係。
- 編寫 RiscV RV32I 指令的指示語意函式。
- 執行小型的「Hello World」測試最終模擬工具執行檔。
語意函式總覽
MPACT-Sim 中的語意函式是實作運算作業的函式 指示,以便在模擬狀態下顯示其副作用 就像您在容器中執行指令時 硬體模擬器中每個已解碼指示的內部表示法 包含可呼叫函式,用於呼叫該函式的語意函式 指示
語意函式具備簽章 void(Instruction *)
,也就是說
這個函式會將指標指向 Instruction
類別的執行個體,並
會傳回 void
。
Instruction
類別已定義於
instruction.h
在編寫語意函式方面,我們特別感興趣
使用
Source(int i)
和 Destination(int i)
方法呼叫。
來源和目的地運算元介面如下所示:
// The source operand interface provides an interface to access input values
// to instructions in a way that is agnostic about the underlying implementation
// of those values (eg., register, fifo, immediate, predicate, etc).
class SourceOperandInterface {
public:
// Methods for accessing the nth value element.
virtual bool AsBool(int index) = 0;
virtual int8_t AsInt8(int index) = 0;
virtual uint8_t AsUint8(int index) = 0;
virtual int16_t AsInt16(int index) = 0;
virtual uint16_t AsUint16(int) = 0;
virtual int32_t AsInt32(int index) = 0;
virtual uint32_t AsUint32(int index) = 0;
virtual int64_t AsInt64(int index) = 0;
virtual uint64_t AsUint64(int index) = 0;
// Return a pointer to the object instance that implements the state in
// question (or nullptr) if no such object "makes sense". This is used if
// the object requires additional manipulation - such as a fifo that needs
// to be pop'ed. If no such manipulation is required, nullptr should be
// returned.
virtual std::any GetObject() const = 0;
// Return the shape of the operand (the number of elements in each dimension).
// For instance {1} indicates a scalar quantity, whereas {128} indicates an
// 128 element vector quantity.
virtual std::vector<int> shape() const = 0;
// Return a string representation of the operand suitable for display in
// disassembly.
virtual std::string AsString() const = 0;
virtual ~SourceOperandInterface() = default;
};
// The destination operand interface is used by instruction semantic functions
// to get a writable DataBuffer associated with a piece of simulated state to
// which the new value can be written, and then used to update the value of
// the piece of state with a given latency.
class DestinationOperandInterface {
public:
virtual ~DestinationOperandInterface() = default;
// Allocates a data buffer with ownership, latency and delay line set up.
virtual DataBuffer *AllocateDataBuffer() = 0;
// Takes an existing data buffer, and initializes it for the destination
// as if AllocateDataBuffer had been called.
virtual void InitializeDataBuffer(DataBuffer *db) = 0;
// Allocates and initializes data buffer as if AllocateDataBuffer had been
// called, but also copies in the value from the current value of the
// destination.
virtual DataBuffer *CopyDataBuffer() = 0;
// Returns the latency associated with the destination operand.
virtual int latency() const = 0;
// Return a pointer to the object instance that implmements the state in
// question (or nullptr if no such object "makes sense").
virtual std::any GetObject() const = 0;
// Returns the order of the destination operand (size in each dimension).
virtual std::vector<int> shape() const = 0;
// Return a string representation of the operand suitable for display in
// disassembly.
virtual std::string AsString() const = 0;
};
為一般 3 運算元編寫語意函式的基本方法
32 位元 add
指令等指示如下:
void MyAddFunction(Instruction *inst) {
uint32_t a = inst->Source(0)->AsUint32(0);
uint32_t b = inst->Source(1)->AsUint32(0);
uint32_t c = a + b;
DataBuffer *db = inst->Destination(0)->AllocateDataBuffer();
db->Set<uint32_t>(0, c);
db->Submit();
}
現在來詳細說明此函式的各個部分。
函式主體讀取來源運算元 0 和 1 的內容。AsUint32(0)
呼叫
會將基礎資料解讀為 uint32_t
陣列,並擷取第 0 個
元素。無論基礎暫存器或值為何,皆是如此
陣列的數值。來源運算元的大小 (以元素為單位) 可以是
從來源運算元方法 shape()
取得,此方法會傳迴向量
內含每個維度的元素數量該方法會傳回 {1}
若是純量,{16}
代表 16 元素向量,{4, 4}
代表 4x4 陣列。
uint32_t a = inst->Source(0)->AsUint32(0);
uint32_t b = inst->Source(1)->AsUint32(0);
接著,系統會將 a + b
值指派給名為 c
的 uint32_t
臨時暫存檔。
下一行可能需要多方解釋:
DataBuffer *db = inst->Destination(0)->AllocateDataBuffer();
DataBuffer 是參照的計數物件,用來將值儲存在
例如暫存器等模擬狀態這個類型相對不類型
目標大小在本例中,該大小是
sizeof(uint32_t)
。這個陳述式會為
做為此目的地運算元的目標目的地,在本例中為
32 位元整數暫存器。DataBuffer 也會使用
架構延遲時間這項資訊會在操作說明指定時指定。
解碼器。
下一行會將資料緩衝區例項視為 uint32_t
的陣列,
會將儲存在 c
中的值寫入第 0 個元素。
db->Set<uint32_t>(0, c);
最後,最後一個陳述式會將資料緩衝區提交至模擬工具 做為目標機器狀態的新值 (在此例中為暫存器) 指令解碼時設定的延遲時間,以及 已填入目的地運算元向量。
雖然這個功能相當簡單,但確實會有一些樣板 程式碼在指示後實作時可能會重複。 此外,這類指令可能會遮蓋指令的實際語意。在訂單中 進一步簡化為大多數指示編寫語意函式 您可以在 instruction_helpers.h。 這些輔助程式會隱藏樣板程式碼,用於指示一、二或三個 來源運算元和單一目的地運算元。以下分別介紹 運算元輔助函式:
// This is a templated helper function used to factor out common code in
// two operand instruction semantic functions. It reads two source operands
// and applies the function argument to them, storing the result to the
// destination operand. This version supports different types for the result and
// each of the two source operands.
template <typename Result, typename Argument1, typename Argument2>
inline void BinaryOp(Instruction *instruction,
std::function<Result(Argument1, Argument2)> operation) {
Argument1 lhs = generic::GetInstructionSource<Argument1>(instruction, 0);
Argument2 rhs = generic::GetInstructionSource<Argument2>(instruction, 1);
Result dest_value = operation(lhs, rhs);
auto *db = instruction->Destination(0)->AllocateDataBuffer();
db->SetSubmit<Result>(0, dest_value);
}
// This is a templated helper function used to factor out common code in
// two operand instruction semantic functions. It reads two source operands
// and applies the function argument to them, storing the result to the
// destination operand. This version supports different types for the result
// and the operands, but the two source operands must have the same type.
template <typename Result, typename Argument>
inline void BinaryOp(Instruction *instruction,
std::function<Result(Argument, Argument)> operation) {
Argument lhs = generic::GetInstructionSource<Argument>(instruction, 0);
Argument rhs = generic::GetInstructionSource<Argument>(instruction, 1);
Result dest_value = operation(lhs, rhs);
auto *db = instruction->Destination(0)->AllocateDataBuffer();
db->SetSubmit<Result>(0, dest_value);
}
// This is a templated helper function used to factor out common code in
// two operand instruction semantic functions. It reads two source operands
// and applies the function argument to them, storing the result to the
// destination operand. This version requires both result and source operands
// to have the same type.
template <typename Result>
inline void BinaryOp(Instruction *instruction,
std::function<Result(Result, Result)> operation) {
Result lhs = generic::GetInstructionSource<Result>(instruction, 0);
Result rhs = generic::GetInstructionSource<Result>(instruction, 1);
Result dest_value = operation(lhs, rhs);
auto *db = instruction->Destination(0)->AllocateDataBuffer();
db->SetSubmit<Result>(0, dest_value);
}
您會發現,而不是使用類似下列陳述式:
uint32_t a = inst->Source(0)->AsUint32(0);
輔助函式會使用:
generic::GetInstructionSource<Argument>(instruction, 0);
GetInstructionSource
是一系列範本式輔助函式,
用於提供範本存取方法給指示來源
運算元。如果沒有每個指令輔助函式
各類型都專門用於存取含有正確的程式碼運算元
As<int type>()
函式。您可以查看這些範本的定義
函式
instruction.h。
如您所見,根據來源
運算元類型與目的地相同,無論目的地為
或來源是否不同每個
這個函式會指向指令執行個體,以及可呼叫的
(包括 lambda 函式)。這表示我們現在可以重新編寫 add
上述語意函式,如下所示:
void MyAddFunction(Instruction *inst) {
generic::BinaryOp<uint32_t>(inst,
[](uint32_t a, uint32_t b) { return a + b; });
}
在建構中使用 bazel build -c opt
和 copts = ["-O3"]
進行編譯時
檔案,應該完全內嵌且不會產生負擔
簡潔明瞭,不會對效能造成任何影響。
正如先前所述,有一元、二元和三元純量適用的輔助函式 指示和向量對等項目這些範本也可做為實用的範本 。
初始版本
若您尚未將目錄變更為 riscv_semantic_functions
,請進行這項操作。
您的兒時朋友不久後就會遭到解僱
並要求您暫時保密接著按照下列方式建構專案,這項建構作業應會成功。
$ bazel build :riscv32i
...<snip>...
由於系統不會產生任何檔案,因此這只是 確定一切正常
新增三個運算元 ALU 指令
現在我們要針對一般 3 運算元 ALU 新增語意函式
操作說明。開啟「rv32i_instructions.cc
」檔案,並確認是否有任何
缺少定義就會加進檔案 rv32i_instructions.h
。
新增的操作說明如下:
add
- 32 位元整數加上。and
:32 位元位元和。or
:32 位元位元或。sll
- 32 位元邏輯移位。sltu
:32 位元無正負號集合小於。sra
:32 位元算術右移。srl
:32 位元邏輯右移。sub
- 減 32 位元整數。xor
- 32 位元位元 xor。
如果您已完成先前的教學課程,可能會還記得 註冊方式與立即註冊操作說明 傳給解碼器我們不再需要實作語意函式, 運算元介面會從運算元讀取運算元值 註冊或立即註冊時,您可以使用完全不受 瞭解基礎來源運算元的真正目的
除了 sra
以外,上述所有指令都可視為
32 位元無正負號值,因此可以使用 BinaryOp
範本函式
僅使用單一範本類型引數填入
rv32i_instructions.cc
中的函式主體。請注意
第二個運算元的位元的值用於位移
金額。否則所有作業會採用 src0 op src1
形式:
add
:a + b
and
:a & b
or
:a | b
sll
:a << (b & 0x1f)
sltu
:(a < b) ? 1 : 0
srl
:a >> (b & 0x1f)
sub
:a - b
xor
:a ^ b
如果是 sra
,我們會使用三個引數 BinaryOp
範本。查看
範本,第一個類型引數是結果類型 uint32_t
。第二項是
來源運算元 0 的類型,在本例中為 int32_t
,最後一個則是類型
來源運算元 1,在本例中為 uint32_t
。這會成為 sra
的主體
語意函式:
generic::BinaryOp<uint32_t, int32_t, uint32_t>(
instruction, [](int32_t a, uint32_t b) { return a >> (b & 0x1f); });
請繼續進行變更並建構。您可以在 rv32i_instructions.cc。
新增兩個運算元 ALU 指令
只有兩個運算元 ALU 指令:lui
和 auipc
。前
會將預先轉移的來源運算元直接複製到目的地。後者
會先將指示地址加到 ,然後再寫入
目的地。操作說明地址可透過 address()
方法存取
Instruction
物件的另一個節點。
由於只有一個來源運算元,我們無法使用 BinaryOp
,因此
我們需要使用 UnaryOp
由於我們可以同時處理來源和
目的地運算元做為 uint32_t
,可以使用單一引數範本
版本。
// This is a templated helper function used to factor out common code in
// single operand instruction semantic functions. It reads one source operand
// and applies the function argument to it, storing the result to the
// destination operand. This version supports the result and argument having
// different types.
template <typename Result, typename Argument>
inline void UnaryOp(Instruction *instruction,
std::function<Result(Argument)> operation) {
Argument lhs = generic::GetInstructionSource<Argument>(instruction, 0);
Result dest_value = operation(lhs);
auto *db = instruction->Destination(0)->AllocateDataBuffer();
db->SetSubmit<Result>(0, dest_value);
}
// This is a templated helper function used to factor out common code in
// single operand instruction semantic functions. It reads one source operand
// and applies the function argument to it, storing the result to the
// destination operand. This version requires that the result and argument have
// the same type.
template <typename Result>
inline void UnaryOp(Instruction *instruction,
std::function<Result(Result)> operation) {
Result lhs = generic::GetInstructionSource<Result>(instruction, 0);
Result dest_value = operation(lhs);
auto *db = instruction->Destination(0)->AllocateDataBuffer();
db->SetSubmit<Result>(0, dest_value);
}
lui
的語意函式主體盡可能重要
只會傳回來源auipc
的語意函式導入了次要函式
問題,因為您需要存取 Instruction
中的 address()
方法
執行個體。答案是將 instruction
新增至 lambda 擷取,
可用於 lambda 函式主體。應寫入 [instruction](uint32_t a) { ... }
,而不是像之前的 [](uint32_t a) { ...
}
。
現在 instruction
可在 lambda 主體中使用。
請繼續進行變更並建構。您可以在 rv32i_instructions.cc。
新增控制流程變更操作說明
您需要導入的控制流程變更操作說明分為 寫入條件分支版本操作說明 (如果遇到下列情況,則會產生更短的分支版本 這裡顯示的結果是正確的) 以及「跳轉連結」的操作說明 實作函式呼叫 (藉由設定連結 註冊零時差,讓這類寫入作業自動化。
新增條件分支版本操作說明
沒有用於分支版本指示的輔助函式,因此有兩個選項。 從頭開始編寫語意函式,或編寫本機輔助函式。 由於我們要實作 6 個分支指示,後者似乎值得 開始之前,我們先來看看分支版本的實作 指令語意函式
void MyConditionalBranchGreaterEqual(Instruction *instruction) {
int32_t a = generic::GetInstructionSource<int32_t>(instruction, 0);
int32_t b = generic::GetInstructionSource<int32_t>(instruction, 1);
if (a >= b) {
uint32_t offset = generic::GetInstructionSource<uint32_t>(instruction, 2);
uint32_t target = offset + instruction->address();
DataBuffer *db = instruction->Destination(0)->AllocateDataBuffer();
db->Set<uint32_t>(0,m target);
db->Submit();
}
}
每個分支版本指示中唯一的差異是分支版本
限制條件,以及兩者中已簽署與未簽署的 32 位元整數
來源運算元。也就是說,我們需要擁有
來源運算元。輔助函式本身需要擷取 Instruction
執行個體和可呼叫物件,例如傳回 bool
的 std::function
做為參數輔助函式如下所示:
template <typename OperandType>
static inline void BranchConditional(
Instruction *instruction,
std::function<bool(OperandType, OperandType)> cond) {
OperandType a = generic::GetInstructionSource<OperandType>(instruction, 0);
OperandType b = generic::GetInstructionSource<OperandType>(instruction, 1);
if (cond(a, b)) {
uint32_t offset = generic::GetInstructionSource<uint32_t>(instruction, 2);
uint32_t target = offset + instruction->address();
DataBuffer *db = instruction->Destination(0)->AllocateDataBuffer();
db->Set<uint32_t>(0, target);
db->Submit();
}
}
現在我們可以編寫 bge
(大於或等於帶正負號的分支版本) 語意函式。
為:
void RV32IBge(Instruction *instruction) {
BranchConditional<int32_t>(instruction,
[](int32_t a, int32_t b) { return a >= b; });
}
其餘分支版本的操作說明如下:
- 貝克 - 分支等於。
- Bgeu - 分支版本大於或等於 (無符號)。
- 組合 - 小於 (帶正負號) 的分支。
- Bltu - 小於 (未簽署的) 分支版本。
- Bne - 分支不等於。
繼續變更以實作這些語意函式 重新建構您可以在 rv32i_instructions.cc。
新增跳轉連結的操作說明
撰寫跳轉和連結輔助函式時,並沒有明確的意義 指示,因此我們需要從頭開始撰寫這些項目。首先從 並檢查其指令語義
jal
指令會採用來源運算元 0 的偏移值,並將其新增至
目前的電腦 (指示位址) 用來計算跳躍目標。跳躍目標
寫入目的地運算元 0。傳回地址是
就會進入下一個序列中的操作說明方法是將目前
指引它的大小系統會將退貨地址寫入
目的地運算元 1。請記得將操作說明物件指標加入
lambda 擷取。
jalr
指令會將基礎暫存器當做來源運算元 0 和偏移量
做為來源運算元 1,然後相加,即可計算跳躍目標。
否則,會與 jal
指令相同。
根據這些操作說明語意,撰寫兩項語意 和建構您可以在 rv32i_instructions.cc。
新增記憶體存放區操作說明
需要實作以下三種商店說明:商店位元組
(sb
)、商店半字 (sh
) 和商店字詞 (sw
)。門市說明
與目前為止導入的操作說明不同,
寫入本機處理器狀態而是寫入系統資源:
主記憶體。MPACT-Sim 不會將記憶體視為指令運算元
所以必須使用其他方法
才能存取記憶體
答案是將記憶體存取方法新增至 MPACT-Sim ArchState
物件。
請至少建立從 ArchState
衍生的新 RiscV 狀態物件
您就能在其中新增這項功能ArchState
物件會管理核心資源,例如
註冊和其他狀態物件以及用來管理
緩衝目的地運算元資料緩衝區,直到可以寫回
暫存器物件大部分指示都能實作,
但也有一些用途
指令需要功能位於此狀態物件中。
讓我們來看看 fence
指令的語意函式:
,這是使用 rv32i_instructions.cc
中實作的範例。fence
指令會保留指示問題,直到特定記憶體作業出現
已完成用於確保指示之間的記憶體順序
再執行命令
// Fence.
void RV32IFence(Instruction *instruction) {
uint32_t bits = instruction->Source(0)->AsUint32(0);
int fm = (bits >> 8) & 0xf;
int predecessor = (bits >> 4) & 0xf;
int successor = bits & 0xf;
auto *state = static_cast<RiscVState *>(instruction->state());
state->Fence(instruction, fm, predecessor, successor);
}
fence
指令語意函式的關鍵部分是最後兩個函式
即可首先,系統會使用 Instruction
中的方法擷取狀態物件
類別,以及 downcast<>
至 RiscV 專用衍生類別然後Fence
系統會呼叫 RiscVState
類別的方法來執行圍欄作業。
商店販售方式也類似。首先是
系統會從基礎和偏移指令來源運算元計算記憶體存取權
則要儲存的值會從下一個來源運算元擷取。接下來,
您可以透過 state()
方法呼叫取得 RiscV 狀態物件
static_cast<>
,並呼叫適當的方法。
RiscVState
物件 StoreMemory
方法相對簡單,但具有
請留意下列幾項影響:
void StoreMemory(const Instruction *inst, uint64_t address, DataBuffer *db);
如畫面所示,這個方法會使用三個參數,指向商店的指標
指示本身、商店地址,以及指向 DataBuffer
的指標
包含商店資料的執行個體。請注意,您不需要指定大小
DataBuffer
執行個體本身包含 size()
方法。不過
適用於指示的目的地運算元
分配適當大小的 DataBuffer
執行個體。相反地
使用從 db_factory()
方法取得的 DataBuffer
工廠
Instruction
例項。該工廠具有方法 Allocate(int size)
該函式會傳回所需大小的 DataBuffer
例項。我們來看個例子
以下說明如何使用這個做法,為半字儲存庫分配 DataBuffer
例項
(請注意,auto
是 C++ 功能,可從右手推斷類型)
作業期間):
auto *state = down_cast<RiscVState *>(instruction->state());
auto *db = state->db_factory()->Allocate(sizeof(uint16_t));
取得 DataBuffer
例項後,我們就能照常寫入該例項:
db->Set<uint16_t>(0, value);
然後將其傳遞至記憶體儲存介面:
state->StoreMemory(instruction, address, db);
我們尚未結束所有作業。DataBuffer
例項會計入參照。這個
通常會由 Submit
方法理解及處理,因此為了保留
盡可能簡化常見用途不過,StoreMemory
並不是
。運作期間,系統會 IncRef
DataBuffer
執行個體
但完成後為 DecRef
。不過,如果語意函式
DecRef
本身的參照,永遠無法收回。因此,最後一行程式碼
:
db->DecRef();
儲存庫有三個商店函式
唯一的差別是
讀取及寫入記憶體似乎是當地其他地區的大好良機
範本輔助函式商店功能的唯一差異是
商店值的類型,因此範本必須將該值做為引數。
除此之外,只需傳入 Instruction
例項:
template <typename ValueType>
inline void StoreValue(Instruction *instruction) {
auto base = generic::GetInstructionSource<uint32_t>(instruction, 0);
auto offset = generic::GetInstructionSource<uint32_t>(instruction, 1);
uint32_t address = base + offset;
auto value = generic::GetInstructionSource<ValueType>(instruction, 2);
auto *state = down_cast<RiscVState *>(instruction->state());
auto *db = state->db_factory()->Allocate(sizeof(ValueType));
db->Set<ValueType>(0, value);
state->StoreMemory(instruction, address, db);
db->DecRef();
}
繼續完成商店語意函式並建構。您可以檢查 攻擊 rv32i_instructions.cc。
新增記憶體負載指示
需要實作的載入操作說明如下:
lb
- 載入位元組,並將符號延伸為字詞。lbu
- 將未帶正負號的位元組載入,從零延伸為一個字詞。lh
- 載入半形句號,然後再延伸為一個字詞。lhu
- 載入半字未帶正負號的半字,零延伸為一個字詞。lw
- 載入字詞。
載入指示是指模型建立模型時最複雜的指示
我們會介紹這個教學課程這類似於商店說明,因為
存取 RiscVState
物件,但會在每次載入作業時增加複雜度
指令分成兩個獨立的語意函式。第一種是
和分店指示類似
可以計算有效的地址
並啟動記憶體存取第二項是在記憶體中
存取完畢,並將記憶體資料寫入註冊目的地
運算元。
首先,請查看 RiscVState
中的 LoadMemory
方法宣告:
void LoadMemory(const Instruction *inst, uint64_t address, DataBuffer *db,
Instruction *child_inst, ReferenceCount *context);
相較於 StoreMemory
方法,LoadMemory
會使用兩個額外
參數:指向 Instruction
執行個體的指標,以及指向
參照計算的 context
物件。前者是子項指示,
實作註冊寫回寫入功能 (如 ISA 解碼器教學課程中所述)。這項服務
如要存取,請使用目前 Instruction
執行個體中的 child()
方法。
後者是指標的例項,該例項衍生自
在此情況下,ReferenceCount
會儲存 DataBuffer
例項,
包含載入的資料結構定義物件可透過
Instruction
物件中的 context()
方法 (雖然大多數操作說明都適用
設為 nullptr
)。
RiscV 記憶體載入的結構定義物件定義為下列結構:
// A simple load context class for convenience.
struct LoadContext : public generic::ReferenceCount {
explicit LoadContext(DataBuffer *vdb) : value_db(vdb) {}
~LoadContext() override {
if (value_db != nullptr) value_db->DecRef();
}
// Override the base class method so that the data buffer can be DecRef'ed
// when the context object is recycled.
void OnRefCountIsZero() override {
if (value_db != nullptr) value_db->DecRef();
value_db = nullptr;
// Call the base class method.
generic::ReferenceCount::OnRefCountIsZero();
}
// Data buffers for the value loaded from memory (byte, half, word, etc.).
DataBuffer *value_db = nullptr;
};
除了資料大小 (位元組 字元集和字詞) 以及載入的值是否延伸。 後者只會將影響因素為 child 指令。建立一個範本 用於主負載指示的輔助函式。這個程序與 儲存指示,但無法存取來源運算元來取得值。 會建立情境物件
template <typename ValueType>
inline void LoadValue(Instruction *instruction) {
auto base = generic::GetInstructionSource<uint32_t>(instruction, 0);
auto offset = generic::GetInstructionSource<uint32_t>(instruction, 1);
uint32_t address = base + offset;
auto *state = down_cast<RiscVState *>(instruction->state());
auto *db = state->db_factory()->Allocate(sizeof(ValueType));
db->set_latency(0);
auto *context = new riscv::LoadContext(db);
state->LoadMemory(instruction, address, db, instruction->child(), context);
context->DecRef();
}
如您所見,主要差異在於分配的 DataBuffer
執行個體
都會以參數的形式傳遞至 LoadMemory
呼叫,並儲存在
LoadContext
物件。
child 指令語意函式非常類似。首先,
呼叫 Instruction
方法 context()
即可取得 LoadContext
。
靜態轉換為 LoadContext *
其次,價值 (根據
type) 從載入資料 DataBuffer
執行個體讀取。第三是
系統會從目的地運算元分配 DataBuffer
執行個體。最後,
已載入的值會寫入新的 DataBuffer
例項,並寫入 Submit
。
再次提醒,建議使用範本輔助函式:
template <typename ValueType>
inline void LoadValueChild(Instruction *instruction) {
auto *context = down_cast<riscv::LoadContext *>(instruction->context());
uint32_t value = static_cast<uint32_t>(context->value_db->Get<ValueType>(0));
auto *db = instruction->Destination(0)->AllocateDataBuffer();
db->Set<uint32_t>(0, value);
db->Submit();
}
繼續並實作這些最後的輔助函式和語意函式。付款 注意您在範本中每個輔助函式使用的資料類型 呼叫,並將該函式對應到載入事件的大小和已簽署/未簽署性質 指示
您可以在 rv32i_instructions.cc。
建構並執行最終模擬工具
我們完成了所有艱難的步驟,可以打造出最終的模擬工具了。
頂層 C++ 程式庫能連結這些教學課程中的所有作業
位於other/
。不需要過度細看該程式碼。三
會前往日後的進階教學課程瀏覽相關主題。
將工作目錄變更為 other/
,然後進行建構。建構時應不使用
發生錯誤。
$ cd ../other
$ bazel build :rv32i_sim
在該目錄中,有一個簡單的「hello world」程式
hello_rv32i.elf
。如要在這個檔案上執行模擬工具,看看結果:
$ bazel run :rv32i_sim -- other/hello_rv32i.elf
您應該會看到:
INFO: Analyzed target //other:rv32i_sim (0 packages loaded, 0 targets configured).
INFO: Found 1 target...
Target //other:rv32i_sim up-to-date:
bazel-bin/other/rv32i_sim
INFO: Elapsed time: 0.203s, Critical Path: 0.00s
INFO: 1 process: 1 internal.
INFO: Build completed successfully, 1 total action
INFO: Running command line: bazel-bin/other/rv32i_sim other/hello_rv32i.elf
Starting simulation
Hello World
Simulation done
$
您也可以使用 bazel
run :rv32i_sim -- -i other/hello_rv32i.elf
指令,在互動模式下執行模擬工具。這樣會使
指令殼層在提示中輸入 help
,查看可用的指令。
$ bazel run :rv32i_sim -- -i other/hello_rv32i.elf
INFO: Analyzed target //other:rv32i_sim (0 packages loaded, 0 targets configured).
INFO: Found 1 target...
Target //other:rv32i_sim up-to-date:
bazel-bin/other/rv32i_sim
INFO: Elapsed time: 0.180s, Critical Path: 0.00s
INFO: 1 process: 1 internal.
INFO: Build completed successfully, 1 total action
INFO: Running command line: bazel-bin/other/rv32i_sim -i other/hello_rv32i.elf
_start:
80000000 addi ra, 0, 0
[0] > help
quit - exit command shell.
core [N] - direct subsequent commands to core N
(default: 0).
run - run program from current pc until a
breakpoint or exit. Wait until halted.
run free - run program in background from current pc
until breakpoint or exit.
wait - wait for any free run to complete.
step [N] - step [N] instructions (default: 1).
halt - halt a running program.
reg get NAME [FORMAT] - get the value or register NAME.
reg NAME [FORMAT] - get the value of register NAME.
reg set NAME VALUE - set register NAME to VALUE.
reg set NAME SYMBOL - set register NAME to value of SYMBOL.
mem get VALUE [FORMAT] - get memory from location VALUE according to
format. The format is a letter (o, d, u, x,
or X) followed by width (8, 16, 32, 64).
The default format is x32.
mem get SYMBOL [FORMAT] - get memory from location SYMBOL and format
according to FORMAT (see above).
mem SYMBOL [FORMAT] - get memory from location SYMBOL and format
according to FORMAT (see above).
mem set VALUE [FORMAT] VALUE - set memory at location VALUE(1) to VALUE(2)
according to FORMAT. Default format is x32.
mem set SYMBOL [FORMAT] VALUE - set memory at location SYMBOL to VALUE
according to FORMAT. Default format is x32.
break set VALUE - set breakpoint at address VALUE.
break set SYMBOL - set breakpoint at value of SYMBOL.
break VALUE - set breakpoint at address VALUE.
break SYMBOL - set breakpoint at value of SYMBOL.
break clear VALUE - clear breakpoint at address VALUE.
break clear SYMBOL - clear breakpoint at value of SYMBOL.
break clear all - remove all breakpoints.
help - display this message.
_start:
80000000 addi ra, 0, 0
[0] >
本教學課程到此結束。希望對您有所幫助。