0、内容声明
本文章内容源于Marie.js使用手册,代码摘自Marie.js的样例代码,若有错误,敬请斧正。
1、Marie.js简介
这是一个非常精简易用的CPU仿真器,笔者曾自己用MFC写过类似的东西,但是效果和这个差了十万八千里。该仿真器是一个16位机,有三个通用寄存器,和一个只有16个指令的指令集,内存配置只有4K,不过这足够初学者研究一段时间了,但是它非常简单易用,满足了各种常用的X86架构和ARM架构处理器的指令。
2、配置介绍
如上图,共7个寄存器,意义如下:
AC 寄存器:通用寄存器、累加器,保存中间过程、累加、暂存等功能。
PC 寄存器:程序计数器,指向内存中下一条要执行的指令
MAR 寄存器:内存访问寄存器,简简单单的与地址线相连
MBR 寄存器:内存缓冲寄存器,简简单单的与数据线相连
IR 寄存器:指令寄存器,存放当前在执行的指令
Out、In寄存器:通用寄存器,I/O会使用这两个寄存器,不是用I/O时,可以当做通用寄存器用
7个寄存器和内存共8中调取数据的方式,正好可以使用3位表示。其中,MAR和PC因为和内存地址交互,只有12位,其余5个寄存器和内存中的字均为16位。
3、指令集
数学逻辑运算
Add X | 将X作为操作数,与AC相加,并保存到AC中 | |
Subt X | 将X作为操作数,用AC减之,并保存到AC中 | |
Addl X | 将X作为操作数的指针,与AC相加,并保存到AC中 | |
Clear | 将AC清零 |
与内存的数据交换
Load X | 从内存地址X中取数存值AC | |
Store X | 将AC存入地址为X的内存中 | |
Loadl X | 将X处存储的内容作为指针,获取操作数存入AC | |
Storel X | 将X处存储的内容作为指针,将AC的值存入指向的内存 |
I/O
Input | 要求用户输入一个值替换当前AC | ---- |
Output | 将AC的值输出 | ---- |
分支
Jump X | PC跳转到X的地址 | ---- |
Skipcond(C) | 基于AC和C跳过下一条语句的执行 | 跳转条件: C=000&&AC<0 C=400&&AC=0 C=800&&AC>0 |
子程序
JnS X | 将PC存至X的地址并且跳转至X+1 | Jump X+1 |
Jumpl X | PC跳转到X指向的地方 |
后续单有一部分说明子程序/子函数的执行过程
程序终止
Halt | 程序终止 | ---- |
共16个指令,可以用4位表示
4、寻址方式
按照指令集中的指令名称,可以发现有部分指令有两种形式,即不带‘l'的形式和带'l'的形式。如:
Add | Addl |
Load | Loadl |
Store | Storel |
Jump | Jumpl |
左侧的一列为不含‘l’的指令,右侧为含‘l’的指令,左侧直接将存储空间X中存储的内容作为操作数,为直接寻址;右侧将存储空间X中存储的内容作为指针,使用指针寻找操作数,为间接寻址。
类似的直接寻址指令还有subt等;类似的间接寻址指令还有JnS等。
5、简单代码示例与注释
加法代码
//输入X和Y,并存储至内存
Input
Store X
Input
Store Y
//AC中当前存储的是Y,所以Add X即可完成加法过程
Add X
Output
Halt
//声明内存空间
X, DEC 0
Y, DEC 0
HelloWorld
LOOP, Load PTR
// 输出字符
LoadI PTR
Output
// 字符串指针自增
Load PTR
Add ONE
Store PTR
// 计数器自增
Load CTR
Add ONE
Store CTR
// 检查计数个数,是否完全输出字符串
Subt WORDS
Skipcond 400
Jump LOOP
Halt
// 数据空间
ONE, DEC 1
CTR, DEC 0
WORDS, DEC 12
// 初始化指针指向字符串
PTR, ADR S
// "Hello World!"
S, HEX 48
HEX 45
HEX 4C
HEX 4C
HEX 4F
HEX 20
HEX 57
HEX 4F
HEX 52
HEX 4C
HEX 44
HEX 21
6、子程序/子函数代码过程
先上代码
//调用子函数
JnS func
Halt
func, HEX 000 // 此处保存要返回的地址
...
Jumpl func
这段代码中,func是一个函数的入口地址,JnS将PC指向func的下一字,跳过的一字保存这跳之前指针的下一位置,跳之前语句为JnS,那么下一个位置(本程序中为Halt),也就是函数返回之后要继续执行的位置。
那么代码是如何返回的呢?func函数执行完毕后,Jumpl将func作为指针间接寻址,将其地址赋值给PC,本程序中也就是Halt语句的地址被赋值给了PC,那么程序就可以正常返回了!
7、其它注意事项
Marie中对数据段和代码段没有加以区分,为了避免将数据当作代码执行,应当将数据放置于代码之后,但是有特殊情况,如子程序中,程序的返回地址会放在程序开头的位置,也正是因为这样的机制,可以让如此精简的Marie可以使用子函数的功能。
关于内存中“数据段”中数据的常见表示格式,见下表:
DEC | 十进制 |
HEX | 十六进制 |
ADR | 地址标签(一般做指针用) |
内存的4K容量为内存中字的数量,共4K个16位的字,实际内存为8KB。
推荐的代码格式:
先数据再代码
Jump begin
//此为数据段
...
//数据段结束
begin,
//此为主程序
...
Halt
//主程序结束
func1,Hex 000
//子程序1
...
Jumpl func1
//子程序1结束
...
先代码再数据
//此为主程序
...
Halt
//主程序结束
func1,Hex 000
//子程序1
...
Jumpl func1
//子程序1结束
...
//此为数据段
...
//数据段结束
每个子函数前有所需数据
Jump begin
//主程序数据段
begin,
//此为主程序
...
Halt
//主程序结束
//子程序1数据段
func1,Hex 000
//子程序1
...
Jumpl func1
//子程序1结束
...
关于注释的问题:单斜线'/'即可完成整行的注释,上述代码中写双斜线是为了满足CSDN的C++代码的格式要求,如果个人习惯双斜线,也可以使用