如何新增/自定义扩展(三):修改spike,支持运行自定义扩展指令
如何新增/自定义扩展(三):修改spike,支持运行自定义扩展指令
——最近研究了一下如何基于开源toolchain和spike,自定义扩展。特此做一个记录/分享。
旨在分享,为避免某些收费平台转用,特此申明,本文不允许转载,只可贴本文链接。
写在最前面
全文共分三个章部分,列表如下:
Spike是 RISC-V ISA 模拟器。 可以仿真单核和多核的 RISC-V 处理器的功能模型。源码集成/关联在 riscv-tools 中。
Spike运行指令流程
Spike运行指令流程示意图
spike main解析参数后,将isa字符串通过sim_t传递给processor_t。- 自定义扩展对象的实现通过
libcustomext.so链接进 spike。- 自定义的扩展通过
REGISTER_EXTENSION注册到扩展表。REGISTER_EXTENSION宏将定义对应的注册类,声明static变量时调用构造函数。 - 构造函数中调用
register_extension函数。该函数中将扩展写入(或者说注册进)extensions map 中(注意:代码中的extensions存放的是自定义扩展)。
- 自定义的扩展通过
Spike注册自定义指令
-
processor_t构造时
a) 实例化isa_parser_t解析isa字符串。
b) 注册isa参数所指定的所有扩展,以及其指令(base insn)。
c) 实例化 MMU(build_opcode_map),指令 load 进 iCache。 -
processor_t step函数,一次执行一条指令。
- 先判断是否 debug 模式。是则执行 debug 模式操作;否则进行下一步。
- 执行指令序列:
- pending 中断处理
- 从 mmu 读取 insn
- 执行
- PC+=1
综上,根据isa_parser_t解析指令时的两条路径,可以按照标准 extension 方式新增 extension,也可以按照自定义方式新增 extension。
新增extension方案:类似std extension方式
修改isa parser
- 在
riscv-isa-sim/riscv/isa_parser.h文件的isa_extension_t枚举中,添加EXT_XIM,示例如下。
添加XiM扩展枚举
riscv-isa-sim-mod/disasm/isa_parser.cc文件中,在‘X’ extension之前,添加新增 extension,示例如下。
解析isa时,支持XiM扩展
如上图所示,对于‘X’自定义扩展,此处将扩展名称存入
std::set extensions 中。
指令定义
RISC-V 标准指令通过模板/宏定义的方式生成。可以参考上一篇文章《如何新增/自定义扩展(二):修改Toolchain,支持编译自定义扩展》。
声明指令
在riscv-isa-sim/riscv/encoding.h文件中添加新的操作码定义。
#define MATCH_MDMUL 0x800002b
#define MASK_MDMUL 0xf800007f
......
DECLARE_INSN(mdmul, MATCH_MDMUL, MASK_MDMUL)
其中的
DECLARE_INSN 定义在processor.cc文件中。
void processor_t::register_base_instructions()
{
#define DECLARE_INSN(name, match, mask) \
insn_bits_t name##_match = (match), name##_mask = (mask); \
isa_extension_t name##_ext = NUM_ISA_EXTENSIONS; \
bool name##_overlapping = false;
#include "encoding.h"
#undef DECLARE_INSN
......
}
定义指令
修改编译脚本,在riscv-tools/riscv-isa-sim/riscv/riscv.mk.in中添加如下示例行。
解析isa时,支持XiM扩展
1.
insn_list.h文件,其中定义指令,如DEFINE_INSN(mdmul)。DEFINE_INSN定义在processor.cc文件中register_base_instructions函数中。
点击查看代码
void processor_t::register_base_instructions()
{
......
#define DEFINE_INSN(name) \
extern reg_t fast_rv32i_##name(processor_t*, insn_t, reg_t); \
extern reg_t fast_rv64i_##name(processor_t*, insn_t, reg_t); \
extern reg_t fast_rv32e_##name(processor_t*, insn_t, reg_t); \
extern reg_t fast_rv64e_##name(processor_t*, insn_t, reg_t); \
extern reg_t logged_rv32i_##name(processor_t*, insn_t, reg_t); \
extern reg_t logged_rv64i_##name(processor_t*, insn_t, reg_t); \
extern reg_t logged_rv32e_##name(processor_t*, insn_t, reg_t); \
extern reg_t logged_rv64e_##name(processor_t*, insn_t, reg_t);
#include "insn_list.h"
#undef DEFINE_INSN
......
// add overlapping instructions first, in order
#define DECLARE_OVERLAP_INSN(name, ext) \
......
// add all other instructions. since they are non-overlapping, the order
// does not affect correctness, but more frequent instructions should
// appear earlier to improve search time on opcode_cache misses.
#define DEFINE_INSN(name) \
if (!name##_overlapping) \
register_base_insn((insn_desc_t) { \
name##_match, \
name##_mask, \
fast_rv32i_##name, \
fast_rv64i_##name, \
fast_rv32e_##name, \
fast_rv64e_##name, \
logged_rv32i_##name, \
logged_rv64i_##name, \
logged_rv32e_##name, \
logged_rv64e_##name});
#include "insn_list.h"
#undef DEFINE_INSN
-
从上面代码可以看到,
- 第一次
#include "insn_list.h"为一条指令声明了 8 个外部函数,然后定义 overlap 的指令。 - 第二次
#include "insn_list.h"定义非 overlap 的指令。
定义指令调用了函数
DEFINE_INSN使用了两次。
register_base_insn。
build/insn/mdmul.cc。在该文件中,定义了上述第一次#include "insn_list.h"时声明的 8 个外部函数,示例如下。
点击查看代码
reg_t logged_rv64i_mdmul(processor_t* p, insn_t insn, reg_t pc)
{
#define xlen 64
PROLOGUE;
#include "insns/mdmul.h"
EPILOGUE;
#undef xlen
}
#include "insns/mdmul.h"引用该指令的具体实现。
实现指令
在riscv-isa-sim/riscv/insns目录下,新增mdmul.h并实现该指令的逻辑功能。
点击查看代码
require_extension(EXT_XIM);
sreg_t rd = sext_xlen(RD);
sreg_t rs = sext_xlen(READ_REG(insn.xim_rs1()));
sreg_t rt = sext_xlen(READ_REG(insn.xim_rs2()));
uint32_t i = sext_xlen(insn.xim_imm());
for(uint32_t m=0; m<i; m++)
for(uint32_t n=0; n<i; n++)
{
uint32_t rs1 = MMU.load<uint32_t>(rs);
uint32_t rs2 = MMU.load<uint32_t>(rt);
MMU.store<uint32_t>(rd, rs1 * rs2);
rs+=4;
rt+=4;
rd+=4;
}
decode(if necessary)
如果增加的 extension/instruction 沿用现有 RISC-V 指令类型,不需要修改 decode。
本文示例中,自定义寄存器 bit 位,因此需要实现对应的 decode 功能。
在文件riscv-isa-sim/riscv/decode.h,类class insn_t中,添加如下示例代码。
//xim extension
uint64_t xim_rs1() { return x(12, 5); }
uint64_t xim_rs2() { return x(17, 5); }
uint64_t xim_imm() { return x(22, 5); }
rd沿用 RISC-V 指令格式 bit 位,因此不用再次定义。
指令asm解析
将指令解析位 asm 语句。否则运行时无法显示指令参数。
Spike运行时解析汇编指令失败示例
在文件riscv-isa-sim/disasm/disasm.cc中,增加如下示例代码。
点击查看代码
//xim extension
struct : public arg_t {
std::string to_string(insn_t insn) const {
return xpr_name[insn.xim_rs1()];
}
} xim_rs1;
struct : public arg_t {
std::string to_string(insn_t insn) const {
return xpr_name[insn.xim_rs2()];
}
} xim_rs2;
struct : public arg_t {
std::string to_string(insn_t insn) const {
return std::to_string((int)insn.xim_imm());
}
} xim_imm;
然后在函数void disassembler_t::add_instructions(const isa_parser_t* isa, bool strict)中增加如下示例代码。
点击查看代码
void disassembler_t::add_instructions(const isa_parser_t* isa, bool strict)
{
......
// XIM extension
if(ext_enabled(EXT_XIM))
{
add_insn(new disasm_insn_t("mdmul", match_mdmul, mask_mdmul, {&xrd, &xim_rs1, &xim_rs2, &xim_imm}));
}
......
}
1. 宏定义
ext_enabled展开后调用的时isa_parser_t中的extension_enabled函数。从下面定义可见,常用的单字符扩展(如'I'、'A'、'M'等)可以传递字符类型。
本示例是'XIM',因此此处采用
isa_extension_t枚举类型。
点击查看代码
bool extension_enabled(unsigned char ext) const {
return extension_enabled(isa_extension_t(ext));
}
bool extension_enabled(isa_extension_t ext) const {
return extension_table[ext];
}
riscv-isa-sim/disasm/disasm.cc中重新定义了宏DECLARE_INSN,其中定义变量match_mdmul, mask_mdmul。
点击查看代码
#define DECLARE_INSN(code, match, mask) \
const uint32_t match_##code = match; \
const uint32_t mask_##code = mask;
#include "encoding.h"
#undef DECLARE_INSN
解析后的指令格式如下所示。
Spike运行时解析汇编指令成功示例
新增extension方案:customext方式
和章节《3.1 修改 isa parser》不同,自定义扩展方式,不需要在‘X’ extension之前,添加新增 extension。因此,就进入‘X’自定义扩展,将扩展名称存入std::set<std::string> extensions中。
定义扩展
在riscv-isa-sim/customext文件夹中,创建xim.cc,并在其中实现扩展。
点击查看代码
#include "insn_macros.h"
#include "extension.h"
#include "decode_macros.h"
#include "mmu.h"
#include <cstring>
#define MATCH_MDMUL 0x800002b
#define MASK_MDMUL 0xf800007f
#define ILLEGAL_INSN_FUNC &::illegal_instruction
//定义指令各个bit字段
struct xim_insn_t
{
unsigned opcode : 7;
unsigned xim_rd : 5;
unsigned xim_rs1 : 5;
unsigned xim_rs2 : 5;
unsigned xim_imm : 5;
unsigned funct : 5;
};
//自定义指令和标准指令结构之间的转换
#define CHANGE_INSN_TO_XIM insn_bits_t insn_bits = insn.bits(); \
xim_insn_t xim; \
memcpy(&xim, &insn_bits, sizeof(xim));
//定义指令解析汇编字符串
#define DISASM_REG(name) struct: public arg_t \
{ \
std::string to_string(insn_t insn) const \
{ \
CHANGE_INSN_TO_XIM \
return xpr_name[xim.xim_##name]; \
} \
}xim_##name;
DISASM_REG(rd)
DISASM_REG(rs1)
DISASM_REG(rs2)
DISASM_REG(imm)
//实现指令功能
// implement for insn mdmul
static reg_t custom_xim_mdmul(processor_t* p, insn_t insn, reg_t pc)
{
CHANGE_INSN_TO_XIM;
state_t* state = p->get_state();
sreg_t rd = state->XPR[xim.xim_rd];
sreg_t rs = state->XPR[xim.xim_rs1];
sreg_t rt = state->XPR[xim.xim_rs2];
uint32_t i = xim.xim_imm;
for(uint32_t m=0; m<i; m++)
for(uint32_t n=0; n<i; n++)
{
uint32_t rs1 = p->get_mmu()->load<uint32_t>(rs);
uint32_t rs2 = p->get_mmu()->load<uint32_t>(rt);
p->get_mmu()->store<uint32_t>(rd, rs1 * rs2);
rs+=4;
rt+=4;
rd+=4;
}
return pc + 4; \
}
//自定义扩展类
class xim_t : public extension_t
{
public:
const char* name() { return "xim"; }
xim_t() {}
//扩展包含的指令
std::vector<insn_desc_t> get_instructions()
{
std::vector<insn_desc_t> insns;
insns.push_back((insn_desc_t)
{
MATCH_MDMUL, MASK_MDMUL,
custom_xim_mdmul, //fast_rv32i
custom_xim_mdmul, //fast_rv64i
custom_xim_mdmul, //fast_rv32e
custom_xim_mdmul, //fast_rv64e
custom_xim_mdmul, //logged_rv32i
custom_xim_mdmul, //logged_rv64i
custom_xim_mdmul, //logged_rv32e
ILLEGAL_INSN_FUNC //logged_rv64e, assume not surpport
});
return insns;
}
//扩展中的指令汇编
std::vector<disasm_insn_t*> get_disasms() {
std::vector<disasm_insn_t*> insns;
insns.push_back(new disasm_insn_t("mdmul", MATCH_MDMUL, MASK_MDMUL, {&xim_rd, &xim_rs1, &xim_rs2, &xim_imm}));
return insns;
}
};
//注册自定义扩展
REGISTER_EXTENSION(xim, []() { return new xim_t; })
修改 makefile,集成自定义扩展
在riscv-isa-sim/customext/customext.mk.in中添加xim.cc行,示例如下。
添加自定义扩展源码文件
方案比较
一句话概括,相比类似std extension方式,customext方式是将所有实现集中在一个文件中。
| 方案 | 优点 | 缺点 |
|---|---|---|
| 类似std extension方式 | 需要多处修改 | |
| customext方式 | 只需要修改两处 | 需要自定义指令字段转换 |
编译运行测试
在riscv-tools/riscv-isa-sim目录,重新编译spike,然后运行测试程序。
# 前面编译过,存在build目录。若不存在,请先 mkdir build && cd build && ../configure
$cd riscv-tools/riscv-isa-sim/build
$make -j8
# 运行
$spike pk mdmul.elf
z[0]=4, z[1]=8, z[0]=12
z[3]=16, z[4]=20, z[5]=24
z[6]=28, z[7]=32, z[8]=36
浙公网安备 33010602011771号