From 475f2e4591f064d20be1851cfdc2803758c87199 Mon Sep 17 00:00:00 2001 From: Neil Webber Date: Mon, 11 Sep 2023 17:28:45 -0600 Subject: [PATCH] improved WITH syntax for instruction sequences --- pdpasmhelper.py | 130 ++++++++++++++++++-------------- pdptests.py | 192 ++++++++++++++++++++++++++---------------------- 2 files changed, 178 insertions(+), 144 deletions(-) diff --git a/pdpasmhelper.py b/pdpasmhelper.py index 66e8de1..7e77e37 100644 --- a/pdpasmhelper.py +++ b/pdpasmhelper.py @@ -28,6 +28,9 @@ # are focused around helping to create hand-constructed test code. # +import contextlib + + class PDP11InstructionAssembler: B6MODES = {} _rnames = [(f"R{_i}", _i) for _i in range(8)] + [("SP", 6), ("PC", 7)] @@ -40,19 +43,11 @@ class PDP11InstructionAssembler: B6MODES[f"@-({_rn})"] = 0o50 | _i # autodecr deferred del _i, _rn, _rnames - def __init__(self): - self.activeblock = None + @classmethod + def newsequence(cls): + return _Sequence() - def startblock(self): - self.activeblock = [] - - def addtoblock(self, insts): - if self.activeblock is not None: - self.activeblock += insts - return insts - - def endblock(self): - insts, self.activeblock = self.activeblock, None + def append_seq(self, insts): return insts def immediate_value(self, s): @@ -125,7 +120,7 @@ class PDP11InstructionAssembler: return [self.B6MODES[operand]] except KeyError: pass - + # last chance: X(Rn) and @X(rn) # see if X(Rn) or @X(Rn)... @@ -149,72 +144,97 @@ class PDP11InstructionAssembler: b6 = self.B6MODES['(' + s[1]] except KeyError: raise valerr() from None + seq = [mode | (b6 & 0o07), idxval] + try: + self.append_seq(seq) + except AttributeError: + pass + return seq - return [mode | (b6 & 0o07), idxval] + # no-op here, but overridden in _Sequence to track generated instructions + def _seqwords(self, seq): + return seq def _2op(self, operation, src, dst): src6, *src_i = self.operand_parser(src) dst6, *dst_i = self.operand_parser(dst) - return [ operation | src6 << 6 | dst6, *src_i, *dst_i ] + return self._seqwords([operation | src6 << 6 | dst6, *src_i, *dst_i]) def _1op(self, operation, dst): dst6, *dst_i = self.operand_parser(dst) - return [ operation | dst6, *dst_i ] + return self._seqwords([operation | dst6, *dst_i]) def mov(self, src, dst): - return self.addtoblock(self._2op(0o010000, src, dst)) + return self._2op(0o010000, src, dst) + + def cmp(self, src, dst): + return self._2op(0o020000, src, dst) + + def add(self, src, dst): + return self._2op(0o060000, src, dst) + + def sub(self, src, dst): + return self._2op(0o160000, src, dst) def clr(self, dst): - return self.addtoblock(self._1op(0o005000, dst)) + return self._1op(0o005000, dst) def inc(self, dst): - return self.addtoblock(self._1op(0o005200, dst)) + return self._1op(0o005200, dst) def halt(self): - return self.addtoblock([0]) + return self.literal(0) - xxx = """ - - 0o012706, 0o20000, # put system stack at 8k and works down + def mtpi(self, dst): + return self._1op(0o006600, dst) - 0o012737, 0o22222, 0o20000, - 0o012737, 0o33333, 0o20002, - 0o012737, 0o44444, 0o40000, + def mfpi(self, src): + return self._1op(0o006500, src) - # point both kernel seg 0 PARs to physical zero - 0o005037, cn.KISA0, # CLR $KISA0 - 0o005037, cn.KDSA0, # CLR $KDSA0 + def mtpd(self, dst): + return self._1op(0o106600, dst) - # kernel seg 7 D space PAR to I/O page (at 22-bit location) - 0o012737, 0o017760000 >> 6, cn.KDSA0 + (7 * 2), + def mfpd(self, src): + return self._1op(0o106500, src) - # user I seg 0 to 0o20000, user D seg 0 to 0o40000 - 0o012737, 0o20000 >> 6, cn.UISA0, - 0o012737, 0o40000 >> 6, cn.UDSA0, + def trap(self, tnum): + return self.literal(0o104400 | tnum) - # set the PDRs for segment zero - - 0o012703, 0o077406, # MOV #77406,R3 - # 77406 = PDR<2:0> = ACF = 0o110 = read/write - # PLF<14:8> =0o0774 = full length (128*64 bytes = 8K) - 0o010337, cn.KISD0, # MOV R3,KISD0 ... - 0o010337, cn.KDSD0, - 0o010337, cn.UISD0, - 0o010337, cn.UDSD0, - # PDR for segment 7 - 0o010337, cn.KDSD0 + (7 * 2), + # generally used for instructions not implemented by an explicit method + # Allows for one (or none) operand in the low 6 bits + def literal(self, inst, oprnd=None, /): + if oprnd is not None: + return self._1op(inst, oprnd) + else: + return self._seqwords(inst) - # set previous mode to USER, keeping current mode KERNEL, pri 7 - 0o012737, (p.KERNEL << 14) | (p.USER << 12) | (7 << 5), - self.ioaddr(p, p.PS_OFFS), +# this is used for WITH ... it is mostly for the notational convenience +# of being able to do thing like +# with ASM.sequence() as u: +# u.mov('r2','r6') +# u.trap(0) +# user_mode_instructions = u.sequence() - # turn on 22-bit mode, unibus mapping, and I/D sep for k & u - 0o012737, 0o000065, self.ioaddr(p, p.mmu.MMR3_OFFS), +class _Sequence(PDP11InstructionAssembler, contextlib.AbstractContextManager): + def __init__(self): + super().__init__() + self._seq = [] - # turn on relocation mode ... yeehah! (MMR0 known zero here) - 0o005237, self.ioaddr(p, p.mmu.MMR0_OFFS), # INC MMR0 - ) + def __enter__(self): + return self -""" - + def __exit__(self, *args, **kwargs): + return None + + def _seqwords(self, seq): + """seq can be an iterable, or a naked (integer) instruction.""" + if self._seq is not None: + try: + self._seq += seq + except TypeError: + self._seq += [seq] + return seq + + def sequence(self): + return self._seq diff --git a/pdptests.py b/pdptests.py index c59c3c1..f5c7d3b 100644 --- a/pdptests.py +++ b/pdptests.py @@ -29,6 +29,7 @@ import random from pdpasmhelper import PDP11InstructionAssembler as ASM + class TestMethods(unittest.TestCase): PDPLOGLEVEL = 'INFO' @@ -85,6 +86,7 @@ class TestMethods(unittest.TestCase): ns.UDSA0 = ns.UISA0 + 0o20 ns.MMR0 = cls.ioaddr(p, p.mmu.MMR0_OFFS) + ns.MMR3 = cls.ioaddr(p, p.mmu.MMR3_OFFS) return ns @@ -96,13 +98,17 @@ class TestMethods(unittest.TestCase): # User Data space seg 0 points to physical 0o40000 # and turns on the MMU # + # premmu is an optional list of instructions to execute + # before turning on the MMU + # + # postmmu is an optional list of instructions to execute + # after turning on the MMU + # - def simplemapped_pdp(self, p=None, addons=[]): + def simplemapped_pdp(self, p=None, *, premmu=[], postmmu=[]): if p is None: p = self.make_pdp() - asm = ASM() - cn = self.usefulconstants() # this is a table of instructions that ... @@ -120,126 +126,132 @@ class TestMethods(unittest.TestCase): # These instructions will be placed at 2K in memory # - asm.startblock() - asm.mov(0o20000, 'sp') # start system stack at 8k + with ASM.newsequence() as a: + a.mov(0o20000, 'sp') # start system stack at 8k - # write the constants as described above - asm.mov(0o22222, asm.ptr(0o20000)) - asm.mov(0o33333, asm.ptr(0o20002)) - asm.mov(0o44444, asm.ptr(0o40000)) + # write the constants as described above + a.mov(0o22222, a.ptr(0o20000)) + a.mov(0o33333, a.ptr(0o20002)) + a.mov(0o44444, a.ptr(0o40000)) - # point both kernel seg 0 PARs to physical zero - asm.clr(asm.ptr(cn.KISA0)) - asm.clr(asm.ptr(cn.KDSA0)) + # point both kernel seg 0 PARs to physical zero + a.clr(a.ptr(cn.KISA0)) + a.clr(a.ptr(cn.KDSA0)) - # kernel seg 7 D space PAR to I/O page (at 22-bit location) - asm.mov(0o017760000 >> 6, asm.ptr(cn.KDSA0 + (7 * 2))) + # kernel seg 7 D space PAR to I/O page (at 22-bit location) + a.mov(0o017760000 >> 6, a.ptr(cn.KDSA0 + (7 * 2))) - # user I seg 0 to 0o20000, user D seg 0 to 0o40000 - asm.mov(0o20000 >> 6, asm.ptr(cn.UISA0)) - asm.mov(0o40000 >> 6, asm.ptr(cn.UDSA0)) + # user I seg 0 to 0o20000, user D seg 0 to 0o40000 + a.mov(0o20000 >> 6, a.ptr(cn.UISA0)) + a.mov(0o40000 >> 6, a.ptr(cn.UDSA0)) - # set the PDRs for segment zero - asm.mov(0o077406, 'r3') - # 77406 = PDR<2:0> = ACF = 0o110 = read/write - # PLF<14:8> =0o0774 = full length (128*64 bytes = 8K) + # set the PDRs for segment zero + a.mov(0o077406, 'r3') + # 77406 = PDR<2:0> = ACF = 0o110 = read/write + # PLF<14:8> =0o0774 = full length (128*64 bytes = 8K) - asm.mov('r3', asm.ptr(cn.KISD0)) - asm.mov('r3', asm.ptr(cn.KDSD0)) - asm.mov('r3', asm.ptr(cn.UISD0)) - asm.mov('r3', asm.ptr(cn.UDSD0)) + a.mov('r3', a.ptr(cn.KISD0)) + a.mov('r3', a.ptr(cn.KDSD0)) + a.mov('r3', a.ptr(cn.UISD0)) + a.mov('r3', a.ptr(cn.UDSD0)) - # PDR for segment 7 - asm.mov('r3', asm.ptr(cn.KDSD0 + (7 * 2))) + # PDR for segment 7 + a.mov('r3', a.ptr(cn.KDSD0 + (7 * 2))) - # set previous mode to USER, keeping current mode KERNEL, pri 7 - asm.mov((p.KERNEL << 14) | (p.USER << 12) | (7 << 5), - asm.ptr(self.ioaddr(p, p.PS_OFFS))) + # set previous mode to USER, keeping current mode KERNEL, pri 7 + a.mov((p.KERNEL << 14) | (p.USER << 12) | (7 << 5), + a.ptr(self.ioaddr(p, p.PS_OFFS))) - # turn on 22-bit mode, unibus mapping, and I/D sep for k & u - asm.mov(0o000065, asm.ptr(self.ioaddr(p, p.mmu.MMR3_OFFS))) + # turn on 22-bit mode, unibus mapping, and I/D sep for k & u + a.mov(0o000065, a.ptr(cn.MMR3)) - # turn on relocation mode ... yeehah! (MMR0 known zero here) - asm.inc(asm.ptr(self.ioaddr(p, p.mmu.MMR0_OFFS))) + # Instructions supplied by caller, to be execute before + # enabling the MMU. They are "literals" since they have + # already been assembled. + for w in premmu: + a.literal(w) + # turn on relocation mode ... + a.inc(a.ptr(cn.MMR0)) - asm.addtoblock(addons) - asm.halt() - setup_instructions = asm.endblock() + # and the post-MMU instructions + for w in postmmu: + a.literal(w) + a.halt() instloc = 0o4000 # 2K - - self.loadphysmem(p, setup_instructions, instloc) + self.loadphysmem(p, a.sequence(), instloc) return p, instloc # these tests end up testing a other stuff too of course, including MMU def test_mfpi(self): - # ((r0, ..., rN) results, (instructions)), ... - tvecs = ( - # r1=2, mfpi (r1) -> r0; expect r0 = 33333 - ((0o33333,), (0o012701, 0o02, 0o006511, 0o012600)), + tvecs = [] - # r1=0, mfpi (r1) -> r0; expect r0 = 22222 - ((0o22222,), (0o012701, 0o00, 0o006511, 0o012600)), - ) + for result, r1tval in ((0o33333, 2), (0o22222, 0)): + # r1=r1tval, mfpi (r1) -> r0; expect r0 = result + with ASM.newsequence() as a: + a.mov(r1tval, 'r1') + a.mfpi('(r1)') + a.mov('(sp)+', 'r0') + tvecs.append((result, a.sequence())) - for rslts, insts in tvecs: - with self.subTest(rslts=rslts, insts=insts): - p, pc = self.simplemapped_pdp(addons=insts) + for result, insts in tvecs: + with self.subTest(result=result, insts=insts): + p, pc = self.simplemapped_pdp(postmmu=insts) p.run(pc=pc) - for rN, v in enumerate(rslts): - self.assertEqual(p.r[rN], v) + self.assertEqual(p.r[0], result) def test_mfpxsp(self): cn = self.usefulconstants() - insts = ( - # gotta turn mapping back off for these... - 0o005037, cn.MMR0, # CLR MMR0 - 0o012737, 0o14000, 0o34, # mov $14000,*#34 - 0o005037, 0o36, # clear *#36 .. perfectly fine PSW - 0o012700, 0o20000, # mov #20000,r0 - 0o012720, 0o010206, # put into user 0: mov r2,r6 - 0o012720, 0o104400, # put into user 2: trap 0 + # these two instructions are needed as literals in the test sequence + with ASM.newsequence() as u: + u.mov('r2', 'r6') + u.trap(0) + user_mode_instructions = u.sequence() - 0o012702, 0o123456, # put 123456 into R2 - 0o012746, 0o140340, # push user-ish PSW onto kernel stack - 0o005046, # new user PC == 0 - 0o005237, cn.MMR0, # back on with the mapping! + with ASM.newsequence() as premmu: + ts = premmu # just for brevity... + ts.mov(0o14000, ts.ptr(0o34)) # set vector 034 to 14000 + ts.clr(ts.ptr(0o36)) # PSW for trap - zero work + ts.mov(0o20000, 'r0') # mov #20000,r0 - 0o000006, # RTT -- goes to user mode, addr 0 - ) + for uinst in user_mode_instructions: + ts.mov(uinst, '(r0)+') + ts.mov(0o123456, 'r2') # mov #123456,r2 + ts.mov(0o140340, '-(sp)') # push user-ish PSW to K stack + ts.clr('-(sp)') # new user PC = 0 - p, pc = self.simplemapped_pdp(addons=insts) + with ASM.newsequence() as postmmu: + postmmu.literal(6) # RTT - goes to user mode, addr 0 + + p, pc = self.simplemapped_pdp(premmu=premmu.sequence(), + postmmu=postmmu.sequence()) # put the trap handler at 14000 as expected - traph = ( - 0o106506, # mfpd sp - 0o012603, # pop stack into r3 - 0 - ) - - self.loadphysmem(p, traph, 0o14000) - p.instlog = True + with ASM.newsequence() as th: + th.mfpd('sp') + th.mov('(sp)+', 'r3') + th.halt() + self.loadphysmem(p, th.sequence(), 0o14000) p.run(pc=pc) self.assertEqual(p.r[2], p.r[3]) def test_mtpi(self): - # need an instance just for the constants, meh - px = self.make_pdp() + cn = self.usefulconstants() + tvecs = ( - ((0o1717,), (0o012746, 0o1717, 0o006637, 0o02, - # turn MMU back off (!) - 0o005037, self.ioaddr(px, px.mmu.MMR0_OFFS), - 0o013700, 0o20002)), + (0o1717, (0o012746, 0o1717, 0o006637, 0o02, + # turn MMU back off (!) + 0o005037, cn.MMR0, + 0o013700, 0o20002)), ) - for rslts, insts in tvecs: - with self.subTest(rslts=rslts, insts=insts): - p, pc = self.simplemapped_pdp(addons=insts) + for r0result, insts in tvecs: + with self.subTest(r0result=r0result, insts=insts): + p, pc = self.simplemapped_pdp(postmmu=insts) p.run(pc=pc) - for rN, v in enumerate(rslts): - self.assertEqual(p.r[rN], v) + self.assertEqual(p.r[0], r0result) def test_add_sub(self): p = self.make_pdp() @@ -258,10 +270,12 @@ class TestMethods(unittest.TestCase): add_loc = testloc sub_loc = testloc + 4 - p.physmem[add_loc >> 1] = 0o060001 # ADD R0,R1 - p.physmem[(add_loc >> 1) + 1] = 0 - p.physmem[sub_loc >> 1] = 0o160001 # SUB R0,R1 - p.physmem[(sub_loc >> 1) + 1] = 0 + for addsub, loc in (('add', add_loc), ('sub', sub_loc)): + with ASM.newsequence() as a: + getattr(a, addsub)('r0', 'r1') + a.halt() + for offs, inst in enumerate(a.sequence()): + p.physmem[(loc >> 1) + offs] = inst for r0, r1, added, a_nzvc, subbed, s_nzvc in testvecs: with self.subTest(r0=r0, r1=r1, op="add"):