#!/usr/bin/python3 # # Assembler for Western Digital CP16xx Microcode # Copyright (c) 2020 Viacheslav Ovsiienko <1801BM1@gmail.com> # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License version 3 # as published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # # Mnemonics table is borrowed from: # Disassembler for Western Digital CP16xx/WD21xx Microcode # Copyright 2015 Eric Smith # import os import re import sys import time import argparse MIN_LC = 0x0000 MAX_LC = 0x0800 T_SYM = 0 T_REG = 1 T_TRA = 2 class Wd16(object): ''' Assembler for Western Digital CP16xx Microcode ''' # # Compiled regular expressions # RE_OPCODE = re.compile(r'(?:[\.a-zA-Z][a-zA-Z0-9]*\s*)') RE_SYMBOL = re.compile(r'(?:[\.a-zA-Z$_][a-zA-Z0-9$_\.]*\s*=\s*)') RE_LABEL = re.compile(r'(?:[a-zA-Z0-9$_\.]+\s*:\s*)') RE_LOCAL = re.compile(r'(?:\d+\$)') # # Operations to calculate expressions # EXP_OPERS = { 'U+': (9, lambda x: x), # unary plus 'U-': (9, lambda x: -x), # unary minus 'U~': (8, lambda x: ~x), # unary one's complement '*': (6, lambda x, y: x * y), # binary mul '/': (6, lambda x, y: x // y), # binary div '+': (5, lambda x, y: x + y), # binary add '-': (5, lambda x, y: x - y), # binary sub '&': (4, lambda x, y: x & y), # binary and '^': (3, lambda x, y: x ^ y), # binary xor '|': (2, lambda x, y: x | y) # binary or } EXP_UNA = '+-~' EXP_OPS = '+-~*/+-&^|' EXP_ALL = '+-~*/+-&^|()' # # CP-1621 PLA locations and translation codes (LSI-11) # PLA_LSI11 = { 0x037: 0x25, 0x040: 0x13, 0x041: 0x51, 0x048: 0x32, 0x049: 0x13, 0x04A: 0x51, 0x050: 0x16, 0x051: 0x13, 0x052: 0x64, 0x053: 0x51, 0x05A: 0x32, 0x05B: 0x13, 0x05C: 0x4C, 0x05F: 0x51, 0x061: 0x23, 0x063: 0x13, 0x06C: 0x32, 0x06D: 0x13, 0x073: 0x32, 0x074: 0x13, 0x07D: 0x32, 0x07E: 0x13, 0x07F: 0x52, 0x080: 0x68, 0x08A: 0x38, 0x092: 0x38, 0x09C: 0x38, 0x0A4: 0x38, 0x0AE: 0x38, 0x0B5: 0x38, 0x0BF: 0x38, 0x0C8: 0x15, 0x0D1: 0x15, 0x0D9: 0x15, 0x0E2: 0x15, 0x0EB: 0x15, 0x0F2: 0x15, 0x0FC: 0x15, 0x100: 0x70, 0x10A: 0x58, 0x10D: 0x2A, 0x111: 0x2C, 0x112: 0x58, 0x114: 0x58, 0x11C: 0x58, 0x121: 0x49, 0x123: 0x58, 0x126: 0x58, 0x12E: 0x58, 0x135: 0x58, 0x13F: 0x58, 0x140: 0x0B, 0x150: 0x4A, 0x154: 0x4A, 0x158: 0x4A, 0x15C: 0x4A, 0x160: 0x07, 0x162: 0x26, 0x170: 0x4A, 0x174: 0x4A, 0x178: 0x4A, 0x148: 0x4A, 0x14C: 0x4A, 0x168: 0x4A, 0x16C: 0x4A, 0x180: 0x0E, 0x1A0: 0x0D, 0x188: 0x4A, 0x18C: 0x4A, 0x1A8: 0x4A, 0x1AC: 0x4A, 0x1E8: 0x4A, 0x1EC: 0x4A, 0x200: 0x4A, 0x228: 0x4A, 0x22C: 0x4A, 0x2A4: 0x54, 0x329: 0x1A, 0x342: 0x1C, 0x3B3: 0x1C, 0x3C7: 0x1C, 0x3D1: 0x1C, 0x3E8: 0x1C, 0x41B: 0x34, 0x43B: 0x34, 0x453: 0x19, 0x47A: 0x19, 0x4AC: 0x62, 0x4BC: 0x62, 0x490: 0x62, 0x4D0: 0x62, 0x506: 0x62, 0x527: 0x34, 0x54E: 0x19, 0x568: 0x29, 0x569: 0x29, 0x56A: 0x29, 0x56B: 0x29, 0x579: 0x13, 0x540: 0x34, 0x560: 0x34, 0x592: 0x19, 0x598: 0x19, 0x584: 0x34, 0x58C: 0x34, 0x5A4: 0x34, 0x5AC: 0x34, 0x5CC: 0x19, 0x5CF: 0x19, 0x5EC: 0x19, 0x5C0: 0x34, 0x5C8: 0x34, 0x5E0: 0x34, 0x5E8: 0x34 } TTL_LSI11 = { 0x022: 0x30, 0x025: 0x24, 0x029: 0x04, 0x10C: 0x24, 0x116: 0x38, 0x156: 0x34, 0x157: 0x34, 0x1C2: 0x3C, 0x309: 0x04, 0x322: 0x2C, 0x327: 0x28, 0x32C: 0x28, 0x33B: 0x25, 0x346: 0x38, 0x347: 0x3C } def __init__(self): self.ps = 0 # pass number self.lc = 0 # location counte self.radx = 8 # default radix self.lnum = 0 # line number self.fnam = None # source file name self.fend = 0 # .end directive found self.wcnt = 0 # warning counter self.ecnt = 0 # error counter self.elst = [] # line error list for listing self.efst = 0 # first error output self.bloc = {} # local labels block self.cloc = {} # current local block self.clin = {} # local block line number self.gval = None # generated value for listing self.data = [-1] * MAX_LC self.symb = { # initialize register names 'G': (0, T_REG), 'GL': (0, T_REG), 'GH': (1, T_REG), 'RBA': (2, T_REG), 'RBAL': (2, T_REG), 'RBAH': (3, T_REG), 'RSRC': (4, T_REG), 'RSRCL': (4, T_REG), 'RSRCH': (5, T_REG), 'RDST': (6, T_REG), 'RDSTL': (6, T_REG), 'RDSTH': (7, T_REG), 'RIR': (8, T_REG), 'RIRL': (8, T_REG), 'RIRH': (9, T_REG), 'RPSW': (10, T_REG), 'RPSWL': (10, T_REG), 'RPSWH': (11, T_REG), 'SP': (12, T_REG), 'SPL': (12, T_REG), 'SPH': (13, T_REG), 'PC': (14, T_REG), 'PCL': (14, T_REG), 'PCH': (15, T_REG), 'I4': (1, T_SYM), 'I5': (2, T_SYM), 'I6': (4, T_SYM), 'C': (1, T_SYM), 'V': (2, T_SYM), 'Z': (4, T_SYM), 'N': (8, T_SYM), 'T': (16, T_SYM), 'C8': (16, T_SYM), 'C4': (32, T_SYM), 'ZB': (64, T_SYM), 'NB': (128, T_SYM), 'UB': (0, T_SYM), 'LB': (1, T_SYM), 'UBC': (2, T_SYM), 'LBC': (3, T_SYM), 'RMW': (4, T_SYM), 'TG6': (1, T_SYM), 'TG8': (2, T_SYM), 'LRR': (1, T_SYM), 'RSVC': (2, T_SYM) } return # # Directive handlers should return negative value # to indicate local counter should not be updated # and no data writen into storage # def dir_title(self, opcode, word): # # Ignore .title for compatibility # return -1 def dir_radix(self, opcode, word): radx = self.eval_exp(opcode[1]) # # radix 16 is not supported to avoid digits and names # misinterpreting, i.e. DEAD0 might represent both # if radx not in (8, 10): raise SyntaxError("unsupported radix value '%d'" % radx) self.radx = radx return -1 def dir_tran(self, opcode, word): arg = opcode[1] tran = self.eval_exp(opcode[2]) qt = self.symb.get(arg) if qt is not None and (qt[0] != tran or qt[1] != T_TRA): raise SyntaxError("translation definition duplication '%s'" % arg) if self.ps == 1: self.symb[arg] = (tran, T_TRA) self.gval = tran return -1 def dir_reg(self, opcode, word): arg = opcode[1] reg = self.eval_exp(opcode[2]) if not 0 <= reg < 16: raise SyntaxError("register is out of range %d" % reg) qr = self.symb.get(arg) if qr is not None and (qr[0] != reg or qr[1] != T_REG): raise SyntaxError("register definition duplication '%s'" % arg) if self.ps == 1: self.symb[arg] = (reg, T_REG) self.gval = reg return -1 def dir_loc(self, opcode, word): loc = self.eval_exp(opcode[1]) # # Allow counter to get any value # Check will happen on actual usage # self.lc = loc self.gval = loc return -1 def dir_align(self, opcode, word): pw2 = self.eval_exp(opcode[1]) mask = (1 << pw2) - 1 if not MIN_LC <= mask < MAX_LC or pw2 >= 16: raise SyntaxError("aligment is out of range 0x%08X" % mask) self.lc += mask self.lc &= ~mask return -1 def dir_end(self, opcode, word): self.fend = 1 return -1 def get_reg(self, sym): reg = self.symb.get(sym) if reg is None: raise SyntaxError("undefined register name '%s'" % sym) if reg[1] != T_REG: raise SyntaxError("unregistered as register name '%s'" % sym) if not 0 <= reg[0] < 16: raise SyntaxError("register is out of range '%s'" % sym) return reg[0] # # Opcode handlers should return zero or positive value # to indicate local counter should be updated and returned # data should be writen into storage at self.lc # def op_jmp(self, opcode, word): addr = self.eval_exp(opcode[1]) if not MIN_LC <= addr < MAX_LC: raise SyntaxError("address is out of range 0x%08X" % addr) return word | addr def op_jsr(self, opcode, word): addr = self.eval_exp(opcode[1]) if not MIN_LC <= addr < MAX_LC: raise SyntaxError("address is out of range 0x%08X" % addr) return word | addr | 1 << 16 # # For conditional jump we should check the upper byte # of target address against current one, at the moment # of jump is taket the lc is alredy incremented # def op_jxx(self, opcode, word): addr = self.eval_exp(opcode[1]) if not MIN_LC <= addr < MAX_LC: raise SyntaxError("address is out of range 0x%08X" % addr) if ((self.lc + 1) ^ addr) & ~0xFF: raise SyntaxError("conditional jump is out of range 0x%08X" % addr) return word | (addr & 0xFF) def op_non(self, opcode, word): return word def op_lit(self, opcode, word): lit = self.eval_exp(opcode[1]) if not -128 <= lit < 0x100: raise SyntaxError("literal is out of range 0x%08X" % lit) reg = self.get_reg(opcode[2]) return word | reg | (lit & 0xFF) << 4 def op_xi(self, opcode, word): mask = self.eval_exp(opcode[1]) if not 0 <= mask < 8: raise SyntaxError("interrupt mask is out of range 0x%08X" % mask) return word | mask << 4 def op_lcf(self, opcode, word): mask = self.eval_exp(opcode[1]) if not 0 <= mask < 0x10: raise SyntaxError("invalid condition flag mask 0x%4X" % mask) reg = self.get_reg(opcode[2]) return word | reg | mask << 4 def op_ra(self, opcode, word): reg = self.get_reg(opcode[1]) return word | reg def op_rba(self, opcode, word): rb = self.get_reg(opcode[1]) ra = self.get_reg(opcode[2]) return word | ra | rb << 4 def op_in(self, opcode, word): if opcode[0] in ('ib', 'ibf', 'iw', 'iwf'): lim = 8 else: lim = 4 # # The mode operand is allowed to be empty, default is 0 # if opcode[1]: mode = self.eval_exp(opcode[1]) else: mode = 0 if not 0 <= mode < lim: raise SyntaxError("I/O mode is out of range 0x%08X" % mode) ra = self.get_reg(opcode[2]) return word | ra | mode << 4 dir_ops = { 'jmp': (0x0000, 1, op_jmp), # unconditional JuMP 'jsr': (0x0000, 1, op_jsr), # unconditional JuMP and save return 'rfs': (0x0800, 0, op_non), # Return From Subroutine 'jzbf': (0x1000, 1, op_jxx), # Jump if ZB flag is False 'jzbt': (0x1100, 1, op_jxx), # Jump if ZB flag is True 'jc8f': (0x1200, 1, op_jxx), # Jump if C8 flag is False 'jc8t': (0x1300, 1, op_jxx), # Jump if C8 flag is True 'jif': (0x1400, 1, op_jxx), # Jump if Indirect condition is False 'jit': (0x1500, 1, op_jxx), # Jump if Indirect condition is True 'jnbf': (0x1600, 1, op_jxx), # Jump if NB flag False 'jnbt': (0x1700, 1, op_jxx), # Jump if NB flag True 'jzf': (0x1800, 1, op_jxx), # Jump if Z flag False 'jzt': (0x1900, 1, op_jxx), # Jump if Z flag True 'jcf': (0x1a00, 1, op_jxx), # Jump if C flag False 'jct': (0x1b00, 1, op_jxx), # Jump if C flag True 'jvf': (0x1c00, 1, op_jxx), # Jump if V flag False 'jvt': (0x1d00, 1, op_jxx), # Jump if V flag True 'jnf': (0x1e00, 1, op_jxx), # Jump if N flag False 'jnt': (0x1f00, 1, op_jxx), # Jump if N flag True 'al': (0x2000, 2, op_lit), # Add Literal 'cl': (0x3000, 2, op_lit), # Compare Literal 'nl': (0x4000, 2, op_lit), # aNd Literal 'tl': (0x5000, 2, op_lit), # Test Literal 'll': (0x6000, 2, op_lit), # Load Literal 'ri': (0x7000, 1, op_xi), # Reset Interrupts 'si': (0x7100, 1, op_xi), # Set Interrupts 'ccf': (0x7200, 1, op_ra), # Copy Condition Flags 'lcf': (0x7300, 2, op_lcf), # Load Condition Flags 'rtsr': (0x7400, 0, op_non), # Reset Translation State Register 'lgl': (0x7500, 1, op_ra), # Load G Low 'cib': (0x7600, 1, op_ra), # Conditionally Increment Byte 'cdb': (0x7700, 1, op_ra), # Conditionally Decrement Byte 'mb': (0x8000, 2, op_rba), # Move Byte 'mbf': (0x8100, 2, op_rba), # Move Byte, Flags 'mw': (0x8200, 2, op_rba), # Move Word 'mwf': (0x8300, 2, op_rba), # Move Word, Flags 'cmb': (0x8400, 2, op_rba), # Conditionally Move Byte 'cmbf': (0x8500, 2, op_rba), # Conditionally Move Byte, Flags 'cmw': (0x8600, 2, op_rba), # Conditionally Move Word 'cmwf': (0x8700, 2, op_rba), # Conditionally Move Word, Flags 'slbc': (0x8800, 2, op_rba), # Shift Left Byte with Carry 'slbcf': (0x8900, 2, op_rba), # Shift Left Byte with Carry, Flags 'slwc': (0x8a00, 2, op_rba), # Shift Left Word with Carry 'slwcf': (0x8b00, 2, op_rba), # Shift Left Word with Carry, Flags 'slb': (0x8c00, 2, op_rba), # Shift Left Byte 'slbf': (0x8d00, 2, op_rba), # Shift Left Byte, Flags 'slw': (0x8e00, 2, op_rba), # Shift Left Word 'slwf': (0x8f00, 2, op_rba), # Shift Left Word, Flags 'icb1': (0x9000, 2, op_rba), # Increment Byte By 1 'icb1f': (0x9100, 2, op_rba), # Increment Byte By 1, Flags 'icw1': (0x9200, 2, op_rba), # Increment Word By 1 'icw1f': (0x9300, 2, op_rba), # Increment Word By 1, Flags 'icb2': (0x9400, 2, op_rba), # Increment Byte By 2 'icb2f': (0x9500, 2, op_rba), # Increment Byte By 2, Flags 'icw2': (0x9600, 2, op_rba), # Increment Word By 2 'icw2f': (0x9700, 2, op_rba), # Increment Word By 2, Flags 'tcb': (0x9800, 2, op_rba), # Twos Complement Byte 'tcbf': (0x9900, 2, op_rba), # Twos Complement Byte, Flags 'tcw': (0x9a00, 2, op_rba), # Twos Complement Word 'tcwf': (0x9b00, 2, op_rba), # Twos Complement Word, Flags 'ocb': (0x9c00, 2, op_rba), # Ones Complement Byte 'ocbf': (0x9d00, 2, op_rba), # Ones Complement Byte, Flags 'ocw': (0x9e00, 2, op_rba), # Ones Complement Word 'ocwf': (0x9f00, 2, op_rba), # Ones Complement Word, Flags 'ab': (0xa000, 2, op_rba), # Add Byte 'abf': (0xa100, 2, op_rba), # Add Byte, Flags 'aw': (0xa200, 2, op_rba), # Add Word 'awf': (0xa300, 2, op_rba), # Add Word, Flags 'cab': (0xa400, 2, op_rba), # Conditionally Add Byte 'cabf': (0xa500, 2, op_rba), # Conditionally Add Byte, Flags 'caw': (0xa600, 2, op_rba), # Conditionally Add Word 'cawf': (0xa700, 2, op_rba), # Conditionally Add Word, Flags 'abc': (0xa800, 2, op_rba), # Add Byte with Carry 'abcf': (0xa900, 2, op_rba), # Add Byte with Carry, Flags 'awc': (0xaa00, 2, op_rba), # Add Word with Carry 'awcf': (0xab00, 2, op_rba), # Add Word with Carry, Flags 'cad': (0xac00, 2, op_rba), # Conditionally Add Digits 'cawi': (0xae00, 2, op_rba), # Conditionally Add Word on Icc 'cawif': (0xaf00, 2, op_rba), # Conditionally Add Word on Icc, Flags 'sb': (0xb000, 2, op_rba), # Subtract Byte 'sbf': (0xb100, 2, op_rba), # Subtract Byte, Flags 'sw': (0xb200, 2, op_rba), # Subtract Word 'swf': (0xb300, 2, op_rba), # Subtract Word, Flags 'cb': (0xb400, 2, op_rba), # Compare Byte 'cbf': (0xb500, 2, op_rba), # Compare Byte, Flags 'cw': (0xb600, 2, op_rba), # Compare Word 'cwf': (0xb700, 2, op_rba), # Compare Word, Flags 'sbc': (0xb800, 2, op_rba), # Subtract Byte with Carry 'sbcf': (0xb900, 2, op_rba), # Subtract Byte with Carry, Flags 'swc': (0xba00, 2, op_rba), # Subtract Word with Carry 'swcf': (0xbb00, 2, op_rba), # Subtract Word with Carry, Flags 'db1': (0xbc00, 2, op_rba), # Decrement Byte by 1 'db1f': (0xbd00, 2, op_rba), # Decrement Byte by 1, Flags 'dw1': (0xbe00, 2, op_rba), # Decrement Word by 1 'dw1f': (0xbf00, 2, op_rba), # Decrement Word by 1, Flags 'nb': (0xc000, 2, op_rba), # aNd Byte 'nbf': (0xc100, 2, op_rba), # aNd Byte, Flags 'nw': (0xc200, 2, op_rba), # aNd Word 'nwf': (0xc300, 2, op_rba), # aNd Word, Flags 'tb': (0xc400, 2, op_rba), # Test Byte 'tbf': (0xc500, 2, op_rba), # Test Byte, Flags 'tw': (0xc600, 2, op_rba), # Test Word 'twf': (0xc700, 2, op_rba), # Test Word, Flags 'orb': (0xc800, 2, op_rba), # OR Byte 'orbf': (0xc900, 2, op_rba), # OR Byte, Flags 'orw': (0xca00, 2, op_rba), # OR Word 'orwf': (0xcb00, 2, op_rba), # OR Word, Flags 'xb': (0xcc00, 2, op_rba), # eXclusive or Byte 'xbf': (0xcd00, 2, op_rba), # eXclusive or Byte, Flags 'xw': (0xce00, 2, op_rba), # eXclusive or Word 'xwf': (0xcf00, 2, op_rba), # eXclusive or Word, Flags 'ncb': (0xd000, 2, op_rba), # aNd Complement Byte 'ncbf': (0xd100, 2, op_rba), # aNd Complement Byte, Flags 'ncw': (0xd200, 2, op_rba), # aNd Complement Word 'ncwf': (0xd300, 2, op_rba), # aNd Complement Word, Flags 'srbc': (0xd800, 2, op_rba), # Shift Right Byte with Carry 'srbcf': (0xd900, 2, op_rba), # Shift Right Byte with Carry, Flags 'srwc': (0xda00, 2, op_rba), # Shift Right Word with Carry 'srwcf': (0xdb00, 2, op_rba), # Shift Right Word with Carry, Flags 'srb': (0xdc00, 2, op_rba), # Shift Right Byte 'srbf': (0xdd00, 2, op_rba), # Shift Right Byte, Flags 'srw': (0xde00, 2, op_rba), # Shift Right Word 'srwf': (0xdf00, 2, op_rba), # Shift Right Word, Flags 'ib': (0xe000, 2, op_in), # Input Byte 'ibf': (0xe100, 2, op_in), # Input Byte, Flags 'iw': (0xe200, 2, op_in), # Input Word 'iwf': (0xe300, 2, op_in), # Input Word, Flags 'isb': (0xe400, 2, op_in), # Input Status Byte 'isbf': (0xe500, 2, op_in), # Input Status Byte, Flags 'isw': (0xe600, 2, op_in), # Input Status Word 'iswf': (0xe700, 2, op_in), # Input Status Word, Flags 'mi': (0xec00, 2, op_rba), # Modify microInstruction 'ltr': (0xee00, 2, op_rba), # Load Translation Register 'rib1': (0xf000, 2, op_rba), # Read and Increment Byte by 1 'wib1': (0xf100, 2, op_rba), # Write and Increment Byte by 1 'riw1': (0xf200, 2, op_rba), # Read and Increment Word by 1 'wiw1': (0xf300, 2, op_rba), # Write and Increment Word by 1 'rib2': (0xf400, 2, op_rba), # Read and Increment Byte by 2 'wib2': (0xf500, 2, op_rba), # Write and Increment Byte by 2 'riw2': (0xf600, 2, op_rba), # Read and Increment Word by 2 'wiw2': (0xf700, 2, op_rba), # Write and Increment Word by 2 'r': (0xf800, 2, op_rba), # Read 'w': (0xf900, 2, op_rba), # Write 'ra': (0xfa00, 2, op_rba), # Read Acknowledge 'wa': (0xfb00, 2, op_rba), # Write Acknowledge 'ob': (0xfc00, 2, op_rba), # Output Byte 'ow': (0xfd00, 2, op_rba), # Output Word 'os': (0xfe00, 2, op_rba), # Output Status 'nop': (0xff00, 0, op_non), # No OPeration '.title': (-1, 1, dir_title), # .title "ignored title" '.radix': (-1, 1, dir_radix), # .radix 8./10. '.align': (-1, 1, dir_align), # .align 2 '.tran': (-1, 2, dir_tran), # .tran DC1, 0x2A '.reg': (-1, 2, dir_reg), # .reg '.loc': (-1, 1, dir_loc), # .loc '.org': (-1, 1, dir_loc), # .org '.end': (-1, 0, dir_end), # .end } # # Calculates the expression in Polish Reverse Notation # def calc_exp(self, polish): stack = [] for token in polish: if token in Wd16.EXP_OPERS: if token[0] == 'U': x = stack.pop() stack.append(Wd16.EXP_OPERS[token][1](x)) else: y, x = stack.pop(), stack.pop() stack.append(Wd16.EXP_OPERS[token][1](x, y)) else: stack.append(token) if not stack: raise SyntaxError("invalid integer expression (empty)") return stack[0] # # Converts the infix expression to Polish Reverse Notation # def polish_exp(self, string): stack = [] for token in string: if token in Wd16.EXP_OPERS: while (stack and stack[-1] != "(" and Wd16.EXP_OPERS[token][0] <= Wd16.EXP_OPERS[stack[-1]][0]): yield stack.pop() stack.append(token) elif token == ")": while stack: x = stack.pop() if x == "(": break yield x elif token == "(": stack.append(token) else: yield token while stack: yield stack.pop() # # Token generator wrapper, replaces [+-~] with unary tags # def subst_unary(self, string): prev = True for token in string: if type(token) is not str: prev = False yield token continue if token in Wd16.EXP_UNA and prev: yield 'U' + token continue prev = token in Wd16.EXP_OPS or token == '(' yield token # # Token generator helper, replaces the numbers with actual integers # def subst_number(self, token): try: lt = len(token) if token[0] == '0': if lt > 1: if token[1] in 'xX': return int(token[2:], base=16) if token[1] in 'bB': return int(token[2:], base=2) if token[-1] == '.': return int(token[:-1], base=10) return int(token, base=8) if token[-1] == '.': return int(token[:-1], base=10) return int(token, self.radx) except ValueError: raise SyntaxError("invalid integer expression '%s'" % token) # # Token generator helper, replaces the local labels nnnn$ # def subst_local(self, token): if not token[:-1].isdigit(): raise SyntaxError("invalid local identifier '%s'" % token) label = self.cloc.get(token) if label is None: raise SyntaxError("undefined local identifier '%s'" % token) return label # # Token generator wrapper, replaces the symbols with actual integers # def subst_symbol(self, string): for token in string: if token in Wd16.EXP_ALL: yield token elif token[0] in '0123456789': if token[-1] == '$': yield self.subst_local(token) else: yield self.subst_number(token) elif token[0] == "'" and token[-1] == "'" and len(token) == 3: yield ord(token[1]) elif token == '.': yield self.lc else: value = self.symb.get(token) if value is None: raise SyntaxError("undefined identifier '%s'" % token) yield value[0] # # Source token generator parses the original line # def parse_exp(self, string): quota = False start = -1 pos = -1 for s in string: pos += 1 if start < 0: if s in ' \t': continue start = pos if s == "'": quota = not quota continue if quota: continue if s == ';': if start >= 0 and start != pos: yield string[start:pos] start = -1 break if s.isalnum() or s in '"$_.': continue if start >= 0 and start != pos: yield string[start:pos] start = -1 if s in Wd16.EXP_ALL: yield s start = -1 continue if s not in ' \t': raise SyntaxError("invalid expression '%s'" % s) if start >= 0: yield string[start:] # # Evaluate arithmetics expression, returns integer if succeeded, # otherwise throws SyntaxError with error message generated # def eval_exp(self, exp): value = self.calc_exp( # calculate polish self.polish_exp( # convert to polish self.subst_unary( # convert unary self.subst_symbol( # subst symbols self.parse_exp(exp))))) # generate tokens return value def open_file(self, name, ext, mode): # # Add the default file name extension if needed # sname = os.path.splitext(name) if not sname[1]: name += '.' + ext self.fnam = name try: file = open(name, mode, -1, None, None) except OSError as err: raise RuntimeError(err) return file def close_file(self, file): if file is not None: file.close() return # # Prints the specified symbol cathegoty into listing # def final_dict(self, title, t, fl): maxl = 0 nl = [] for s in self.symb: sy = self.symb[s] if sy[1] == t: nl.append(s) maxl = max(maxl, len(s)) nl.sort() numl = len(nl) if numl: print("\r\n%s: %d entries" % (title, numl), file=fl) ncol = max(80 // (maxl + 14), 1) nlin = (numl + ncol - 1) // ncol for i in range(nlin): s = '' for j in range(ncol): idx = j * nlin + i if idx < numl: if j: s += ' ' * 4 s += nl[idx].ljust(maxl + 2) s += '%08X' % self.symb[nl[idx]][0] if s: print(s, file=fl) return # # Show final compilation statistics # def final_stat(self, flst): msg = '\r\nErrors: %d\r\nWarnings: %d\r\n' % (self.ecnt, self.wcnt) if self.ecnt or self.wcnt: print(msg, file=sys.stderr) if flst is not None: self.final_dict("Register definitions", T_REG, flst) self.final_dict("Translation definitions", T_TRA, flst) self.final_dict("Symbol definitions", T_SYM, flst) print(msg, file=flst) return # # Store error information for the listing after the line # def log_error(self, emsg, ps=2): if ps in (0, self.ps): if self.efst == 0: print('', file=sys.stderr) self.efst = 1 msg = '*** Error[%d]: %s' % (self.lnum, emsg) print(msg, file=sys.stderr) self.elst.append(msg) self.ecnt += 1 return def log_warning(self, emsg, ps=2): if ps in (0, self.ps): if self.efst == 0: print('', file=sys.stderr) self.efst = 1 msg = '*** Warning[%d]: %s' % (self.lnum, emsg) print(msg, file=sys.stderr) self.elst.append(msg) self.wcnt += 1 return # # Process the local label block boundary # def local_block(self): if self.ps == 1: if self.cloc: self.bloc[self.clin] = self.cloc.copy() self.cloc = {} else: self.cloc = self.bloc.get(self.lnum, {}) self.clin = self.lnum return # # Assign the local or generic label with local counter # def assign_label(self, label): match = Wd16.RE_LOCAL.match(label) if match is not None: # # Local label processing # if self.ps == 1: if label in self.cloc: msg = "label duplication '%s'" % label self.log_error(msg, 0) raise SyntaxError(msg) self.cloc[label] = self.lc else: if self.cloc.get(label, -1) != self.lc: raise SyntaxError("label phase mismatch '%s'" % label) else: # # Generic label processing # if label == '.': raise SyntaxError("wrong location counter usage") label = label.upper() lc = self.symb.get(label) if self.ps == 1: if lc is not None: msg = "label duplication '%s'" % label self.log_error(msg, 0) raise SyntaxError(msg) self.symb[label] = (self.lc, T_SYM) else: if lc is None or lc[0] != self.lc or lc[1] != T_SYM: raise SyntaxError("label phase mismatch '%s'" % label) return # # Assign the value to symbol in "symbol = value" # def assign_symbol(self, symbol, exp): symbol = symbol.upper() value = self.eval_exp(exp) if symbol == '.': self.lc = value self.gval = value return qs = self.symb.get(symbol) if self.ps == 1: if qs is not None: msg = "symbol duplication '%s'" % symbol self.log_error(msg, 0) raise SyntaxError(msg) self.symb[symbol] = (value, T_SYM) else: if qs is None: self.symb[symbol] = (value, T_SYM) else: if qs[0] != value or qs[1] != T_SYM: raise SyntaxError("symbol phase mismatch '%s'" % symbol) self.gval = value return # # Field generator - splits on ',' accounting quotas, slashes, comments # def parse_gen(self, string): start = -1 quota = 0 slash = 0 pos = -1 for s in string: pos += 1 if start < 0: if s in ' \t': continue if s == ',': yield '' start = -1 quota = 0 slash = 0 continue if s == ';': break start = pos if quota == 0: if s == ',': yield string[start:pos] start = -1 quota = 0 slash = 0 continue if s == ';': if pos != start: yield string[start:pos] start = -1 break if s == "'": quota = 1 elif s == '"': quota = 2 continue if slash: slash = 0 continue if s == '\\': slash = 1 continue if quota == 1: if s == "'": quota = 0 else: if s == '"': quota = 0 if start >= 0: yield string[start:] # # Parse the optional parameters after opcode/directive # def parse_args(self, opcode, line): for field in self.parse_gen(line): field = field.strip(' \t') if field and field[0] not in '\'\"': field = field.upper() opcode.append(field) return opcode def insert_data(self, data, addr): if not MIN_LC <= addr < MAX_LC: raise SyntaxError("Location is out of range '0x%04X'" % addr) if self.data[addr] >= 0 and self.data[addr] != data: raise SyntaxError("Location multiple commit '0x%04X'" % addr) self.data[addr] = data return # # Do preliminary opcode/directive processing # def do_preop(self, opcode, obj): oc = opcode[0] narg = len(opcode) - 1 if narg < obj[1]: raise SyntaxError("not enough parameters for '%s'" % oc) if obj[0] < 0: if narg > obj[1]: raise SyntaxError("too many directive parameters '%s'" % oc) elif narg > (obj[1] + 2): raise SyntaxError("too many parameters for '%s'" % oc) narg -= obj[1] word = obj[0] if narg >= 1: # # Handle the LRR, RSVC and TTL bit field # oc = opcode[obj[1]+1] if oc and self.ps == 2: ext = self.eval_exp(oc) if ext & ~0x3F: raise SyntaxError("out of range extension 0x%X" % ext) word |= ext << 16 if narg >= 2: # # Handle the translation code field # oc = opcode[obj[1]+2] if oc and self.ps == 2: tran = self.symb.get(oc) if tran is None or tran[1] != T_TRA or tran[0] & ~0x7F: raise SyntaxError("invalid translation code '%s'" % oc) word |= tran[0] << 24 return word def do_opcode(self, opcode): oc = opcode[0] obj = Wd16.dir_ops.get(oc.lower()) if obj is None: raise SyntaxError("invalid command or directive '%s'" % oc) try: word = self.do_preop(opcode, obj) word = obj[2](self, opcode, word) except SyntaxError as err: if obj[0] < 0: raise err self.log_error(err) return 0 if word >= 0: self.gval = word return word # # Compile the single source line # def do_line(self, line): self.gval = None self.elst = [] # # Skip empty line # if not line: return # # Detect local label block boundary # if line[0] not in ' \t;0123456789': self.local_block() line = line.strip(' \t') if not line: return # # Match on optional present label "labels:" # match = Wd16.RE_LABEL.match(line) if match is not None: label = match.group().rstrip(' \t:') line = line[match.end():] self.assign_label(label) if not line: return # # Match on variable assignment "var = exp" # match = Wd16.RE_SYMBOL.match(line) if match is not None: label = match.group().rstrip(' \t=') line = line[match.end():] self.assign_symbol(label, line) return # # Match on opcode or assembler directive # match = Wd16.RE_OPCODE.match(line) if match is None: if line[0] == ';': return raise SyntaxError("syntax error") opcode = [match.group().rstrip(' \t')] line = line[match.end():] opcode = self.parse_args(opcode, line) word = self.do_opcode(opcode) if word >= 0: addr = self.lc self.lc += 1 if self.ps == 2: self.insert_data(word, addr) return def do_list(self, line, lc, flst): if flst is not None: if self.gval is None: val = ' ' * 8 else: val = '%08X' % self.gval print("%5d %03X %s\t\t%s" % (self.lnum, lc, val, line), file=flst) for emsg in self.elst: print("%s" % emsg, file=flst) return # # Set the pass number and reinit the parser # def set_pass(self, ps): self.ps = ps self.lc = 0 self.ecnt = 0 self.wcnt = 0 self.lnum = 0 self.clin = 0 self.cloc = self.bloc.get(0, {}) return # # Process one source file # def do_assembly(self, fsrc, flst): if self.ps == 2 and flst is not None: print('00000 Time: %s ' % time.ctime(), 'File: %s\r\n' % self.fnam.upper(), file=flst) self.fend = 0 for line in fsrc: self.lnum += 1 line = line.strip('\r\n') lc = self.lc try: self.do_line(line) except SyntaxError as err: self.log_error(err) if self.ps == 2 and flst is not None: self.do_list(line, lc, flst) if self.fend: break self.local_block() if self.fend == 0: self.elst = [] self.log_warning("missing '.end' directive") if flst is not None: print('%s' % self.elst[0], file=flst) return def verify_locs(self): for i in range(MAX_LC): pla = Wd16.PLA_LSI11.get(i) if pla is not None: if self.data[i] < 0: self.log_error("missed translation address 0x%03X" % i) continue if ((self.data[i] >> 24) & 0xFF) != pla: self.log_error("translation mismatch 0x%03X: %02X,%02X" % (i, self.data[i] >> 24, pla)) continue if self.data[i] >= 0: if (self.data[i] >> 24) & 0xFF: self.log_error("unexpected translation 0x%03X: %02X" % (i, self.data[i] >>24)) for i in range(MAX_LC): ttl = Wd16.TTL_LSI11.get(i) if ttl is not None: ttl >>= 2 if self.data[i] < 0: self.log_error("missed TTL address 0x%03X" % i) continue dat = (self.data[i] >> 18) & 0x0F if dat != ttl: self.log_error("TTL mismatch 0x%03X: %02X,%02X" % (i, dat, ttl)) continue if self.data[i] >= 0: dat = (self.data[i] >> 18) & 0x0F if dat: self.log_error("unexpected TTL 0x%03X: %02X" % (i, ttl)) return def write_obj(self, fobj, width): msk = (1 << width) - 1 lim = -1 for i in range(MAX_LC): if self.data[i] >= 0: lim = i if lim < 0: return lim += 1 print('DEPTH = %d;\n' 'WIDTH = %d;\n' 'ADDRESS_RADIX = HEX;\n' 'DATA_RADIX = HEX;\n' 'CONTENT BEGIN' % (lim, width), file=fobj) for i in range(lim): data = self.data[i] if data >= 0: print('%03X: %06X;' % (i, data & msk), file=fobj) print('END;', file=fobj) return def write_ttl(self, fttl): head = False msk = 0x0F << 18 for i in range(MAX_LC): data = self.data[i] if data >= 0 and data & msk: data &= msk if not head: print('TTL locations:', file=fttl) head = True print('%03X: %08X, %1X;' % (i, data, data >> 18), file=fttl) head = False msk = 0xFF << 24 for i in range(MAX_LC): data = self.data[i] if data >= 0 and data & msk: data &= msk if not head: print('TRA locations:', file=fttl) head = True print('%03X: %08X, %02X;' % (i, data, data >> 24), file=fttl) return def createParser(): p = argparse.ArgumentParser( description='Western Digital MCP-1600 Microcode Assembler, ' 'Version 20.05c, (c) 1801BM1') p.add_argument('src', nargs='+', help='input source file(s)', metavar='file [file ...]') p.add_argument('-l', '--lst', help='output listing file', metavar='file') p.add_argument('-o', '--obj', help='output object file', metavar='file') p.add_argument('-t', '--ttl', help='output TTL-logic file', metavar='file') p.add_argument('-v', '--verify', action='store_const', const=True, help='verify TRA/TTL for LSI-11') return p def main(): parser = createParser() params = parser.parse_args() try: asm = Wd16() # # Check all source files existance # for src in params.src: fsrc = asm.open_file(src, 'mic', 'r') asm.close_file(fsrc) # # Do the first pass for all sources # asm.set_pass(1) for src in params.src: fsrc = asm.open_file(src, 'mic', 'r') asm.do_assembly(fsrc, None) asm.close_file(fsrc) # # Do the second pass with optional listing output # asm.set_pass(2) if params.lst is not None: flst = asm.open_file(params.lst, 'lst', 'w') else: flst = None for src in params.src: fsrc = asm.open_file(src, 'mic', 'r') asm.do_assembly(fsrc, flst) asm.close_file(fsrc) # # Verify the translation and TTL locations # if params.verify: asm.verify_locs() asm.final_stat(flst) asm.close_file(flst) # # Save the assembling results # if params.obj is not None and asm.ecnt == 0: fobj = asm.open_file(params.obj, 'mif', 'w') if params.ttl is None: asm.write_obj(fobj, 22) else: asm.write_obj(fobj, 18) asm.close_file(fobj) if params.ttl is not None and asm.ecnt == 0: fttl = asm.open_file(params.ttl, 'ttl', 'w') asm.write_ttl(fttl) asm.close_file(fttl) if asm.ecnt or asm.wcnt: sys.exit(1) sys.exit(0) except RuntimeError as err: print('\r\nerror: %s' % err, file=sys.stderr) sys.exit(1) if __name__ == '__main__': main()