2025-04-22 20:02:27 +03:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
# a simple assembler/disassembler for the mu808 VM
|
|
|
|
|
# supports both text and binary program formats
|
2025-04-27 16:53:48 +03:00
|
|
|
# usage: mu808asm [at|ab|dt|db|t2b|b2t|t74] file outfile
|
2025-04-22 20:02:27 +03:00
|
|
|
# modes:
|
|
|
|
|
# at: assemble to plaintext (MU8) machine code
|
|
|
|
|
# ab: assemble to binary (MU8B) machine code
|
|
|
|
|
# dt: disassemble plaintext (MU8) machine code
|
|
|
|
|
# db: disassemble binary (MU8B) machine code
|
|
|
|
|
# t2b: convert from plaintext MU8 to binary MU8B
|
|
|
|
|
# b2t: convert from binary MU8B to plaintext MU8
|
2025-04-27 16:53:48 +03:00
|
|
|
# t74: convert from plaintext MU8 to the TI-74 representation
|
2025-04-22 20:02:27 +03:00
|
|
|
|
|
|
|
|
import sys, re, struct
|
|
|
|
|
|
|
|
|
|
# mu808 mnemonics for assembly and disasssembly
|
|
|
|
|
mnemos = ['nop', 'jmp', 'iat', 'out', 'inp', 'set', 'cpy', 'fma',
|
|
|
|
|
'sub', 'div', 'mdf', 'abs', 'sqr', 'nel', 'tri', 'rnd']
|
|
|
|
|
|
|
|
|
|
# converts the MU8A source code to the plaintext machine code representation
|
|
|
|
|
def assemble(source):
|
|
|
|
|
labels = {}
|
|
|
|
|
out = ''
|
|
|
|
|
lno = 0
|
|
|
|
|
vreg = re.compile(r'\s+')
|
|
|
|
|
for line in source.split('\n'):
|
|
|
|
|
fields = []
|
|
|
|
|
line = line.split(';')[0].strip()
|
|
|
|
|
if len(line) > 0: # actual line to be counted on
|
|
|
|
|
lno += 1 # populate the line number
|
|
|
|
|
fields = vreg.split(line)[:5] # split it into fields
|
|
|
|
|
if fields[0].startswith(':'): # this is a label
|
|
|
|
|
labels[fields[0]] = lno # remember the label
|
|
|
|
|
fields = fields[1:] # remove the label from fields
|
|
|
|
|
try:
|
|
|
|
|
cmd = mnemos.index(fields[0].lower())
|
|
|
|
|
except ValueError:
|
|
|
|
|
try:
|
|
|
|
|
cmd = int(cmd)
|
|
|
|
|
except:
|
|
|
|
|
print('Assembly error - unknown mnemonic at line', lno)
|
|
|
|
|
sys.exit(1)
|
|
|
|
|
try:
|
|
|
|
|
out += ' '.join([str(lno), str(cmd), fields[1], fields[2], fields[3]]) + '\n'
|
|
|
|
|
except IndexError:
|
|
|
|
|
print('Assembly error - not enough operands at line', lno)
|
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
|
|
for lbl, lineno in labels.items():
|
|
|
|
|
out = out.replace(lbl, str(lineno))
|
|
|
|
|
return out
|
|
|
|
|
|
|
|
|
|
# converts the plaintext machine code representation to MU8A source code
|
|
|
|
|
def disassemble(machcode):
|
|
|
|
|
vreg = re.compile(r'\s+')
|
|
|
|
|
iindex = 0
|
|
|
|
|
instr = []
|
|
|
|
|
instrs = []
|
|
|
|
|
for instrpart in vreg.split(machcode):
|
|
|
|
|
if iindex == 5:
|
|
|
|
|
instrs.append(instr)
|
|
|
|
|
instr = []
|
|
|
|
|
iindex = 0
|
|
|
|
|
if len(instrpart) > 0:
|
|
|
|
|
instr.append(int(instrpart))
|
|
|
|
|
iindex += 1
|
|
|
|
|
# now, instrs contains 5-number groups
|
|
|
|
|
assembly = [None for i in range(0, 16384)]
|
|
|
|
|
labels = {}
|
|
|
|
|
for instr in instrs: # iterate over each instruction
|
|
|
|
|
lno, cmd, x, y, z = instr
|
|
|
|
|
try:
|
|
|
|
|
mnemo = mnemos[cmd]
|
|
|
|
|
except IndexError:
|
|
|
|
|
print('Disassembly error - unknown opcode at line', lno)
|
|
|
|
|
sys.exit(1)
|
|
|
|
|
if cmd == 1: # save the label for jump instructions
|
|
|
|
|
labels[z] = ':lbl_' + str(z)
|
|
|
|
|
assembly[lno] = [mnemo, str(x), str(y), labels[z]]
|
|
|
|
|
else:
|
|
|
|
|
assembly[lno] = [mnemo, str(x), str(y), str(z)]
|
|
|
|
|
out = ''
|
|
|
|
|
lno = 0
|
|
|
|
|
for asline in assembly:
|
|
|
|
|
if lno in labels:
|
|
|
|
|
out += labels[lno] + ' '
|
|
|
|
|
if asline is not None:
|
|
|
|
|
out += ' '.join(asline) + '\n'
|
|
|
|
|
lno += 1
|
|
|
|
|
return out
|
|
|
|
|
|
|
|
|
|
# converts the plaintext machine code representation to binary representation
|
|
|
|
|
def tobinary(txtrep):
|
|
|
|
|
instrs = [] # instruction list to store here
|
|
|
|
|
vreg = re.compile(r'\s+')
|
|
|
|
|
iindex = 0
|
|
|
|
|
instr = []
|
|
|
|
|
for instrpart in vreg.split(txtrep):
|
|
|
|
|
if iindex == 5:
|
|
|
|
|
instrs.append(instr)
|
|
|
|
|
instr = []
|
|
|
|
|
iindex = 0
|
|
|
|
|
if len(instrpart) > 0:
|
|
|
|
|
instr.append(int(instrpart))
|
|
|
|
|
iindex += 1
|
|
|
|
|
# now, instrs contains 5-number groups
|
|
|
|
|
out = b''
|
|
|
|
|
# the format is: cmd (8 bits), lno (14 bits), arg1 to arg3 (14 bits each)
|
|
|
|
|
for instr in instrs: # iterate over each instruction
|
|
|
|
|
b1 = instr[1] & 15
|
|
|
|
|
b2 = (instr[0] >> 6) & 255
|
|
|
|
|
b3 = ((instr[0] << 2) | (instr[2] >> 12)) & 255
|
|
|
|
|
b4 = (instr[2] >> 4) & 255
|
|
|
|
|
b5 = ((instr[2] << 4) | (instr[3] >> 10)) & 255
|
|
|
|
|
b6 = (instr[3] >> 2) & 255
|
|
|
|
|
b7 = ((instr[3] << 6) | (instr[4] >> 8)) & 255
|
|
|
|
|
b8 = instr[4] & 255
|
|
|
|
|
out += struct.pack('BBBBBBBB', b1, b2, b3, b4, b5, b6, b7, b8)
|
|
|
|
|
return out
|
|
|
|
|
|
|
|
|
|
# converts the binary machine code representation to plaintext representation
|
|
|
|
|
def totext(binrep):
|
|
|
|
|
out = ''
|
|
|
|
|
while len(binrep) > 0:
|
|
|
|
|
chunk = binrep[0:8]
|
|
|
|
|
binrep = binrep[8:]
|
|
|
|
|
b1, b2, b3, b4, b5, b6, b7, b8 = struct.unpack('BBBBBBBB', chunk)
|
|
|
|
|
cmd = b1 & 15
|
|
|
|
|
lno = ((b2 << 6) | (b3 >> 2)) & 16383
|
|
|
|
|
x = ((b3 << 12) | (b4 << 4) | (b5 >> 4)) & 16383
|
|
|
|
|
y = ((b5 << 10) | (b6 << 2) | (b7 >> 6)) & 16383
|
|
|
|
|
z = ((b7 << 8) | b8) & 16383
|
|
|
|
|
out += ' '.join([str(lno), str(cmd), str(x), str(y), str(z)]) + '\n'
|
|
|
|
|
return out
|
|
|
|
|
|
2025-04-27 16:53:48 +03:00
|
|
|
# converts the MU8 plaintext machine code to the TI-74 DATA statements (MU74)
|
|
|
|
|
def ti74data(txtrep:str, baseaddr:int=5000):
|
|
|
|
|
instrs = [] # instruction list to store here
|
|
|
|
|
vreg = re.compile(r'\s+')
|
|
|
|
|
iindex = 0
|
|
|
|
|
instr = []
|
|
|
|
|
for instrpart in vreg.split(txtrep):
|
|
|
|
|
if iindex == 5:
|
|
|
|
|
instrs.append(instr)
|
|
|
|
|
instr = []
|
|
|
|
|
iindex = 0
|
|
|
|
|
if len(instrpart) > 0:
|
|
|
|
|
instr.append(int(instrpart))
|
|
|
|
|
iindex += 1
|
|
|
|
|
# now, instrs contains 5-number groups
|
|
|
|
|
out = f'{baseaddr} DATA 0,0\n'
|
|
|
|
|
maxlno = 0
|
|
|
|
|
for instr in instrs:
|
|
|
|
|
lno, cmd, x, y, z = instr
|
|
|
|
|
if lno > maxlno:
|
|
|
|
|
maxlno = lno
|
|
|
|
|
a = cmd*10000 + x
|
|
|
|
|
b = y*10000 + z
|
|
|
|
|
out += f'{baseaddr + lno} DATA {a},{b}\n'
|
|
|
|
|
out += f'{baseaddr + maxlno + 1} DATA -1,0\n'
|
|
|
|
|
return out
|
|
|
|
|
|
2025-04-22 20:02:27 +03:00
|
|
|
def main():
|
|
|
|
|
if len(sys.argv) < 4:
|
2025-04-27 16:54:32 +03:00
|
|
|
print("Usage: mu808asm [at|ab|dt|db|t2b|b2t|t74] file outfile")
|
2025-04-22 20:02:27 +03:00
|
|
|
return
|
|
|
|
|
mode = sys.argv[1]
|
|
|
|
|
srcfile = sys.argv[2]
|
|
|
|
|
targetfile = sys.argv[3]
|
|
|
|
|
srctext = ''
|
|
|
|
|
inpmode = 'r'
|
|
|
|
|
if mode == 'b2t' or mode == 'db':
|
|
|
|
|
inpmode = 'rb'
|
|
|
|
|
try:
|
|
|
|
|
fd = open(srcfile, inpmode)
|
|
|
|
|
srctext = fd.read()
|
|
|
|
|
close(fd)
|
|
|
|
|
except:
|
|
|
|
|
pass
|
|
|
|
|
if len(srctext) > 0:
|
|
|
|
|
outmode = 'w'
|
|
|
|
|
output = ''
|
|
|
|
|
if mode == 'b2t': # binary-to-text
|
|
|
|
|
output = totext(srctext)
|
|
|
|
|
elif mode == 't2b': # text-to-binary
|
|
|
|
|
output = tobinary(srctext)
|
|
|
|
|
outmode = 'wb'
|
|
|
|
|
elif mode == 'dt': # disassemble text
|
|
|
|
|
output = disassemble(srctext)
|
|
|
|
|
elif mode == 'db': # disassemble binary
|
|
|
|
|
srctext = totext(srctext)
|
|
|
|
|
output = disassemble(srctext)
|
2025-04-27 16:53:48 +03:00
|
|
|
elif mode == 't74': # convert to TI-74 DATA statements
|
|
|
|
|
output = ti74data(srctext)
|
2025-04-22 20:02:27 +03:00
|
|
|
else: # assemble
|
|
|
|
|
output = assemble(srctext) # default mode
|
|
|
|
|
if mode == 'ab':
|
|
|
|
|
output = tobinary(output)
|
|
|
|
|
outmode = 'wb'
|
|
|
|
|
# write the output file
|
|
|
|
|
try:
|
|
|
|
|
fd = open(targetfile, outmode)
|
|
|
|
|
fd.write(output)
|
|
|
|
|
close(fd)
|
|
|
|
|
except:
|
|
|
|
|
pass
|
|
|
|
|
else:
|
|
|
|
|
print('Nothing to process!')
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
|
main()
|