在开发不同的设备时,经常会遇到一个问题:设备之间的算法在不同位置重复执行,而设备本身则完全不同。我正在开发三种设备,它们在某些地方会重复彼此的功能,它们使用三种不同的处理器(三种不同的体系结构),但是只有一种算法。为了以某种方式统一所有内容,计划编写一个最小的虚拟机。
总的来说,我看过Java,Lua和其他机器的字节码,但是我不想将所有可用的行李改写成另一种语言。因此,我们决定使用语言-C。尽管Java或Lua听起来仍然很有吸引力。[1] [2] [3] [4]。下一个标准是编译器。在我的项目中,我经常使用“由学生编写的匿名GCC(c)cookie”。那些。如果描述您的体系结构,则必须提出整个GCC(编译器,链接器等)。由于我是一个懒惰的人,所以我一直在寻找尽可能小的具有GCC支持的体系结构。并成为MSP430。简短的介绍
MSP430是一个非常简单的体系结构。它只有27条指令[5]和几乎任何寻址。虚拟机的构建始于处理器的上下文。操作系统中处理器的上下文是一种完全描述处理器状态的结构。并通过以下方式描述此虚拟处理器的状态:- 目前的团队
- 寄存器
- 中断寄存器的可选状态
- RAM和ROM的可选内容
MSP430 — 16. 16 4 . , ( ).
user guide msp430x1xxx [6]. — , . «-» (, ) , , — callback.
« » ( AVR [7][8]), (, i2c SD ).
同样在处理器的上下文中,描述了中断寄存器(SFR)。MSP430中断系统在[6]第2.2节中有最准确的描述。但是在描述的虚拟机中,我略微偏离了原来的虚拟机。在原始处理器中,中断标志位于外设寄存器中。在这种情况下,中断在SFR寄存器中描述。通过回调以相同的方式描述处理器外围设备,这使您可以随意创建自己的外围设备。下一个处理器项是命令多路复用器。命令多路复用器执行单独的功能。多路复用器从命令字中选择命令本身,对源和接收器进行寻址,然后执行所选命令的操作。单独的功能描述了源地址(SRC)和接收器。如何使用它
在项目存储库[9]中的examples文件夹中,有以下处理器的示例:- 用于IAR编译器的STM8
- 用于SDCC编译器的STM8
- 用于Keil Armcc编译器的STM32
- 适用于GCC编译器的AVR
在Cpu.h文件中,配置了处理器。以下设置说明:- RAM_USE_CALLBACKS-指示是否在处理器的上下文中使用调用(回调)而不是单个数组。是否使用调用来使用RAM(调用cpu.ram_read,cpu.ram_write)
- ROM_USE_CALLBACKS-是否使用调用来处理ROM(调用cpu.rom_read)
- IO_USE_CALLBACKS-是否使用调用与外围设备配合使用(调用cpu.io_read,cpu.io_write),如果为0,则应在cpu.c文件的msp430_io函数中描述与外围设备配合使用的功能
- RAM_SIZE-RAM大小(RAM),根据此参数自动重新计算结束地址
- ROM_SIZE-ROM大小(ROM),根据此参数自动重新计算起始地址
- IRQ_USE-指示是否将使用中断;如果为1,则允许中断
- HOST_ENDIANESS-指示主机控制器(运行虚拟机的控制器)的字节顺序。AVR,X86,STM32的体系结构是低端的,STM8是大端的
- DEBUG_ON-指示是否将使用调试。通过fprintf-stderr进行调试
使用库首先将cpu.c和cpu.h连接到项目。#include "cpu.h"
接下来是处理器上下文的声明。根据* _USE_CALLBACKS参数的使用,上下文声明代码将更改。对于所有* _USE_CALLBACKS = 1处理器上下文声明将如下所示:msp430_context_t cpu_context =
{
.ram_read_cb = ram_read,
.ram_write_cb = ram_write,
.rom_read_cb = rom_read,
.io_read_cb = io_read,
.io_write_cb = io_write
};
其中* _cb变量接受函数指针(请参见示例)。相反,对于* _USE_CALLBACKS = 0,声明将如下所示:msp430_context_t cpu_context =
{
.rom = { },
};
接下来是通过函数进行上下文初始化:msp430_init(&cpu_context)
并通过一个函数一次执行一条指令:while(1)
msp430_cpu(&cpu_context)
使用地址空间的回调如下所示:uint16_t io_read(uint16_t address);
void io_write(uint16_t address,uint16_t data);
uint8_t ram_read(uint16_t address);
void ram_write(uint16_t address,uint8_t data);
uint8_t rom_read(uint16_t address);
IO的地址相对于0地址空间进行传输(即,如果虚拟机程序访问分配给地址0x20的P1IN,则将地址0x20传递给函数)。相反,RAM和ROM的地址是相对于起点传输的(例如,访问地址0xfc06并将ROM从0xfc00启动时,地址0x0006将传递给该函数。即,该地址从0到RAM_SIZE,0-ROM_SIZE)这使您可以使用外部存储器,例如I2C(已经降低了处理器的速度)。如何完成
完全没有完成该项目。它可以正常工作,测试固件可以正常工作。但是大多数编译器实际上并不使用不同的特定命令(例如,Dadd是源和接收方的十进制加法(带有连字符))。因此,无需谈论与真实处理器的100%兼容性。自然,每个虚拟机命令大约有两打主机操作,因此谈论任何速度特性都没有意义。项目资源和更广泛的描述可在bitbucket.org上获得[9]。如果这个项目对某人有用,我会很高兴。[1] dmitry.gr/index.php?r=05.Projects&proj=12.%20uJ%20-%20a%20micro%20JVM[2] www.harbaum.org/till/nanovm/index.shtml[3]www.eluaproject.net[4]
code.google.com/p/picoc[5]
ru.wikipedia.org/wiki/MSP430[6]
www.ti.com/lit/ug/slau049f/slau049f.pdf[7]
ru.wikipedia.org/wiki/%D0%93%D0%B0%D1%80%D0%B2%D0%B0%D1%80%D0%B4%D1%81%D0%BA%D0%B0%D1%8F_%D0%B0%D1%80%D1%85%D0%B8%D1%82%D0%B5%D0%BA%D1%82%D1%83%D1%80%D0%B0[8]
ru.wikipedia.org/wiki/AVR[9]
bitbucket.org/intl/msp430_vm