RISC-V on Rust 从零开始(8) - 实作instruction decoder

这次要来实作指令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);

完整的程式码可以参考此连结


关于作者: 网站小编

码农网专注IT技术教程资源分享平台,学习资源下载网站,58码农网包含计算机技术、网站程序源码下载、编程技术论坛、互联网资源下载等产品服务,提供原创、优质、完整内容的专业码农交流分享平台。

热门文章