phase1 mmu_updown pure asm conversion

This commit is contained in:
Neil Webber 2023-09-15 23:47:48 -05:00
parent 28ea9b2d7f
commit 17831a75e2

View file

@ -678,11 +678,10 @@ class TestMethods(unittest.TestCase):
# and KERNEL I space page 7 is mapped to the I/O page.
# I/D separation is NOT enabled for KERNEL.
#
#
# USER I space is mapped to 0o20000.
# All 64K of USER D space is mapped to 64K of physical memory
# from 0o200000 (not a typo) to 0o400000 (not a typo), but with
# a bizarre segment length scheme according to UP or DOWN phase of
# a bizarre page length scheme according to UP or DOWN phase of
# the test as below. I/D separation is (obviously) enabled for USER.
# All 64K of that memory is filled with sequential words such
# that (vaddr) + vaddr = 0o123456 (where vaddr is a user D space
@ -690,10 +689,16 @@ class TestMethods(unittest.TestCase):
# the MMU map is working correctly: by where the accessibility of a
# segment ends and by the value at the location where it ends.
#
# For UP:
# Segment pages in the PDP-11 are broken down into 32-word (64-byte)
# units called "blocks" in the manual. There are 128 blocks in
# each 8KB page.
#
# For UP direction test:
#
# using ED=0 (segments grow upwards), create a user DSPACE mapping
# where segment zero has length ("PLF") 0, segment 1 has length 1,
# etc... and then check that valid addresses map correctly and
# where page zero has length ("PLF") 0, page 1 has
# length 16, page 2 has length 32 (all measured in blocks) etc...
# and then check that valid addresses map correctly and
# invalid ones fault correctly. Note a subtle semantic of the PDP
# page length field: to be invalid (in an upward growing segment)
# the address has to be GREATER than the computed block number.
@ -701,11 +706,63 @@ class TestMethods(unittest.TestCase):
#
# For DOWN:
# using ED=1 ("dirbit" = 0o10) segments grow downwards, with the
# same 0, 1, 2 .. progression (of valid "blocks") but they
# same 0, 16, 32 .. progression (of valid "blocks") but they
# are at the end of the segments.
# these instructions do initialization common to both up/down cases
kernel_addr = 0o4000 # arbitrary start for all this
kernel_addr = 0o6000 # arbitrary start for all this
traps_addr = 0o4000 # make sure enough room before kernel_addr
# this code will go at traps_addr
with ASM() as tr:
# The trap handler for MMU faults and the trap 0 / trap 1 from
# the user code (when there is no MMU fault). It is integrated
# into one routine.
#
# The user code gets to use r0 and r1, the trap handler
# gets to use r2-r5:
# r2: expected good flag (initialized elsewhere)
# r3: determined good flag (by trap entry)
# r5: TESTTABLE pointer (initialized elsewhere)
tr.label('UTrap')
# first determine if trap0 (bad) or trap1 (good)
tr.mov('(sp)', 'r3') # get user PC from trap frame
tr.mfpi('-2(r3)') # get the trap instruction
tr.mov('(sp)+', 'r3') # r3 is now the trap instruction
tr.bic(0o177400, 'r3')
tr.cmp(1, 'r3')
tr.beq(1)
# this was not a "good" trap, the user code failed
tr.halt()
tr.br(1) # skip over the MMU entry point
tr.label('TrapMMU')
tr.clr('r3') # indicate MMU fault case
# see if the access was good/bad as expected
tr.cmp('r2', 'r3')
tr.beq(1) # jump over the HALT
tr.halt() # NOPE, something wrong!
# see if it is time to switch to next table entry
tr.add(2, 'r0') # didn't rely on (r0)+ vs MMU semantic
tr.cmp('2(r5)', 'r0')
tr.bne(7) # skip over the "time to switch" stanza
# it is time to switch
tr.add(4, 'r5')
tr.mov('(r5)', 'r2')
tr.cmp(0o666, 'r2')
tr.bne(1)
tr.halt() # test done; success if r2 = 0o666
# next iteration of the user code loop
tr.clr('(sp)') # put user PC back to zero
tr.rtt()
# this code goes at kernel_addr
with ASM() as a:
a.mov(0o20000, 'sp') # start system stack at 8k
# KERNEL I SPACE
@ -745,28 +802,52 @@ class TestMethods(unittest.TestCase):
a.bis(1, a.ptr(cn.MMR3)) # enable I/D sep just for USER
a.mov(1, a.ptr(cn.MMR0)) # turn on MMU
a.mov(0o140340, '-(sp)') # push user-ish PSW to K stack
a.clr('-(sp)') # new user PC = 0
# create the test table, just push it onto the stack (yeehah!)
a.mov(0, '-(sp)') # this is a PAD (not really needed)
a.mov(0o666, '-(sp)') # this is a sentinel
a.mov(0o176100, '-(sp)')
a.mov(1, '-(sp)')
a.mov(0o160000, '-(sp)')
a.mov(0, '-(sp)')
a.mov(0o154100, '-(sp)')
a.mov(1, '-(sp)')
a.mov(0o140000, '-(sp)')
a.mov(0, '-(sp)')
a.mov(0o132100, '-(sp)')
a.mov(1, '-(sp)')
a.mov(0o120000, '-(sp)')
a.mov(0, '-(sp)')
a.mov(0o110100, '-(sp)')
a.mov(1, '-(sp)')
a.mov(0o100000, '-(sp)')
a.mov(0, '-(sp)')
a.mov(0o66100, '-(sp)')
a.mov(1, '-(sp)')
a.mov(0o60000, '-(sp)')
a.mov(0, '-(sp)')
a.mov(0o44100, '-(sp)')
a.mov(1, '-(sp)')
a.mov(0o40000, '-(sp)')
a.mov(0, '-(sp)')
a.mov(0o22100, '-(sp)')
a.mov(1, '-(sp)')
a.mov(0o20000, '-(sp)')
a.mov(0, '-(sp)')
a.mov(0o100, '-(sp)')
a.mov(1, '-(sp)')
# the test table for the trap handler is now here:
a.mov('sp', 'r5')
# test starts in the region at the start of the table
a.mov('(r5)', 'r2')
# ok, now ready to start the user program
a.mov(0o140340, '-(sp)') # push user-ish PSW to K stack
a.clr('-(sp)') # new user PC = 0
a.clr('r0') # user test expects r0 to start zero
# this halt will be right before the first run of user mode test
a.halt()
# the subsequent p.run() picks up here and starts the user code!
a.rtt()
# these instructions are the trap handlers for both
# the MMU abort and the trap 0 "all good". The only difference
# is that only the MMU abort puts 666 into r5.
a.label('TrapMMU')
a.mov(0o666, 'r5')
a.label('Trap0')
a.halt()
# when test code starts again with p.run(), restarts here...
a.clr('(sp)') # just knows the user loop starts at zero
a.rtt() # back for another iteration
# these instructions will be used to switch over
# to the DOWN phase of the test. Similar to the UP but
# don't have to do the PARs (they stay the same) and the
@ -833,34 +914,39 @@ class TestMethods(unittest.TestCase):
a.rtt()
# poke the trap handler vector (250)
pcps = [kernel_addr + (a.labels['TrapMMU'] * 2), 0]
pcps = [traps_addr + (tr.labels['TrapMMU'] * 2), 0o340]
self.loadphysmem(p, pcps, 0o250)
# same for the "trap 0" handler but skip to trap0_offs
pcps = [kernel_addr + (a.labels['Trap0'] * 2), 0]
# same for the "trap N" handler
pcps = [traps_addr + (tr.labels['UTrap'] * 2), 0o340]
self.loadphysmem(p, pcps, 0o34)
# all those trap instructions
self.loadphysmem(p, tr.instructions(), traps_addr)
# all those kernel instructions
self.loadphysmem(p, a.instructions(), kernel_addr)
# user mode program:
# read the given address: mov (r0)+,r1
# puts 0o42 into r5 (flag that everything worked)
# trap 0 back to kernel
# Test can then verify correct value in r5 (indicating
# MMU aborted or not) and correct value in r1 (indicating
# mapping is correct)
# read the given address: mov (r0),r1
# If this causes an MMU fault, it goes to the MMU trap handler
# If it succeeds, it then hits the trap(1) and goes to that
# handler. The kernel trap handler, using the test table,
# can then validate that the right thing happened.
# The code "loops" only because the kernel trap handler resets
# the PC to zero and bumps r0 then returns to user mode for the
# next iteration. (Yes, all this could have been done with mfpd
# but that feels like a different test than this)
user_phys_ISPACEaddr = 0o20000
with ASM() as u:
# this value never occurs in user DSPACE (because every
# word location has been written with an even value)
# so this is a sentinel for whether the read happened
user_noval = 1
u.mov(user_noval, 'r1')
u.clr('r5') # sentinel becomes 0o42 or 0o666
u.mov('(r0)+', 'r1')
u.mov(0o42, 'r5')
u.trap(0)
# this subtract combines the access check with part1 of chksum
u.mov(0o123456, 'r1')
u.sub('(r0)', 'r1')
u.cmp('r0', 'r1')
u.beq(1)
u.trap(0o77)
u.trap(1) # indicate good status
u.halt() # never get here, this is illegal
self.loadphysmem(p, u.instructions(), user_phys_ISPACEaddr)
@ -872,11 +958,17 @@ class TestMethods(unittest.TestCase):
for o in range(0, 65536, 2))
self.loadphysmem(p, words, user_phys_DSPACEbase)
# finally ready to run the kernel setup instructions
# finally ready to run the whole shebang!
p.run(pc=kernel_addr)
# a halt was encountered, verify r2 is the end sentinel
self.assertEqual(p.r[2], 0o666)
return
# this will be used for both up/down testing, based on goodf
def _test(goodf):
previous_good = True
for segno in range(8):
for o in range(4096):
p.run() # picks up at rtt pc
@ -885,9 +977,18 @@ class TestMethods(unittest.TestCase):
if goodf(segno, o*2):
r5_expected = 0o42
r1_expected = physval
if not previous_good:
print(f"bad to good at r0={oct(p.r[0])}, "
f"addr = {oct(segno*8192 + (2*o))}")
previous_good = True
else:
r5_expected = 0o666
r1_expected = user_noval
if previous_good:
print(f"good to bad at r0={oct(p.r[0])}, "
f"addr = {oct(segno*8192 + (2*o))}")
previous_good = False
self.assertEqual(p.r[1], r1_expected)
self.assertEqual(p.r[5], r5_expected)