背景
我喜欢旧的8位6502芯片。在6502机器代码中解决PPCG方面的一些挑战甚至很有趣。但是,一些应该很简单的事情(例如,读入数据或输出到stdout)在机器代码中不必要地麻烦。因此,我想到一个粗略的想法:发明我自己的8位虚拟机,该虚拟机受6502的启发,但修改后的设计使其更能应对挑战。开始实施某些东西时,我意识到如果将VM的设计减少到最低限度,这本身就是一个不错的挑战:)
任务
实现符合以下规范的8位虚拟机。这是code-golf,因此以最少的字节数实现。
输入值
您的实现应采用以下输入:
一个无符号字节
pc
,它是初始程序计数器(0
基于VM的内存开始执行的内存中的地址)具有最大
256
条目长度的字节列表,这是虚拟机的RAM(及其初始内容)
您可以采用任何合理的格式输入此信息。
输出量
字节列表,这是VM终止后RAM的最终内容(请参阅下文)。您可以假设您只得到导致最终终止的输入。允许使用任何明智的格式。
虚拟CPU
虚拟CPU具有
- 一个8位程序计数器
- 一个8位累加器寄存器称为
A
和 - 一个称为的8位索引寄存器
X
。
有三个状态标志:
Z
-在执行某些操作后置零标志0
N
-在某些操作导致负数之后设置负标志(现在将结果的第7位设置为1)C
-进位标志由加法设置,并移位结果的“缺失”位
在开始的标志被全部清零,程序计数器设置为给定值和内容A
,并X
是不确定的。
8位值代表
- 范围内的无符号整数
[0..255]
- 有符号整数(2的补码),范围为
[-128..127]
取决于上下文。如果某个操作上溢或下溢,则该值会回绕(如果加法,进位标志也会受到影响)。
终止
虚拟机在以下时间终止
- 一
HLT
到达指令 - 访问了不存在的内存地址
- 程序计数器在内存外部运行(请注意,即使VM被提供了完整的256字节内存,它也不会回绕)
地址模式
- 隐式 -指令没有参数,隐含操作数
- 立即数-操作数是指令之后的字节
- 相对 -(仅用于分支)指令后的字节被签名(2的补码),并确定执行分支后要添加到程序计数器的偏移量。
0
是以下指令的位置 - 绝对 -指令后的字节是操作数的地址
- 索引 -指令加号
X
(寄存器)之后的字节是操作数的地址
使用说明
每条指令都由一个操作码(一个字节)组成,在寻址模式下是即时,相对,绝对和索引第二个参数字节。当虚拟CPU执行一条指令时,它会相应地(由1
或2
)递增程序计数器。
此处显示的所有操作码均为十六进制。
LDA
-将操作数加载到A
- 操作码:立即:
00
,绝对:02
,索引:04
- 标志:
Z
,N
- 操作码:立即:
STA
-存储A
到操作数- 操作码:立即:
08
,绝对:0a
,索引:0c
- 操作码:立即:
LDX
-将操作数加载到X
- 操作码:立即:
10
,绝对:12
,索引:14
- 标志:
Z
,N
- 操作码:立即:
STX
-存储X
到操作数- 操作码:立即:
18
,绝对:1a
,索引:1c
- 操作码:立即:
AND
-按位与的A
和操作数并入A
- 操作码:立即:
30
,绝对:32
,索引:34
- 标志:
Z
,N
- 操作码:立即:
ORA
-按位或的A
和操作数并入A
- 操作码:立即:
38
,绝对:3a
,索引:3c
- 标志:
Z
,N
- 操作码:立即:
EOR
-按位XOR(异或)的A
和操作数到A
- 操作码:立即:
40
,绝对:42
,索引:44
- 标志:
Z
,N
- 操作码:立即:
LSR
-逻辑右移,将操作数的所有位右移一位,位0进位- 操作码:立即:
48
,绝对:4a
,索引:4c
- 标志:
Z
,N
,C
- 操作码:立即:
ASL
-算术左移,将操作数的所有位左移一位,第7位进位- 操作码:立即:
50
,绝对:52
,索引:54
- 标志:
Z
,N
,C
- 操作码:立即:
ROR
-向右旋转,将操作数的所有位向右移一位,进位到位7,位0进位- 操作码:立即:
58
,绝对:5a
,索引:5c
- 标志:
Z
,N
,C
- 操作码:立即:
ROL
-向左旋转,将操作数的所有位向左移一位,进位转到位0,位7进位- 操作码:立即:
60
,绝对:62
,索引:64
- 标志:
Z
,N
,C
- 操作码:立即:
ADC
-带进位加法,操作数加进位加法A
,进位设置为溢出- 操作码:立即:
68
,绝对:6a
,索引:6c
- 标志:
Z
,N
,C
- 操作码:立即:
INC
-将操作数加1- 操作码:立即:
78
,绝对:7a
,索引:7c
- 标志:
Z
,N
- 操作码:立即:
DEC
-将操作数减一- 操作码:立即:
80
,绝对:82
,索引:84
- 标志:
Z
,N
- 操作码:立即:
CMP
-A
通过从中减去操作数来与操作数比较A
,忘记结果。溢出时清除进位,否则设置- 操作码:立即:
88
,绝对:8a
,索引:8c
- 标志:
Z
,N
,C
- 操作码:立即:
CPX
-比较X
-同CMP
为X
- 操作码:立即:
90
,绝对:92
,索引:94
- 标志:
Z
,N
,C
- 操作码:立即:
HLT
-终止- 操作码:隐式:
c0
- 操作码:隐式:
INX
-加X
一- 操作码:隐式:
c8
- 标志:
Z
,N
- 操作码:隐式:
DEX
-减X
一- 操作码:隐式:
c9
- 标志:
Z
,N
- 操作码:隐式:
SEC
-设置进位标志- 操作码:隐式:
d0
- 标志:
C
- 操作码:隐式:
CLC
-清除进位标志- 操作码:隐式:
d1
- 标志:
C
- 操作码:隐式:
BRA
-总是分支- 操作码:相对:
f2
- 操作码:相对:
BNE
-如果Z
清除标志则分支- 操作码:相对:
f4
- 操作码:相对:
BEQ
-如果Z
设置了标志则分支- 操作码:相对:
f6
- 操作码:相对:
BPL
-如果N
清除标志则分支- 操作码:相对:
f8
- 操作码:相对:
BMI
-如果N
设置了标志则分支- 操作码:相对:
fa
- 操作码:相对:
BCC
-如果C
清除标志则分支- 操作码:相对:
fc
- 操作码:相对:
BCS
-如果C
设置了标志则分支- 操作码:相对:
fe
- 操作码:相对:
操作码
如果发现任何操作码未映射到上述列表中的有效指令,则VM的行为是不确定的。
根据Jonathan Allan的要求,您可以选择自己的一组操作码,而不是“ 说明”部分中显示的操作码。如果这样做,则必须将完整的映射添加到答案中上面使用的操作码中。
映射应该是带有对的十六进制文件<official opcode> <your opcode>
,例如,如果您替换了两个操作码:
f4 f5
10 11
换行在这里无关紧要。
测试用例(官方操作码)
// some increments and decrements
pc: 0
ram: 10 10 7a 01 c9 f4 fb
output: 10 20 7a 01 c9 f4 fb
// a 16bit addition
pc: 4
ram: e0 08 2a 02 02 00 6a 02 0a 00 02 01 6a 03 0a 01
output: 0a 0b 2a 02 02 00 6a 02 0a 00 02 01 6a 03 0a 01
// a 16bit multiplication
pc: 4
ram: 5e 01 28 00 10 10 4a 01 5a 00 fc 0d 02 02 d1 6a 21 0a 21 02 03 6a 22 0a 22 52
02 62 03 c9 f8 e6 c0 00 00
output: 00 00 00 00 10 10 4a 01 5a 00 fc 0d 02 02 d1 6a 21 0a 21 02 03 6a 22 0a 22 52
02 62 03 c9 f8 e6 c0 b0 36
我可能会在以后添加更多的测试用例。
参考和测试
为了帮助自己进行实验,这里有一些(完全不是高尔夫)参考实现 -它可以stderr
在运行时将跟踪信息(包括反汇编的指令)输出到并转换操作码。
推荐的获取来源的方式:
git clone https://github.com/zirias/gvm --branch challenge --single-branch --recurse-submodules
或结帐分支challenge
并git submodule update --init --recursive
在克隆后进行操作,以获取我的自定义构建系统。
使用GNU make构建工具(只需输入make
,或者gmake
在您的系统上,默认的make不是GNU make)。
用法:gvm [-s startpc] [-h] [-t] [-c convfile] [-d] [-x] <initial_ram
-s startpc
-初始程序计数器,默认为0
-h
-输入为十六进制(否则为二进制)-t
-跟踪执行到stderr
-c convfile
-根据给出的映射转换操作码convfile
-d
-将结果内存转储为二进制数据-x
-将结果内存转储为十六进制initial_ram
-初始RAM内容,十六进制或二进制
请注意,转换功能将在运行时修改操作码的程序上失败。
免责声明:以上规则和规范仅代表挑战,而非此工具。这尤其适用于操作码转换功能。如果您认为此处提供的工具存在规范错误,请在评论中进行报告:)
BRA
(“总是分支”)没有在控制流中引入分支,是否不应该将其称为JMP
?
BRA
存在于诸如65C02和MC 68000之类的后续芯片设计中(6502没有这样的指令)JMP
。区别在于BRA
使用相对寻址和JMP
绝对寻址。因此,我只是遵循了这些设计-确实,这听起来并不合逻辑;)