133 lines
5.6 KiB
Python
133 lines
5.6 KiB
Python
#!/usr/bin/env python3
|
|||
|
|
# n808 VM reference implementation in Python 3/MicroPython
|
||
|
|
# Supports the entire n808 spec
|
||
|
|
# See README.md for all documentation
|
||
|
|
# 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 = 128
|
||
|
|
PMEM = [0 for i in range(0, MEMLIMIT)] # program memory
|
||
|
|
DMEM = [0.0 for i in range(0, MEMLIMIT)] # data memory
|
||
|
|
|
||
|
|
# port I/O function
|
||
|
|
def portio(port:int, data:float):
|
||
|
|
if port == 0: print(data) # standard numeric output
|
||
|
|
elif port == 1: # standard numeric input
|
||
|
|
try: data = float(input())
|
||
|
|
except ValueError: data = 0
|
||
|
|
elif port == 2: # character output
|
||
|
|
sys.stdout.write(chr(int(data)&255))
|
||
|
|
sys.stdout.flush()
|
||
|
|
elif port == 3: # character input
|
||
|
|
ch = '\0'
|
||
|
|
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)
|
||
|
|
data = float(ord(ch))
|
||
|
|
return data
|
||
|
|
|
||
|
|
# main instruction execution logic
|
||
|
|
def iexec(lno:int):
|
||
|
|
global PMEM, DMEM
|
||
|
|
data_override = 0
|
||
|
|
while True: # decode and execute the current instruction
|
||
|
|
cmd = (PMEM[lno] >> 21) & 7 # command opcode
|
||
|
|
p1 = (int(v1) if data_override else (PMEM[lno] >> 14)) & 127 # parameter 1
|
||
|
|
p2 = (int(v2) if data_override else (PMEM[lno] >> 7)) & 127 # parameter 2
|
||
|
|
p3 = (int(v3) if data_override else PMEM[lno]) & 127 # parameter 3
|
||
|
|
DMEM[0] = data_override = 0 # enforce the 0 at the location 0
|
||
|
|
DMEM[127] = 1 # enforce the 1 at the location 127
|
||
|
|
DMEM[126] = -1 # enforce the -1 at the location 126
|
||
|
|
v1 = DMEM[p1]; v2 = DMEM[p2]; v3 = DMEM[p3] # prefetch the values
|
||
|
|
if cmd == 1: # JMP
|
||
|
|
if p1 == 14: DMEM[125] = lno + 1
|
||
|
|
if (v2 == 0 and p1 == 0) or (v2 > 0 and p1 == 1) or (v2 < 0 and p1 == 2) \
|
||
|
|
or (v2 >= 0 and p1 == 3) or (v2 <= 0 and p1 == 4) or (v2 != 0 and p1 == 5) \
|
||
|
|
or p1 == 6 or p1 == 14: lno = p3 - 1
|
||
|
|
elif (v2 == 0 and p1 == 7) or (v2 > 0 and p1 == 8) or (v2 < 0 and p1 == 9) \
|
||
|
|
or (v2 >= 0 and p1 == 10) or (v2 <= 0 and p1 == 11) or (v2 != 0 and p1 == 12) \
|
||
|
|
or p1 == 13: lno = int(v3) - 1
|
||
|
|
elif cmd == 2: data_override = 1 # IAT
|
||
|
|
elif cmd == 3: # INO
|
||
|
|
for i in range(p2,p3+1): DMEM[i] = portio(p1, DMEM[i])
|
||
|
|
elif cmd == 4: # CPY
|
||
|
|
if p1 == 0: DMEM[p3] = p2
|
||
|
|
elif p1 == 1: DMEM[p3] = v2
|
||
|
|
elif p1 == 2: DMEM[int(v3)] = p2
|
||
|
|
elif p1 == 3: DMEM[int(v3)] = v2
|
||
|
|
elif p1 == 4: DMEM[int(v3)] = DMEM[int(v2)]
|
||
|
|
elif cmd == 5: DMEM[p3] = p1 * 100 + p2 + v3 / 100.0 # SET
|
||
|
|
elif cmd == 6: # MAT
|
||
|
|
if p1 == 0: DMEM[p3] = v2 + v3
|
||
|
|
elif p1 == 1: DMEM[p3] = v2 - v3
|
||
|
|
elif p1 == 2: DMEM[p3] = v2 * v3
|
||
|
|
elif p1 == 3: DMEM[p3] = 0 if v3 == 0 else (v2 / v3)
|
||
|
|
elif p1 == 4: DMEM[p3] = math.floor(v2) if v3 == 0 else (v2 % v3)
|
||
|
|
elif p1 == 5: DMEM[p3] = math.fabs(v2)
|
||
|
|
elif p1 == 6: DMEM[p3] = math.sqrt(math.fabs(v2))
|
||
|
|
elif p1 == 7: DMEM[p3] = math.exp(v2)
|
||
|
|
elif p1 == 8: DMEM[p3] = 0 if v2 == 0 else math.log(math.fabs(v2))
|
||
|
|
elif p1 == 9: DMEM[p3] = math.sin(v2)
|
||
|
|
elif p1 == 10: DMEM[p3] = math.cos(v2)
|
||
|
|
elif p1 == 11: DMEM[p3] = math.atan(v2)
|
||
|
|
elif cmd == 7: DMEM[p3] = float(random.randint(int(v1), int(v2))) # RND
|
||
|
|
lno += 1 # increment the program counter
|
||
|
|
if lno >= MEMLIMIT: break
|
||
|
|
|
||
|
|
# interactive mode entry function
|
||
|
|
def intermode(cmd:int, p1:int, p2:int):
|
||
|
|
if cmd == 0 and p1 < MEMLIMIT: iexec(p1) # run from the step
|
||
|
|
elif cmd == 1 and p1 < MEMLIMIT: PMEM[p1] = p2 # enter the instruction into PMEM
|
||
|
|
elif cmd == 2 and p1 < MEMLIMIT and p2 < MEMLIMIT: # clear instructions
|
||
|
|
for cmd in range(p1, p2+1): PMEM[cmd] = 0
|
||
|
|
elif cmd == 3 and p1 < MEMLIMIT and p2 < MEMLIMIT: # clear data
|
||
|
|
for cmd in range(p1, p2+1): DMEM[cmd] = 0.0
|
||
|
|
elif cmd == 4:
|
||
|
|
print("Bye!")
|
||
|
|
sys.exit(0)
|
||
|
|
|
||
|
|
# main REPL environment
|
||
|
|
if __name__ == '__main__':
|
||
|
|
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
|
||
|
|
iindex = 0
|
||
|
|
for instrpart in vreg.split(fc):
|
||
|
|
if len(instrpart) > 0:
|
||
|
|
intermode(1, iindex, int(instrpart))
|
||
|
|
instr.append(int(instrpart))
|
||
|
|
iindex += 1
|
||
|
|
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) == 3: # full instruction registered
|
||
|
|
intermode(int(instr[0]), int(instr[1]), int(instr[2]))
|
||
|
|
instr = []
|
||
|
|
|