phase1 mmu_updown pure asm conversion
This commit is contained in:
parent
28ea9b2d7f
commit
17831a75e2
1 changed files with 146 additions and 45 deletions
191
pdptests.py
191
pdptests.py
|
@ -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)
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue