From d4c80db5c401da30c40d32ea1f24b775f0e7f3e8 Mon Sep 17 00:00:00 2001 From: Neil Webber Date: Mon, 11 Sep 2023 18:57:00 -0600 Subject: [PATCH] checkpoint on asm support with WITH --- pdpasmhelper.py | 76 ++++++++++++++++++++++++++++--------------------- pdptests.py | 39 +++++++++++++------------ 2 files changed, 62 insertions(+), 53 deletions(-) diff --git a/pdpasmhelper.py b/pdpasmhelper.py index 6b58343..d47a731 100644 --- a/pdpasmhelper.py +++ b/pdpasmhelper.py @@ -28,7 +28,7 @@ # are focused around helping to create hand-constructed test code. # -import contextlib +from contextlib import AbstractContextManager class PDP11InstructionAssembler: @@ -43,12 +43,10 @@ class PDP11InstructionAssembler: B6MODES[f"@-({_rn})"] = 0o50 | _i # autodecr deferred del _i, _rn, _rnames + # see _InstBlock for explanation of 'with' syntax use @classmethod - def newsequence(cls): - return _Sequence() - - def append_seq(self, insts): - return insts + def instruction_block(cls): + return _InstBlock() def immediate_value(self, s): base = 8 @@ -145,23 +143,28 @@ class PDP11InstructionAssembler: except KeyError: raise valerr() from None seq = [mode | (b6 & 0o07), idxval] - try: - self.append_seq(seq) - except AttributeError: - pass - return seq + return self._seqwords(seq) # no-op here, but overridden in _Sequence to track generated instructions def _seqwords(self, seq): return seq + # All 2 operand instructions end up here eventually def _2op(self, operation, src, dst): src6, *src_i = self.operand_parser(src) dst6, *dst_i = self.operand_parser(dst) return self._seqwords([operation | src6 << 6 | dst6, *src_i, *dst_i]) + # All 1 operand instructions end up here eventually + # This also supports 0 operand "literals" (which are typically + # instructions that have been hand-assembled another way) def _1op(self, operation, dst): - dst6, *dst_i = self.operand_parser(dst) + """dst can be None for, essentially, a _0op.""" + if dst is None: + dst6 = 0 + dst_i = [] + else: + dst6, *dst_i = self.operand_parser(dst) return self._seqwords([operation | dst6, *dst_i]) def mov(self, src, dst): @@ -203,26 +206,34 @@ class PDP11InstructionAssembler: def trap(self, tnum): return self.literal(0o104400 | tnum) - # 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) + """For hand-assembled instructions. Also allows 1 operand.""" + return self._1op(inst, oprnd) -# 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() +# This provides a convenience for just calling the native methods +# while accumulating a list of instructions. For better or for worse, +# instead of: +# insts = (a.mov('r1', 'r2'), a.clr('r0'), ... etc) +# +# a context manager can be used to write it this way: +# +# with ASM.instruction_block() as a: +# a.mov('r1', 'r2') +# a.clr('r0') +# ... +# etc +# +# and then the instructions are obtained via a.instruction_block() +# (not a typo; the WITH is a class method and ^^^^^ is an instance method) +# +# This is sometimes handy if conditional computation or other gyrations +# are needed in the gathering of the instructions -class _Sequence(PDP11InstructionAssembler, contextlib.AbstractContextManager): +class _InstBlock(PDP11InstructionAssembler, AbstractContextManager): def __init__(self): super().__init__() - self._seq = [] + self._instblock = [] def __enter__(self): return self @@ -232,12 +243,11 @@ class _Sequence(PDP11InstructionAssembler, contextlib.AbstractContextManager): 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] + try: + self._instblock += seq + except TypeError: + self._instblock += [seq] return seq - def sequence(self): - return self._seq + def instruction_block(self): + return self._instblock diff --git a/pdptests.py b/pdptests.py index a28e537..670fdfd 100644 --- a/pdptests.py +++ b/pdptests.py @@ -126,7 +126,7 @@ class TestMethods(unittest.TestCase): # These instructions will be placed at 2K in memory # - with ASM.newsequence() as a: + with ASM.instruction_block() as a: a.mov(0o20000, 'sp') # start system stack at 8k # write the constants as described above @@ -180,7 +180,7 @@ class TestMethods(unittest.TestCase): a.halt() instloc = 0o4000 # 2K - self.loadphysmem(p, a.sequence(), instloc) + self.loadphysmem(p, a.instruction_block(), instloc) return p, instloc # these tests end up testing a other stuff too of course, including MMU @@ -190,11 +190,11 @@ class TestMethods(unittest.TestCase): for result, r1tval in ((0o33333, 2), (0o22222, 0)): # r1=r1tval, mfpi (r1) -> r0; expect r0 = result - with ASM.newsequence() as a: + with ASM.instruction_block() as a: a.mov(r1tval, 'r1') a.mfpi('(r1)') a.mov('(sp)+', 'r0') - tvecs.append((result, a.sequence())) + tvecs.append((result, a.instruction_block())) for result, insts in tvecs: with self.subTest(result=result, insts=insts): @@ -205,13 +205,12 @@ class TestMethods(unittest.TestCase): def test_mfpxsp(self): cn = self.usefulconstants() - # these two instructions are needed as literals in the test sequence - with ASM.newsequence() as u: + with ASM.instruction_block() as u: u.mov('r2', 'r6') u.trap(0) - user_mode_instructions = u.sequence() + user_mode_instructions = u.instruction_block() - with ASM.newsequence() as premmu: + with ASM.instruction_block() 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 @@ -223,31 +222,31 @@ class TestMethods(unittest.TestCase): ts.mov(0o140340, '-(sp)') # push user-ish PSW to K stack ts.clr('-(sp)') # new user PC = 0 - with ASM.newsequence() as postmmu: + with ASM.instruction_block() as postmmu: postmmu.literal(6) # RTT - goes to user mode, addr 0 - p, pc = self.simplemapped_pdp(premmu=premmu.sequence(), - postmmu=postmmu.sequence()) + p, pc = self.simplemapped_pdp(premmu=premmu.instruction_block(), + postmmu=postmmu.instruction_block()) # put the trap handler at 14000 as expected - with ASM.newsequence() as th: + with ASM.instruction_block() as th: th.mfpd('sp') th.mov('(sp)+', 'r3') th.halt() - self.loadphysmem(p, th.sequence(), 0o14000) + self.loadphysmem(p, th.instruction_block(), 0o14000) p.run(pc=pc) self.assertEqual(p.r[2], p.r[3]) def test_mtpi(self): cn = self.usefulconstants() - with ASM.newsequence() as ts: + with ASM.instruction_block() as ts: ts.mov(0o1717, '-(sp)') # pushing 0o1717 ts.mtpi(ts.ptr(0o02)) # and MTPI it to user location 2 ts.clr(ts.ptr(cn.MMR0)) # turn MMU back off ts.mov(ts.ptr(0o20002), 'r0') # r0 = (020002) - tvecs = ((0o1717, ts.sequence()),) + tvecs = ((0o1717, ts.instruction_block()),) for r0result, insts in tvecs: with self.subTest(r0result=r0result, insts=insts): @@ -273,10 +272,10 @@ class TestMethods(unittest.TestCase): sub_loc = testloc + 4 for addsub, loc in (('add', add_loc), ('sub', sub_loc)): - with ASM.newsequence() as a: + with ASM.instruction_block() as a: getattr(a, addsub)('r0', 'r1') a.halt() - for offs, inst in enumerate(a.sequence()): + for offs, inst in enumerate(a.instruction_block()): p.physmem[(loc >> 1) + offs] = inst for r0, r1, added, a_nzvc, subbed, s_nzvc in testvecs: @@ -320,7 +319,7 @@ class TestMethods(unittest.TestCase): # various condition code tests p = self.make_pdp() - with ASM.newsequence() as ts: + with ASM.instruction_block() as ts: # program is: # CLR R0 # BEQ 1f @@ -358,7 +357,6 @@ class TestMethods(unittest.TestCase): ts.mov(ts.ptr(0o5000), 'r1') ts.mov(ts.ptr(0o5002), 'r2') - # CMP R1,R2 BLE ts.cmp('r1', 'r2') ts.literal(0o003401) # BLE 1f ts.halt() @@ -370,7 +368,7 @@ class TestMethods(unittest.TestCase): ts.dec('r0') ts.halt() - insts = ts.sequence() + insts = ts.instruction_block() instloc = 0o4000 self.loadphysmem(p, insts, instloc) @@ -405,6 +403,7 @@ class TestMethods(unittest.TestCase): def test_unscc(self): # more stuff like test_cc but specifically testing unsigned Bxx codes p = self.make_pdp() + insts = ( # program is: # CLR R0