checkpoint on asm support with WITH

This commit is contained in:
Neil Webber 2023-09-11 18:57:00 -06:00
parent aba26d3d5e
commit d4c80db5c4
2 changed files with 62 additions and 53 deletions

View file

@ -28,7 +28,7 @@
# are focused around helping to create hand-constructed test code. # are focused around helping to create hand-constructed test code.
# #
import contextlib from contextlib import AbstractContextManager
class PDP11InstructionAssembler: class PDP11InstructionAssembler:
@ -43,12 +43,10 @@ class PDP11InstructionAssembler:
B6MODES[f"@-({_rn})"] = 0o50 | _i # autodecr deferred B6MODES[f"@-({_rn})"] = 0o50 | _i # autodecr deferred
del _i, _rn, _rnames del _i, _rn, _rnames
# see _InstBlock for explanation of 'with' syntax use
@classmethod @classmethod
def newsequence(cls): def instruction_block(cls):
return _Sequence() return _InstBlock()
def append_seq(self, insts):
return insts
def immediate_value(self, s): def immediate_value(self, s):
base = 8 base = 8
@ -145,23 +143,28 @@ class PDP11InstructionAssembler:
except KeyError: except KeyError:
raise valerr() from None raise valerr() from None
seq = [mode | (b6 & 0o07), idxval] seq = [mode | (b6 & 0o07), idxval]
try: return self._seqwords(seq)
self.append_seq(seq)
except AttributeError:
pass
return seq
# no-op here, but overridden in _Sequence to track generated instructions # no-op here, but overridden in _Sequence to track generated instructions
def _seqwords(self, seq): def _seqwords(self, seq):
return seq return seq
# All 2 operand instructions end up here eventually
def _2op(self, operation, src, dst): def _2op(self, operation, src, dst):
src6, *src_i = self.operand_parser(src) src6, *src_i = self.operand_parser(src)
dst6, *dst_i = self.operand_parser(dst) dst6, *dst_i = self.operand_parser(dst)
return self._seqwords([operation | src6 << 6 | dst6, *src_i, *dst_i]) 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): 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]) return self._seqwords([operation | dst6, *dst_i])
def mov(self, src, dst): def mov(self, src, dst):
@ -203,26 +206,34 @@ class PDP11InstructionAssembler:
def trap(self, tnum): def trap(self, tnum):
return self.literal(0o104400 | 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, /): def literal(self, inst, oprnd=None, /):
if oprnd is not None: """For hand-assembled instructions. Also allows 1 operand."""
return self._1op(inst, oprnd) return self._1op(inst, oprnd)
else:
return self._seqwords(inst)
# this is used for WITH ... it is mostly for the notational convenience # This provides a convenience for just calling the native methods
# of being able to do thing like # while accumulating a list of instructions. For better or for worse,
# with ASM.sequence() as u: # instead of:
# u.mov('r2','r6') # insts = (a.mov('r1', 'r2'), a.clr('r0'), ... etc)
# u.trap(0) #
# user_mode_instructions = u.sequence() # 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): def __init__(self):
super().__init__() super().__init__()
self._seq = [] self._instblock = []
def __enter__(self): def __enter__(self):
return self return self
@ -232,12 +243,11 @@ class _Sequence(PDP11InstructionAssembler, contextlib.AbstractContextManager):
def _seqwords(self, seq): def _seqwords(self, seq):
"""seq can be an iterable, or a naked (integer) instruction.""" """seq can be an iterable, or a naked (integer) instruction."""
if self._seq is not None: try:
try: self._instblock += seq
self._seq += seq except TypeError:
except TypeError: self._instblock += [seq]
self._seq += [seq]
return seq return seq
def sequence(self): def instruction_block(self):
return self._seq return self._instblock

View file

@ -126,7 +126,7 @@ class TestMethods(unittest.TestCase):
# These instructions will be placed at 2K in memory # 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 a.mov(0o20000, 'sp') # start system stack at 8k
# write the constants as described above # write the constants as described above
@ -180,7 +180,7 @@ class TestMethods(unittest.TestCase):
a.halt() a.halt()
instloc = 0o4000 # 2K instloc = 0o4000 # 2K
self.loadphysmem(p, a.sequence(), instloc) self.loadphysmem(p, a.instruction_block(), instloc)
return p, instloc return p, instloc
# these tests end up testing a other stuff too of course, including MMU # 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)): for result, r1tval in ((0o33333, 2), (0o22222, 0)):
# r1=r1tval, mfpi (r1) -> r0; expect r0 = result # r1=r1tval, mfpi (r1) -> r0; expect r0 = result
with ASM.newsequence() as a: with ASM.instruction_block() as a:
a.mov(r1tval, 'r1') a.mov(r1tval, 'r1')
a.mfpi('(r1)') a.mfpi('(r1)')
a.mov('(sp)+', 'r0') a.mov('(sp)+', 'r0')
tvecs.append((result, a.sequence())) tvecs.append((result, a.instruction_block()))
for result, insts in tvecs: for result, insts in tvecs:
with self.subTest(result=result, insts=insts): with self.subTest(result=result, insts=insts):
@ -205,13 +205,12 @@ class TestMethods(unittest.TestCase):
def test_mfpxsp(self): def test_mfpxsp(self):
cn = self.usefulconstants() cn = self.usefulconstants()
# these two instructions are needed as literals in the test sequence with ASM.instruction_block() as u:
with ASM.newsequence() as u:
u.mov('r2', 'r6') u.mov('r2', 'r6')
u.trap(0) 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 = premmu # just for brevity...
ts.mov(0o14000, ts.ptr(0o34)) # set vector 034 to 14000 ts.mov(0o14000, ts.ptr(0o34)) # set vector 034 to 14000
ts.clr(ts.ptr(0o36)) # PSW for trap - zero work 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.mov(0o140340, '-(sp)') # push user-ish PSW to K stack
ts.clr('-(sp)') # new user PC = 0 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 postmmu.literal(6) # RTT - goes to user mode, addr 0
p, pc = self.simplemapped_pdp(premmu=premmu.sequence(), p, pc = self.simplemapped_pdp(premmu=premmu.instruction_block(),
postmmu=postmmu.sequence()) postmmu=postmmu.instruction_block())
# put the trap handler at 14000 as expected # put the trap handler at 14000 as expected
with ASM.newsequence() as th: with ASM.instruction_block() as th:
th.mfpd('sp') th.mfpd('sp')
th.mov('(sp)+', 'r3') th.mov('(sp)+', 'r3')
th.halt() th.halt()
self.loadphysmem(p, th.sequence(), 0o14000) self.loadphysmem(p, th.instruction_block(), 0o14000)
p.run(pc=pc) p.run(pc=pc)
self.assertEqual(p.r[2], p.r[3]) self.assertEqual(p.r[2], p.r[3])
def test_mtpi(self): def test_mtpi(self):
cn = self.usefulconstants() cn = self.usefulconstants()
with ASM.newsequence() as ts: with ASM.instruction_block() as ts:
ts.mov(0o1717, '-(sp)') # pushing 0o1717 ts.mov(0o1717, '-(sp)') # pushing 0o1717
ts.mtpi(ts.ptr(0o02)) # and MTPI it to user location 2 ts.mtpi(ts.ptr(0o02)) # and MTPI it to user location 2
ts.clr(ts.ptr(cn.MMR0)) # turn MMU back off ts.clr(ts.ptr(cn.MMR0)) # turn MMU back off
ts.mov(ts.ptr(0o20002), 'r0') # r0 = (020002) ts.mov(ts.ptr(0o20002), 'r0') # r0 = (020002)
tvecs = ((0o1717, ts.sequence()),) tvecs = ((0o1717, ts.instruction_block()),)
for r0result, insts in tvecs: for r0result, insts in tvecs:
with self.subTest(r0result=r0result, insts=insts): with self.subTest(r0result=r0result, insts=insts):
@ -273,10 +272,10 @@ class TestMethods(unittest.TestCase):
sub_loc = testloc + 4 sub_loc = testloc + 4
for addsub, loc in (('add', add_loc), ('sub', sub_loc)): 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') getattr(a, addsub)('r0', 'r1')
a.halt() a.halt()
for offs, inst in enumerate(a.sequence()): for offs, inst in enumerate(a.instruction_block()):
p.physmem[(loc >> 1) + offs] = inst p.physmem[(loc >> 1) + offs] = inst
for r0, r1, added, a_nzvc, subbed, s_nzvc in testvecs: for r0, r1, added, a_nzvc, subbed, s_nzvc in testvecs:
@ -320,7 +319,7 @@ class TestMethods(unittest.TestCase):
# various condition code tests # various condition code tests
p = self.make_pdp() p = self.make_pdp()
with ASM.newsequence() as ts: with ASM.instruction_block() as ts:
# program is: # program is:
# CLR R0 # CLR R0
# BEQ 1f # BEQ 1f
@ -358,7 +357,6 @@ class TestMethods(unittest.TestCase):
ts.mov(ts.ptr(0o5000), 'r1') ts.mov(ts.ptr(0o5000), 'r1')
ts.mov(ts.ptr(0o5002), 'r2') ts.mov(ts.ptr(0o5002), 'r2')
# CMP R1,R2 BLE
ts.cmp('r1', 'r2') ts.cmp('r1', 'r2')
ts.literal(0o003401) # BLE 1f ts.literal(0o003401) # BLE 1f
ts.halt() ts.halt()
@ -370,7 +368,7 @@ class TestMethods(unittest.TestCase):
ts.dec('r0') ts.dec('r0')
ts.halt() ts.halt()
insts = ts.sequence() insts = ts.instruction_block()
instloc = 0o4000 instloc = 0o4000
self.loadphysmem(p, insts, instloc) self.loadphysmem(p, insts, instloc)
@ -405,6 +403,7 @@ class TestMethods(unittest.TestCase):
def test_unscc(self): def test_unscc(self):
# more stuff like test_cc but specifically testing unsigned Bxx codes # more stuff like test_cc but specifically testing unsigned Bxx codes
p = self.make_pdp() p = self.make_pdp()
insts = ( insts = (
# program is: # program is:
# CLR R0 # CLR R0