MIPS64 指令集模拟器的建模与实现方法

摘 要:用软件编程的方法介绍一个与MIPS32/64 指令集兼容的指令集模拟器的建模与实现过程。该方案用C++来描述处理器的硬件行为,通过在编译时选择不同的选项分别实现对MIPS32 和MIPS64 指令集构架的嵌入式处理器的模拟,实现除浮点数以外的所有指令的译码和执行。该方案的主要好处是代码可重用,指令扩展性能好,可以同时兼容MIPS32 和MIPS64 指令集的模拟。

1 概述
指令集模拟器是在宿主机上模拟虚拟机程序运行的软件,它是开发嵌入式系统的重要工具。目前的MIPS ISS,如OCMIPS、SPIM 及QEMU 都是用C 语言构建MIPS 处理器内核,但有一些不足,如有些在全系统仿真中进行交互操作和并行处理时不能很好地兼容第三方的模块[1];OCMIPS 和SPIM 不能兼容MIPS64 指令等。本文以C++作为处理器硬件描述语言,介绍了在基于X86 指令系统的PC 平台上设计一个兼容MIPS32/64 指令集的CPU 模拟器的主要过程,并用实例说明。

2 处理器建模
图1 是MIPS64 模拟器结构,它实现了MIPS32/64 处理器的译码功能。处理器建模主要是将译码执行的各个部件封装为C++类的形式。

图1 模拟器结构

各部分建模的基本方法分述如下:
(1)定义硬件能直接识别和处理的数据类型和格式。数据格式有2 种,一种是使用标准库中的数据类型,如uint32_t、uint16_t 及uint8_t 等,另一种是用C++自定义数据类型来表示。MIPS32 和MIPS64 指令集兼容问题的具体实现是在MIPS32 和MIPS64 的目录中分别定义bus_width_t 数据类型为uint32_t 和uint64_t,并通过namespace 来解决数据类型冲突。编译时选择不同选项可分别实现对2 种指令集构架的嵌入式处理器的模拟。

(2)处理器系统的实现是通过MIPS_Processor 类定义所有通用寄存器、堆栈指针、内存单元、中断异常及debug 端口等部件并实现其操作。

(3)存储器系统主要由MIPS_MMU 和MIPS_TLB 类定义。先定义与数据、程序存储器对应的一个或多个数组,再定义一组访问函数来实现地址转换、范围检查、大小端定义、字、半字和字节的访问。

(4)在MIPS_Processor 类中定义和实现PC 及所有的通用寄存器的访问, 在MIPS_CauseRegister 和MIPS_Status Register 类中定义和实现各控制寄存器的访问。如MIPS_StatusRegister 类的定义:
class MIPS_StatusRegister
{ public: MIPS_StatusRegister( );
bool bev; //启动异常标志
bool erl;//错误级别标志,CPU 收到错误数据时设置该位
bool exl; //异常级别标志
bool ie; //全局中断使能位
bool im[8]; //中断掩码位
};

(5)指令译码系统的定义首先由MIPS_Instruction 类定义各种指令类型的执行和显示,然后在decode 中根据操作码和功能码查找指令并依据指令格式对操作数域进行解析,最后,将所有MIPS 指令分为arithmetic、branch、load&store 和coprocessor 4 类,并在其中定义和实现具体的指令。

(6)模拟器运行时首先调用Elf_loader 文件[2],经解析后得到的程序和数据用于初始化内存。仿真器执行指令,并将结果输出于控制台。

3 指令译码及实现
除浮点数外,MIPS32/64 指令集共有202 条指令,其中47 条MIPS64 指令。指令字长32 位,且是对准的。MIPS 的2 种寻址方式描述直接编码到操作码中。为便于流水线操作和译码,所有的MIPS 指令格式分为R 型、I 型和J 型3 种编码格式。

3.1 指令操作类型的确定
本方案用多级查找方式实现。首先将需要译码的指令按操作码进行分类,操作码相同的定义为同一个分组,如操作码为000000 的所有指令放在group0 中。对于操作码唯一的指令直接定义指令类型,如mips_sb、mips_j、mips_ori 等。MIPS 指令为6 位定长操作码,可分成64 种分组或指令类型,并通过数组来实现。如将group0、group1、mips_sb、mips_j等放在数组mips_table_main[64]中。其次将各分组中按功能码相同与否进行二次分组,如group0 组中又分为group00、group01 等组,其中每组指令的操作码和功能码都相同。最后在group00、group01 中根据特殊标志位来确定具体的操作类型。

例如,对以下5 条指令进行译码:
000000 00000 00000 00000 00000 000000 nop
000000 00000 00000 00000 00001 000000 ssnop
000000 sssss ttttt 00000 00000 011010 div
001000 sssss ttttt iiiiiiiiiiiiiiii addi
000011 iiiiiiiiiiiiiiiiiiiiiiiiii jal

其中,s 表示操作源rs;t 表示操作目的rt;i 表示偏移量或立即数。它们的个数表示代码位数。

(1)定义分组
本例中相同操作码的指令有3 条,其中,nop 和ssnop指令的功能码相同,故可将nop、ssnop 和div 这3 条指令放在group0 中,再将nop 和ssnop 放入group00 中:
typedef MIPS_Instruction* (*mips_func)(bus_width instr);
mips_func mips_table_main[64]
{ &group0; &mips_addi; &mips_jal;

}

(2)通过操作码分析指令类型
MIPS_Instruction* mipsDecode(uint32_t instr,bus_width pc)
{ uint32_t mainopc = (instr >> 26) & 0x3f; //取操作码
return mips_table_main[mainopc](instr);
}

上述函数对指令译码后,可确定指令类型为group0、addi和jal,再通过步骤(3)对group0 中的指令作进一步译码,最终确定每条指令的具体类型。

(3)根据功能码和特殊标志位确定指令类型
static MIPS_Instruction *mips_group0(bus_width instr)
{ uint32_t ext = instr & 0x3f; //取功能码
switch (ext)
{ case 0 :
switch ((instr>>6)&0xfffff) //取特殊标志位的值
{ case 0 : return new MIPS_nop(bus_width &instr);
case 1 : return new MIPS_ssnop(bus_width &instr); }
case 1 : return new MIPS_div(bus_width &instr); }
}

3.2 操作数的选取
由decode.hpp 文件根据寄存器的类型和操作数的特征通过定义内联函数实现操作数的确定。在所有使用I 类型寄存器的指令中,大部分都需要立即数进行带符号扩展,但有些指令如andi、ori、lbu 等需要对立即数进行0 扩展。故对I类型寄存器的定义如下:
inline void MIPS_IS_FORMAT(const uint32_t &instr, uint8_t &s,
uint8_t &t, bus_width &i)
{ i = instr & 0xffff; //操作数为带符号扩展的I 寄存器格式
if(i & 0x8000) i|= 0xffff0000; //取操作数i 并进行带符号扩展
t = (instr >> 16) & 0x1f;
//取操作数t, 其值为通用寄存器的编号
s = (instr >> 21) & 0x1f;
}
inline void MIPS_IU_FORMAT(const uint32_t &instr, uint8_t &s,
uint8_t &t, bus_width &i) //操作数为0 扩展的I 寄存器格式
{ i = instr & 0xffff; //取立即数i 并进行0 扩展
t = (instr >> 16) & 0x1f; s = (instr >> 21) & 0x1f;
}

有些指令,如bc2t、bc2f 及bc2fl 是用I 格式中第16 位和第17 位来确定操作类型,定义如下:
inline void MIPS_BC_FORMAT(const bit_width &instr, uint8_t
&c, bus_width &f)
{ c = (instr >> 18) & 0x7; f = instr & 0xffff; }

3.3 指令的实现
指令译码完成后首先调用exec 函数完成指令的执行并将PC 值加4 取下一条指令,然后调用display 函数以硬件语言的形式在终端上打印指令的操作码和操作数。

4 应用实例
使用交叉编译工具mips-elf-gcc 和mips64-elf-gcc,版本4.3.2,操作系统Ubuntu 9.04。在simsoc 平台下[1],分别使用MIPS32 和MIPS64 指令模型,运行3 个程序,其中sorting程序对624 个5 位数整数分别进行快速排序、堆排序、合并排序、希尔排序、插入排序和冒泡排序;loop 程序实现了0~0XFFFFFF 的计数;Timer 程序实现了给定频率下的时钟定时功能。这3 个程序可实现90%以上的MIPS 指令测试。运行结果如表1 所示,说明该模拟器对MIPS64 的模拟效果强于MIPS32。

表1 MIPS 指令测试结果

5 结束语
本模拟器模型是针对未来全系统仿真中应用SystemC和TLM模型所设计,在实验室中主要应用与Simsoc平台实现对MIPS32/64指令集的模拟,目前该模拟器实现了典型的解释型模拟,下一步的研究方向主要是在该模型中实现动态模拟技术,并将其用于全系统仿真中。

作者:蔡启先,刘 明,余祖峰 来源:计算机工程第36 卷第18 期

--电子创新网--
粤ICP备12070055号