OVERTURE指令集一览表(共18条)及汇编器

概述

OVERTURE 指令集是游戏图灵完备(Turing Complete)中的第一款虚拟计算机所使用的指令集。这里将描述 OVERTURE 指令集,为实际的 CPU 设计做参考。该指令集共有 16 条指令、2 条可选的拓展指令。

我从来没觉得当肉编器开心过…

指令集描述

B7B6B5B4B3B2B1B0Type
00uint6_t immediateImmediate
01XXXOpcodeCompute
10Source RegDest RegCopy
11XXXOpcodeCompare

OVERTURE 指令集如上图所示,分为 4 部分,分别为:

  • 立即数模式。低 6 位数据作为立即数写入 Reg0。通过立即数可以写入 0~63 的字面值。
  • 计算模式。通过低 3 位选择运算类型。从 Reg1 和 Reg2 中读取数值并将计算结果保存在 Reg3。可能的运算有 OR, NAND, NOR, AND, ADD, SUB。
  • 复制模式。将源寄存器(Source Reg)的值复制到目的寄存器(Dest Reg)。
  • 比较模式。也称跳转模式。读取 Reg3 中的值并和指定的条件比较。条件满足时跳转到 Reg0 的位置。Reg0 内的值不能为 0(用作指示器)。

寄存器

OVERTURE 指令集架构可以最多控制 8 个寄存器/设备。在图灵完备游戏中,Reg0~Reg5 为通用寄存器,Reg6 为 IO 设备。

计算命令

Type CodeDon’t CareOpcodeOperation
01000000OR
01000001NAND
01000010NOR
01000011AND
01000100ADD
01000101SUB

比较命令

Type CodeDon’t CareOpcodeOperation
11000000Never
11000001=0
11000010<0
11000011<=0
11000100Always
11000101!=0
11000110>=0
11000111>0

拓展寻址空间

目前 PC 的寻址空间为 255byte。为了增加实用性,增加一条规定:

  • 程序跳转时,如果 Reg0 中的值为 0,那么选择跳转到 Reg1(低 8 位)和 Reg2(高 8 位)指示的位置。
    通过增加地址位数,可以获得 65kbyte 的寻址空间。

拓展指令

这部分是在 OVERTURE 指令集基础上拓展的指令,增加了对 RAM 的读写操作,使得整个系统更加实用。

  • 计算模式下的内存指令。把 Reg3 处的值存储在 RAM 中 Reg0 所指示的位置。或者把 Reg0 所指示的位置的 RAM 值读取到 Reg3 处。
Type CodeDon’t CareOpcodeOperation
01000110Store Word
01000111Load Word
  • 如果 Reg0 中的值为 0,那么选择 Reg1(低 8 位)和 Reg2(高 8 位)作为 RAM 的地址。

汇编器(Python)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
'''
The Overture CPU assembler.

Author: CoccaGuo
Date: 2023/07/15

Registers:
t0~t3 temp regs,
s0~s1 saved regs,
sp stack ptr,
io gpio.

Instructions:
- OR
- NAND
- NOR
- AND
- ADD
- SUB
- EQ0
- LT0
- LTEQ0
- JMP
- NEQ0
- GTEQ0
- GT0
- SW
- LW

- LABEL name
- name

- MOV R# R#
- IMME number
- #DEFINE name const
- raw_cmd


Use `;` for comments.
'''
REGS = {
'T0': 0,
'T1': 1,
'T2': 2,
'T3': 3,
'S0': 4,
'S1': 5,
# 'SP': 6,
'IO': 6,
}

CMDS = {
# CALC CMDS
'OR': 0b01_000_000,
'NAND': 0b01_000_001,
'NOR': 0b01_000_010,
'AND': 0b01_000_011,
'ADD': 0b01_000_100,
'SUB': 0b01_000_101,
# COND CMDS
'EQ0': 0b11_000_001,
'LT0': 0b11_000_010,
'LTEQ0':0b11_000_011,
'JMP': 0b11_000_100,
'NEQ0': 0b11_000_101,
'GTEQ0':0b11_000_110,
'GT0': 0b11_000_111,
# MEM CMDS; NOT USED HERE
# 'SW': 0b01_000_110,
# 'LW': 0b01_000_111,
}


def digit(op: str):
try:
i = int(op, base=0)
except ValueError:
return None
return i


def register(reg: str):
if reg in REGS.keys():
return REGS.get(reg)

if reg.startswith('R'):
reg = reg.strip('R')
if reg.startswith('REG'):
reg = reg.strip('REG')
if reg.startswith('$'):
reg = reg.strip('$')

try:
i = int(reg.strip('R'))
if i>=0 and i<8:
return i
else:
return None
except ValueError:
return None


def _macro(instr: str):
'''
convert defs into its defination.
'''

defs = dict()

ops = instr.upper().split('\n')

for ind, op in enumerate(ops):
op = op.strip()
if not op: continue
if op.startswith(';'): continue

if op.startswith('#'):
op = op.strip('#').strip()
if op.startswith('DEFINE'):
_defs = op.split()
if len(_defs) < 3:
raise SyntaxError(f'Line {ind+1}: `#DEFINE` should follow with an identifier and a const.')
_name = _defs[1].strip()
_const = ' '.join(_defs[2:]).strip()
if not _name or not _const:
raise SyntaxError(f'Line {ind+1}: `#DEFINE` should follow with an identifier and a const.')
if _name in defs.keys():
raise SyntaxError(f'Line {ind+1}: Multiple definition of #DEFINE `{_name}`.')
defs[_name] = _const

replaced = list()
lines = instr.upper().split('\n')
for l in lines:
tokens = [defs.get(t, t) for t in l.split()]
replaced.append(' '.join(tokens).strip())
return '\n'.join(replaced).strip()


def _compile(instr: str):
'''
Compile asm into binarys.
'''
bin = list() # binary code
labels = dict()

pc = 0 # prog counter
errs = list()

ops = instr.upper().split('\n')

for ind, op in enumerate(ops):
op = op.strip()
if not op: continue
if op.startswith(';'): continue
if op.startswith('#'): continue

# parsing command
if op in CMDS.keys():
bin.append(CMDS.get(op))
pc += 1
continue

# parsing label
if op.startswith('LABEL'):
if len(op.split('LABEL')) < 2:
errs.append(SyntaxError(f'Line {ind+1}: `LABEL` should follow an identifier.'))
continue
_label = op.split('LABEL')[1].strip()
if not _label:
errs.append(SyntaxError(f'Line {ind+1}: `LABEL` should follow with an identifier.'))
elif labels.get(_label):
errs.append(SyntaxError(f'Line {ind+1}: Multiple definition of LABEL `{_label}`.'))
else:
labels[_label] = pc
continue

if op in labels.keys():
_pc_ind = labels.get(op)
if _pc_ind > 63:
errs.append(IndexError(f'Line {ind+1}: LABEL index out of range (0~63).'))
elif _pc_ind == 0x00:
errs.append(IndexError(f'Line {ind+1}: LABEL index should not be zero.'))
bin.append(labels.get(op))
pc += 1
continue


# parse mov
if op.startswith('MOV'):
_regs = op.split()
if len(_regs) != 3:
errs.append(SyntaxError(f'Line {ind+1}: `MOV` should follow with two regs.'))
continue
_reg_src = register(_regs[1].strip())
_reg_dst = register(_regs[2].strip())
if _reg_src is not None and _reg_dst is not None:
_cmd = 0x80 | _reg_src << 3 | _reg_dst
bin.append(_cmd)
pc += 1
continue
else:
errs.append(SyntaxError(f'Line {ind+1}: `MOV` should follow with two regs.'))
continue

# parse imme
if op.startswith('IMME'):
_imme = op.replace('IMME', '').strip()
_imme = digit(_imme)
if _imme is None:
errs.append(SyntaxError(f'Line {ind+1}: `IMME` should follow with an integer.'))
continue
if _imme >= 0 or _imme < 64:
bin.append(_imme)
pc += 1
continue
else:
errs.append(SyntaxError(f'Line {ind+1}: Immediate number out of range (0~63).'))
continue


# parse raw
_raw = digit(op)
if _raw is None:
errs.append(SyntaxError(f'Line {ind+1}: Unidentified instruction `{op}`.'))
elif _raw > 255 or _raw < 0:
errs.append(IndexError(f'Line {ind+1}: Command out of range 0~255.'))
else:
bin.append(_raw)
pc += 1
continue

if not errs:
return 0, bin
else:
return -1, errs


if __name__ == '__main__':
code = r'''
; Example code for this tiny assembler,
; The Maze

; forward a step
; turn left
; if left is wall:
; turn right till not wall
; goto 1

# define left 0
# define forward 1
# define right 2
# define pass 3
# define interact 4
# define wall 1
# define hlt 0b10_100_100

hlt
label start
imme forward
mov t0 io
imme left
mov t0 io
label read_input
mov io t3
start
eq0
right
mov t0 io
interact
mov t0 io
read_input
jmp
'''
asm = _macro(code)
code, arr = _compile(asm)
if code == 0:
for b in arr:
print(b, end=' ')
else:
for err in arr:
print(err)
print()