#!/usr/bin/env python import sys ''' Implements the MINIAC assembly language. Here is an example program: ret EQ 0 # Address where result is placed. arg EQ 1 # Address where number to be doubled is placed. LI 15 ST arg LM arg # Load input. BN 5 # If top bit is set, go to (b) below. RL 1 # If top bit is not set, rotate input left once, ST ret # store into solution, LI 0 # and stop. BZ 0 neg RL 1 # (b): If top bit is set, rotate input left once, ST ret # subtract one from the result to remove the LI -1 # top bit rotated into the 1's bit, AD ret ST ret # store result into solution, LI 0 # and stop. BZ 0 To simulate execution of the MINIAC assembly code found in a file named filename.s, use the following command: python miniac.py -v filename.s To output the hexadecimal assembly code in a format suitable for loading into Logisim, use the following command: python miniac.py -a filename.s ''' def main(): filename = None print_usage = False verbose = False output_assembly = False for arg in sys.argv[1:]: if arg.startswith('-'): if arg == '-v': verbose = True elif arg == '-a': output_assembly = True else: print_usage = True elif filename is None: filename = arg else: print_usage = True if print_usage: sys.stderr.write('usage: miniac [-v] [-a] filename\n') if filename is None: errors, mem, mem_lines = assemble(sys.stdin) else: with open(filename) as file: errors, mem, mem_lines = assemble(file) if len(errors) > 0: for line_num, message in errors: sys.stderr.write(str(line_num) + ': ' + message + '\n') elif output_assembly: print('v2.0 raw') for x0 in range(0, len(mem), 8): x1 = min(x0 + 8, len(mem)) print(' '.join(format(byte, '02x') for byte in mem[x0:x1])) else: execute(mem, mem_lines, verbose) def assemble(file): errors = [] lines = [] labels = {} eqs = {} opcodes = 'BZ BN ST RL LI LM AD SB'.split() line_num = 0 for line in file: line_num += 1 sharp = line.find('#') if sharp >= 0: line = line[:sharp] tokens = line.split() if len(line) > 0 and not line[0].isspace(): if len(tokens) == 3 and tokens[1].upper() == 'EQ': try: int_val = int(tokens[2]) if 0 <= int_val < 32: eqs[tokens[0]] = int_val elif tokens[0] in eqs or tokens[0] in labels: errors.append((line_num, tokens[0] + ' previously defined')) else: errors.append((line_num, 'must have integer between 0 and 31')) except ValueError: errors.append((line_num, 'must have valid integer at end')) tokens = [] else: label = tokens.pop(0) if label in labels or label in eqs: errors.append((line_num, label + ' previously defined')) else: labels[label] = len(lines) if len(tokens) > 0: op = tokens[0].upper() if len(tokens) != 2: errors.append((line_num, 'must be of form "OP ARG"')) else: try: opcode = opcodes.index(op) lines.append((line_num, opcode, tokens[1])) except ValueError: errors.append((line_num, 'unknown operation "' + op + '"')) mem = [0 for line in lines] mem_lines = [0 for line in lines] for i in range(len(mem)): line_num, opcode, arg = lines[i] if arg in labels: val = labels[arg] - i if val < -16 or val >= 16: errors.append((line_num, 'label "' + arg + '" too far from line')) val = 0 elif arg in eqs: val = eqs[arg] else: try: val = int(arg) if val < -16 or val >= 32: errors.append((line_num, 'argument not in allowed range')) val = 0 except ValueError: errors.append((line_num, 'argument not a recognized value')) val = 0 mem[i] = (opcode << 5) | (val & 0x1F) mem_lines[i] = line_num return (errors, mem, mem_lines) def execute(instr_mem, instr_mem_lines, verbose): regs = [0, 0] # pc, ac data_mem = [0] * 32 def extend(arg, bit_length): sign = (arg >> (bit_length - 1)) & 0x1 return (-sign << bit_length) | (arg & ~(-1 << bit_length)) def do_bz(arg): if regs[1] == 0: regs[0] += extend(arg, 5) - 1 def do_bn(arg): if regs[1] < 0: regs[0] += extend(arg, 5) - 1 def do_st(arg): data_mem[arg] = regs[1] def do_rl(arg): result = (regs[1] << arg) | ((regs[1] & 0xFF) >> (8 - arg)) regs[1] = extend(result, 8) def do_li(arg): regs[1] = extend(arg, 5) def do_lm(arg): regs[1] = data_mem[arg] def do_ad(arg): regs[1] += data_mem[arg] def do_sb(arg): regs[1] -= data_mem[arg] ops = (do_bz, do_bn, do_st, do_rl, do_li, do_lm, do_ad, do_sb) opcodes = 'BZ BN ST RL LI LM AD SB'.split() old_pc = -1 while regs[0] != old_pc: old_pc = regs[0] if old_pc < 0 or old_pc >= len(instr_mem): print('out of memory bounds: pc = ' + str(old_pc)) return instr = instr_mem[old_pc] regs[0] = old_pc + 1 opcode = instr >> 5 arg = instr & 0x1F ops[opcode](arg) if verbose: print('line' + format(instr_mem_lines[old_pc], '3d') + ', old_pc=' + format(old_pc, '3d') + ': instr=0x' + format(instr, '02x') + ' (' + opcodes[opcode] + ' ' + format(arg, '2d') + '), ac=0x' + format(regs[1] & 0xFF, '02x') + ' (' + format(extend(regs[1], 8), '4d') + ')' + ', new_pc=' + format(regs[0], '3d') + ', M[' + format(arg, '2d') + ']=0x' + format(data_mem[arg] & 0xFF, '02x')) main()