226 lines
8.1 KiB
Python
226 lines
8.1 KiB
Python
#!/usr/bin/env python3
|
|||
|
|
# mu808 VM reference implementation in Python 3 / MicroPython
|
||
|
|
# See the documentation in README.md
|
||
|
|
# Created by Luxferre in 2025, released into public domain
|
||
|
|
|
||
|
|
import sys, math, random, re
|
||
|
|
|
||
|
|
advterm = False
|
||
|
|
try:
|
||
|
|
import tty, termios # for the character input port
|
||
|
|
advterm = True
|
||
|
|
except:
|
||
|
|
pass
|
||
|
|
|
||
|
|
MEMLIMIT:int = 16384 # 16K floats for data, 64K integers for program
|
||
|
|
PMEM = [[0,0,0,0] for i in range(0, MEMLIMIT)] # program memory
|
||
|
|
DMEM = [0.0 for i in range(0, MEMLIMIT)] # data memory
|
||
|
|
traceflag:bool = False
|
||
|
|
runlimit:int = MEMLIMIT # default runlimit is the program space size
|
||
|
|
runcount:int = 0
|
||
|
|
|
||
|
|
# mu808 mnemonics for assembly and disasssembly
|
||
|
|
mnemos = ['nop', 'jmp', 'iat', 'out', 'inp', 'set', 'cpy', 'fma',
|
||
|
|
'sub', 'div', 'mdf', 'abs', 'sqr', 'nel', 'tri', 'rnd']
|
||
|
|
|
||
|
|
# port output
|
||
|
|
def portout(port:int, data:float):
|
||
|
|
if port == 0: # standard value output port
|
||
|
|
print(data)
|
||
|
|
elif port == 1: # character output port
|
||
|
|
sys.stdout.write(chr(int(data)&255))
|
||
|
|
sys.stdout.flush()
|
||
|
|
|
||
|
|
# port input
|
||
|
|
def portin(port:int):
|
||
|
|
val = 0
|
||
|
|
if port == 0: # standard input port
|
||
|
|
try:
|
||
|
|
val = float(input())
|
||
|
|
except ValueError:
|
||
|
|
val = 0
|
||
|
|
if port == 2: # character input port
|
||
|
|
if advterm: # normal OS with termios
|
||
|
|
fd = sys.stdin.fileno()
|
||
|
|
old_settings = termios.tcgetattr(fd)
|
||
|
|
try:
|
||
|
|
tty.setraw(fd)
|
||
|
|
ch = sys.stdin.read(1)
|
||
|
|
finally:
|
||
|
|
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
|
||
|
|
else: # crippled OS without termios
|
||
|
|
ch = sys.stdin.read(1)
|
||
|
|
val = ord(ch)
|
||
|
|
return float(val)
|
||
|
|
|
||
|
|
# instruction line execution function
|
||
|
|
# the main mu808 logic is defined here
|
||
|
|
def ilexec(lno:int, cmd:int=0, x:int=0, y:int=0, z:int=0):
|
||
|
|
global runcount, PMEM, DMEM
|
||
|
|
halt = False
|
||
|
|
while not halt:
|
||
|
|
if traceflag:
|
||
|
|
print(f'PC: {lno} INSTR: {cmd} {x} {y} {z}')
|
||
|
|
DMEM[0] = 0.0 # force the value at 0 to always be 0
|
||
|
|
data_override = False
|
||
|
|
# perform a boundary check and prefetch the memory values
|
||
|
|
bcheck = True
|
||
|
|
try:
|
||
|
|
v1 = DMEM[x]
|
||
|
|
v2 = DMEM[y]
|
||
|
|
v3 = DMEM[z]
|
||
|
|
except IndexError:
|
||
|
|
bcheck = False
|
||
|
|
if bcheck:
|
||
|
|
# command switch starts here (commands <=0 and >15 are nops)
|
||
|
|
if cmd == 1: # JMP: conditional/unconditional, direct/indirect jump
|
||
|
|
if (v2 == 0 and x == 0) or (v2 > 0 and x == 1) or (v2 < 0 and x == 2) \
|
||
|
|
or (v2 >= 0 and x == 3) or (v2 <= 0 and x == 4) or (v2 != 0 and x == 5) \
|
||
|
|
or x == 6:
|
||
|
|
halt = False
|
||
|
|
lno = z - 1
|
||
|
|
elif (v2 == 0 and x == 7) or (v2 > 0 and x == 8) or (v2 < 0 and x == 9) \
|
||
|
|
or (v2 >= 0 and x == 10) or (v2 <= 0 and x == 11) or (v2 != 0 and x == 12) \
|
||
|
|
or x == 13:
|
||
|
|
halt = False
|
||
|
|
lno = int(v3) - 1
|
||
|
|
elif cmd == 2: # IAT
|
||
|
|
data_override = True
|
||
|
|
elif cmd == 3: # OUT
|
||
|
|
for i in range(x, y + 1):
|
||
|
|
portout(z, DMEM[i])
|
||
|
|
elif cmd == 4: # INP
|
||
|
|
for i in range(x, y + 1):
|
||
|
|
DMEM[i] = portin(z)
|
||
|
|
elif cmd == 5: # SET
|
||
|
|
DMEM[z] = (x % 10000) + (y % 10000) / 10000.
|
||
|
|
elif cmd == 6: # CPY
|
||
|
|
if x == 0:
|
||
|
|
DMEM[z] = y
|
||
|
|
elif x == 1:
|
||
|
|
DMEM[z] = v2
|
||
|
|
elif int(v3) < MEMLIMIT and int(v2) < MEMLIMIT:
|
||
|
|
DMEM[int(v3)] = DMEM[int(v2)]
|
||
|
|
elif cmd == 7: # FMA (v1 + v2 * v3)
|
||
|
|
try:
|
||
|
|
DMEM[z] = math.fma(v3, v2, v1)
|
||
|
|
except:
|
||
|
|
DMEM[z] = v1 + v2 * v3
|
||
|
|
elif cmd == 8: # SUB
|
||
|
|
DMEM[z] = v1 - v2
|
||
|
|
elif cmd == 9: # DIV
|
||
|
|
if v2 == 0:
|
||
|
|
DMEM[z] = 0
|
||
|
|
else:
|
||
|
|
DMEM[z] = v1 / v2
|
||
|
|
elif cmd == 10: # MDF
|
||
|
|
if v2 == 0:
|
||
|
|
DMEM[z] = math.floor(v1)
|
||
|
|
else:
|
||
|
|
DMEM[z] = float(int(v1) % int(v2))
|
||
|
|
elif cmd == 11: # ABS
|
||
|
|
DMEM[z] = math.fabs(v2)
|
||
|
|
elif cmd == 12: # SQR
|
||
|
|
DMEM[z] = math.sqrt(math.fabs(v2))
|
||
|
|
elif cmd == 13: # NEL
|
||
|
|
if x == 0:
|
||
|
|
DMEM[z] = math.exp(v2)
|
||
|
|
else:
|
||
|
|
if v2 == 0:
|
||
|
|
DMEM[z] = 0
|
||
|
|
else:
|
||
|
|
DMEM[z] = math.log(math.fabs(v2))
|
||
|
|
elif cmd == 14: # TRI
|
||
|
|
if x == 0:
|
||
|
|
DMEM[z] = math.sin(v2)
|
||
|
|
elif x == 1:
|
||
|
|
DMEM[z] = math.cos(v2)
|
||
|
|
else:
|
||
|
|
DMEM[z] = math.atan(v2)
|
||
|
|
elif cmd == 15: # RND
|
||
|
|
DMEM[z] = float(random.randint(int(v1), int(v2)))
|
||
|
|
# command switch ends here
|
||
|
|
# increment the program counter
|
||
|
|
lno += 1
|
||
|
|
if lno >= MEMLIMIT or lno < 1 or (runlimit > 0 and runcount > runlimit):
|
||
|
|
if traceflag:
|
||
|
|
print('Memory limit or runlimit hit, halting...')
|
||
|
|
halt = True
|
||
|
|
else: # fetch the next instruction
|
||
|
|
runcount += 1
|
||
|
|
if data_override and bcheck:
|
||
|
|
cmd = PMEM[lno][0]
|
||
|
|
x = int(v1) % MEMLIMIT
|
||
|
|
y = int(v2) % MEMLIMIT
|
||
|
|
z = int(v3) % MEMLIMIT
|
||
|
|
else:
|
||
|
|
cmd, x, y, z = tuple(PMEM[lno])
|
||
|
|
|
||
|
|
# instruction line entry function
|
||
|
|
def ilenter(lno:int, cmd:int=0, x:int=0, y:int=0, z:int=0):
|
||
|
|
global traceflag, runlimit, runcount
|
||
|
|
if lno > 0: # record the instruction line in memory
|
||
|
|
PMEM[lno] = [cmd, x, y, z]
|
||
|
|
elif lno == 0: # immediately execute the instruction line
|
||
|
|
runcount = 0
|
||
|
|
ilexec(lno, cmd, x, y, z)
|
||
|
|
elif lno == -1: # display a range of instructions from cmd to x
|
||
|
|
for i in range(cmd, x + 1):
|
||
|
|
print(f'@{i}:\t{mnemos[PMEM[i][0]]}|{PMEM[i][0]}', PMEM[i][1],
|
||
|
|
PMEM[i][2], PMEM[i][3])
|
||
|
|
elif lno == -2: # clear a range of data or instructions from x to y
|
||
|
|
for i in range(x, y + 1):
|
||
|
|
try:
|
||
|
|
if cmd == 0:
|
||
|
|
PMEM[i] = [0,0,0,0]
|
||
|
|
else:
|
||
|
|
DMEM[i] = 0.0
|
||
|
|
except IndexError:
|
||
|
|
pass
|
||
|
|
elif lno == -3: # turn on/off tracing
|
||
|
|
if cmd == 0:
|
||
|
|
traceflag = False
|
||
|
|
print('Tracing off')
|
||
|
|
else:
|
||
|
|
traceflag = True
|
||
|
|
print('Tracing on')
|
||
|
|
elif lno == -4: # set the runlimit to the value of cmd
|
||
|
|
runlimit = cmd
|
||
|
|
print('Runlimit set to', runlimit)
|
||
|
|
elif lno == -5: # exit to the environment
|
||
|
|
print('Bye!')
|
||
|
|
sys.exit(0)
|
||
|
|
|
||
|
|
# main REPL environment
|
||
|
|
if __name__ == '__main__':
|
||
|
|
print(f'mu808 v1 by Luxferre\nPROGMEM: {MEMLIMIT} steps\nDATAMEM: {MEMLIMIT} floats')
|
||
|
|
vreg = re.compile(r'[^-\d]+')
|
||
|
|
if len(sys.argv) > 1: # preload the file contents
|
||
|
|
fc = ''
|
||
|
|
try:
|
||
|
|
fd = open(sys.argv[1], 'r')
|
||
|
|
fc = fd.read()
|
||
|
|
close(fd)
|
||
|
|
except:
|
||
|
|
pass
|
||
|
|
instr = [] # current instruction cache
|
||
|
|
for instrpart in vreg.split(fc):
|
||
|
|
if len(instrpart) > 0:
|
||
|
|
instr.append(int(instrpart))
|
||
|
|
if len(instr) == 5: # full instruction registered
|
||
|
|
ilenter(int(instr[0]), abs(int(instr[1])),
|
||
|
|
abs(int(instr[2])), abs(int(instr[3])), abs(int(instr[4])))
|
||
|
|
instr = []
|
||
|
|
instr = [] # current instruction cache
|
||
|
|
while True: # main interactive loop
|
||
|
|
rinput = input('> ')
|
||
|
|
if len(rinput) > 0:
|
||
|
|
for v in vreg.split(rinput): # loop over the current input
|
||
|
|
if len(v) > 0:
|
||
|
|
instr.append(v)
|
||
|
|
if len(instr) == 5: # full instruction registered
|
||
|
|
ilenter(int(instr[0]), abs(int(instr[1])),
|
||
|
|
abs(int(instr[2])), abs(int(instr[3])), abs(int(instr[4])))
|
||
|
|
instr = []
|
||
|
|
|