这次要来实作指令decoder,负责pipeline中的decode stage。计组教科书上常见的pipeline架构依序为:fetch、decode、execute、memory、write back,当然在一个instruction-accurate(IA)的模拟器中,没有必要去切得这么细,目前只要大致分成fetch、decode以及execute三个步骤就好,memory与write back就合併在execute stage。这三个步骤做的处理罗列如下:
Fetch: 从memory中读取指令。Decode: 解析指令的raw byte,并且呼叫指令相对应的函式。Execute: 执行指令,依据指令的不同可能会读写记忆体(对应memory stage),或修改暂存器或PC的内容(对应write back stage)。Fetch Stage
暂时先以常数取代,等memory model实作完成后再来时做此stage。
Decode Stage
稍微研究了现有的模拟器是如何实作decoder,发现主要有两种方法:
将指令的特徵放在一个array,每次decode都去查表看符合哪个entry,很明显这个方法複杂度为O(n),n为指令列表的长度。通常这个方法会配合另一个buffer来记录最近match到的指令,每次decode就先查buffer,没有的话才查原本完整的表,由于locality的关係,只要这个buffer的大小合适,就能大大的降低decode所需的时间。fn decode(inst_bytes) -> InstID { for entry in instruction_table_buffer { if (inst_bytes & entry.mask) == entry.opcode return entry.inst_id for entry in instruction_table { if (inst_bytes & entry.mask) == entry.opcode { instruction_table_buffer.add(entry) return entry.inst_id } }}
利用条件判断的方式,判断是哪个指令,複杂度为O(1),虽然複杂度较低,可以预见code会比较杂乱,会有大量的switch-case。fn decode(inst_bytes) -> InstID { switch (inst_bytes & mask_a) { case 0: { switch (inst_bytes & mask_b) { case 0: return InstID::SUB ... } } case 1: return InstID::ADD ... }}
这边採用第二种方案,并且保留弹性,之后也可以实作第一种方案,并且比较两种的效能。
Execute Stage
很简单,直接呼叫对应的指令function就好。
Summary
将以上的三个stage结合起来就可以完整的执行一条指令:
impl RVCore { fn step(&mut self, inst_bytes: u32) { // Decode let inst = self.id_instance.decode(inst_bytes); // Execute (inst.operate)(self, &inst); self.pc += inst.len; }}let mut core = RVCore{};// Fetch, 假设fetch的结果为0x00002197(AUIPC)core.step(0x00002197);
完整的程式码可以参考此连结