all-assembler mmu test works, also verified in SIMH
This commit is contained in:
parent
17831a75e2
commit
f0a848910b
1 changed files with 302 additions and 197 deletions
499
pdptests.py
499
pdptests.py
|
@ -661,28 +661,31 @@ class TestMethods(unittest.TestCase):
|
||||||
trapexpected = i
|
trapexpected = i
|
||||||
self.assertEqual(p.r[1], trapexpected)
|
self.assertEqual(p.r[1], trapexpected)
|
||||||
|
|
||||||
def test_mmu_updown(self):
|
def _make_updown(self, taddr, uaddr, kaddr, uphysdata=0o200000):
|
||||||
# test the page length field support in both up and down directions
|
# Makes the instruction blocks required for the mmu_updown tests.
|
||||||
|
# This is separated out so (as described below) it becomes possible
|
||||||
# XXX whether it was wise to code this test as a magnum opus
|
# to execute this in isolation just to generate the instructions
|
||||||
# of assembler prowess is well open to debate. On the plus
|
# and use them in other simulators (e.g., SIMH)
|
||||||
# side, it certainly exercises a bunch of features besides
|
#
|
||||||
# just testing the MMU page length functionality.
|
# Returns a tuple (t, u, k) each being an InstructionBlock
|
||||||
|
|
||||||
cn = self.usefulconstants()
|
cn = self.usefulconstants()
|
||||||
p = self.make_pdp()
|
|
||||||
|
|
||||||
# Two tests - up and down.
|
# Two tests - up and down.
|
||||||
#
|
#
|
||||||
# In both tests, KERNEL I space page 0 is mapped to physical 0
|
# In both tests, KERNEL I space page 0 is mapped to physical 0
|
||||||
# and KERNEL I space page 7 is mapped to the I/O page.
|
# and KERNEL I space page 7 is mapped to the I/O page.
|
||||||
# I/D separation is NOT enabled for KERNEL.
|
# I/D separation is NOT enabled for KERNEL.
|
||||||
|
# taddr and kaddr MUST be in this first 8K of memory (if only
|
||||||
|
# because the mapping setup doesn't map anything else)
|
||||||
|
#
|
||||||
|
# USER I space is mapped to uaddr which can be any 8K boundary
|
||||||
|
# 0o20000 and beyond.
|
||||||
#
|
#
|
||||||
# USER I space is mapped to 0o20000.
|
|
||||||
# All 64K of USER D space is mapped to 64K of physical memory
|
# 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
|
# from uphysdata to uphysdata + 64K, but with a bizarre page
|
||||||
# a bizarre page length scheme according to UP or DOWN phase of
|
# length scheme according to UP or DOWN phase of the test as
|
||||||
# the test as below. I/D separation is (obviously) enabled for USER.
|
# below. I/D separation is (obviously) enabled for USER.
|
||||||
# All 64K of that memory is filled with sequential words such
|
# All 64K of that memory is filled with sequential words such
|
||||||
# that (vaddr) + vaddr = 0o123456 (where vaddr is a user D space
|
# that (vaddr) + vaddr = 0o123456 (where vaddr is a user D space
|
||||||
# virtual address 0 .. 65534). This gives the test two ways to verify
|
# virtual address 0 .. 65534). This gives the test two ways to verify
|
||||||
|
@ -709,11 +712,7 @@ class TestMethods(unittest.TestCase):
|
||||||
# same 0, 16, 32 .. progression (of valid "blocks") but they
|
# same 0, 16, 32 .. progression (of valid "blocks") but they
|
||||||
# are at the end of the segments.
|
# are at the end of the segments.
|
||||||
|
|
||||||
# these instructions do initialization common to both up/down cases
|
# this code will go at taddr
|
||||||
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:
|
with ASM() as tr:
|
||||||
# The trap handler for MMU faults and the trap 0 / trap 1 from
|
# 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
|
# the user code (when there is no MMU fault). It is integrated
|
||||||
|
@ -732,22 +731,26 @@ class TestMethods(unittest.TestCase):
|
||||||
tr.mov('(sp)+', 'r3') # r3 is now the trap instruction
|
tr.mov('(sp)+', 'r3') # r3 is now the trap instruction
|
||||||
tr.bic(0o177400, 'r3')
|
tr.bic(0o177400, 'r3')
|
||||||
tr.cmp(1, 'r3')
|
tr.cmp(1, 'r3')
|
||||||
tr.beq(1)
|
tr.beq(2) # skip the HALT and the MMU entry point
|
||||||
# this was not a "good" trap, the user code failed
|
# this was not a "good" trap, the user code failed
|
||||||
tr.halt()
|
tr.halt()
|
||||||
|
|
||||||
tr.br(1) # skip over the MMU entry point
|
|
||||||
|
|
||||||
tr.label('TrapMMU')
|
tr.label('TrapMMU')
|
||||||
tr.clr('r3') # indicate MMU fault case
|
tr.clr('r3') # indicate MMU fault case
|
||||||
|
|
||||||
|
# both Utrap and TrapMMU join in common here on out
|
||||||
# see if the access was good/bad as expected
|
# see if the access was good/bad as expected
|
||||||
tr.cmp('r2', 'r3')
|
tr.cmp('r2', 'r3')
|
||||||
tr.beq(1) # jump over the HALT
|
tr.beq(1) # jump over the HALT
|
||||||
tr.halt() # NOPE, something wrong!
|
tr.halt() # NOPE, something wrong!
|
||||||
|
|
||||||
|
# the user mode code specifically avoids '(r0)+'
|
||||||
|
# to avoid ambiguity in machine types for when the
|
||||||
|
# autoincrement happens in the face of MMU aborts.
|
||||||
|
# Bump r0 for the user code here accordingly:
|
||||||
|
tr.add(2, 'r0')
|
||||||
|
|
||||||
# see if it is time to switch to next table entry
|
# 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.cmp('2(r5)', 'r0')
|
||||||
tr.bne(7) # skip over the "time to switch" stanza
|
tr.bne(7) # skip over the "time to switch" stanza
|
||||||
|
|
||||||
|
@ -762,172 +765,15 @@ class TestMethods(unittest.TestCase):
|
||||||
tr.clr('(sp)') # put user PC back to zero
|
tr.clr('(sp)') # put user PC back to zero
|
||||||
tr.rtt()
|
tr.rtt()
|
||||||
|
|
||||||
# this code goes at kernel_addr
|
# this trap handler is only used during the startup phase
|
||||||
with ASM() as a:
|
# See where the kernel code invokes the user setup code
|
||||||
a.mov(0o20000, 'sp') # start system stack at 8k
|
tr.label('trap_usersetup')
|
||||||
# KERNEL I SPACE
|
# the kernel put a resume address onto the stack, just go there
|
||||||
# PAR 0 to physical 0
|
tr.add(4, 'sp') # get rid of user trap frame, don't care
|
||||||
# PAR 7 to physical 760000 and 22bit not turned on
|
tr.mov('(sp)+', 'pc')
|
||||||
#
|
|
||||||
# PDR 77406 = read/write, full length
|
|
||||||
a.clr(a.ptr(cn.KISA0))
|
|
||||||
a.mov(0o760000 >> 6, a.ptr(cn.KISA7))
|
|
||||||
a.mov(0o077406, a.ptr(cn.KISD0))
|
|
||||||
a.mov(0o077406, a.ptr(cn.KISD7))
|
|
||||||
|
|
||||||
# USER I SPACE
|
# user mode program: there are two parts to this
|
||||||
a.mov(0o20000 >> 6, a.ptr(cn.UISA0))
|
# Starting at (user) location ZERO: the test program:
|
||||||
a.mov(0o077406, a.ptr(cn.UISD0))
|
|
||||||
|
|
||||||
# USER D SPACE going UP...
|
|
||||||
a.mov(cn.UDSD0, 'r3') # will walk through D0 .. D7
|
|
||||||
# NOTE: A0 .. A7 is 040(r3)
|
|
||||||
a.clr('r0') # r0: segno*2 = (0, 2, 4, .., 14)
|
|
||||||
a.mov(0o2000, 'r4') # phys addr base (0o200000>>6)
|
|
||||||
|
|
||||||
a.label('PARloop')
|
|
||||||
|
|
||||||
a.mov('r4', '040(r3)') # set U PAR; don't bump r3 yet
|
|
||||||
a.add(0o200, 'r4') # 0o200 = 8192>>6
|
|
||||||
|
|
||||||
a.mov('r0', 'r2') # r2 = segno*2
|
|
||||||
a.ash(3, 'r2') # r2 = segno*16
|
|
||||||
a.swab('r2') # really (segno*16)<<8
|
|
||||||
a.add(0o06, 'r2') # ACF r/w segment
|
|
||||||
a.mov('r2', '(r3)+') # set U PDR
|
|
||||||
a.inc('r0') # bump r0 by two
|
|
||||||
a.inc('r0')
|
|
||||||
a.cmp('r0', 16) # and loop until done all 8 segments
|
|
||||||
a.blt('PARloop')
|
|
||||||
|
|
||||||
a.bis(1, a.ptr(cn.MMR3)) # enable I/D sep just for USER
|
|
||||||
a.mov(1, a.ptr(cn.MMR0)) # turn on MMU
|
|
||||||
|
|
||||||
# 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
|
|
||||||
a.rtt()
|
|
||||||
|
|
||||||
# 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
|
|
||||||
# pln calculations are different.
|
|
||||||
|
|
||||||
a.label('DOWN')
|
|
||||||
a.mov(cn.UDSD0, 'r3')
|
|
||||||
a.clr('r0')
|
|
||||||
a.label('PARloopDOWN')
|
|
||||||
# compute segno * 8 in r2 (r0 starts as segno*2)
|
|
||||||
a.mov('r0', 'r2')
|
|
||||||
a.ash(3, 'r2')
|
|
||||||
# pln = 0o177 - (segno * 16)
|
|
||||||
a.mov(0o177, 'r1')
|
|
||||||
a.sub('r2', 'r1')
|
|
||||||
a.mov('r1', 'r2')
|
|
||||||
a.swab('r2')
|
|
||||||
a.add(0o16, 'r2') # the downward growing case
|
|
||||||
a.mov('r2', '(r3)+') # set U PDR
|
|
||||||
a.inc('r0')
|
|
||||||
a.inc('r0')
|
|
||||||
a.cmp('r0', 16)
|
|
||||||
a.blt('PARloopDOWN')
|
|
||||||
|
|
||||||
# this halt will be right before the first run of user mode test
|
|
||||||
a.halt()
|
|
||||||
|
|
||||||
a.clr('r0') # initial loop condition
|
|
||||||
a.clr('(sp)') # just knows the user loop starts at zero
|
|
||||||
a.rtt()
|
|
||||||
|
|
||||||
# Now for something extra frosty... relocate just segment 4
|
|
||||||
# (arbitrarily chosen) of the user memory to a different
|
|
||||||
# physical page and run the test again to ensure it still works.
|
|
||||||
# This will make use of KERNEL A1 and A2 segments to map the
|
|
||||||
# relocation (note: I space because no sep I/D for kernel here)
|
|
||||||
a.label('BONUS')
|
|
||||||
|
|
||||||
# copy UDSA4 into KISA1 - mapping old segment into kernel space
|
|
||||||
a.mov(a.ptr(cn.UDSA0 + 4*2), a.ptr(cn.KISA0 + 2)) # i.e., A1
|
|
||||||
|
|
||||||
# the new location for this data will be physical 0o600000
|
|
||||||
# (not a typo) which becomes 0o6000 in the PAR
|
|
||||||
a.mov(0o6000, a.ptr(cn.KISA0 + 4)) # i.e., A2
|
|
||||||
|
|
||||||
# the standard PDR access/full-length/etc bits
|
|
||||||
a.mov(0o077406, a.ptr(cn.KISD0 + 2))
|
|
||||||
a.mov(0o077406, a.ptr(cn.KISD0 + 4))
|
|
||||||
|
|
||||||
# count r0, source address r1, destination r2
|
|
||||||
a.mov(4096, 'r0')
|
|
||||||
a.mov(8192, 'r1')
|
|
||||||
a.mov(8192*2, 'r2')
|
|
||||||
a.mov('(r1)+', '(r2)+')
|
|
||||||
a.literal(0o077002) # SOB to the copy
|
|
||||||
|
|
||||||
# switch the user page to the new mapping
|
|
||||||
a.mov(0o6000, a.ptr(cn.UDSA0 + 4*2))
|
|
||||||
|
|
||||||
# and the standard initialization/resume dance
|
|
||||||
a.halt()
|
|
||||||
a.clr('r0')
|
|
||||||
a.clr('(sp)') # just knows the user loop starts at zero
|
|
||||||
a.rtt()
|
|
||||||
|
|
||||||
# poke the trap handler vector (250)
|
|
||||||
pcps = [traps_addr + (tr.labels['TrapMMU'] * 2), 0o340]
|
|
||||||
self.loadphysmem(p, pcps, 0o250)
|
|
||||||
|
|
||||||
# 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
|
# read the given address: mov (r0),r1
|
||||||
# If this causes an MMU fault, it goes to the MMU trap handler
|
# 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
|
# If it succeeds, it then hits the trap(1) and goes to that
|
||||||
|
@ -937,29 +783,288 @@ class TestMethods(unittest.TestCase):
|
||||||
# the PC to zero and bumps r0 then returns to user mode for the
|
# 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
|
# next iteration. (Yes, all this could have been done with mfpd
|
||||||
# but that feels like a different test than this)
|
# but that feels like a different test than this)
|
||||||
|
#
|
||||||
|
# Start at (user) location labelled 'setup'
|
||||||
|
# user-mode code executed before the test begins to put
|
||||||
|
# the test pattern into memory
|
||||||
|
|
||||||
user_phys_ISPACEaddr = 0o20000
|
|
||||||
with ASM() as u:
|
with ASM() as u:
|
||||||
# this subtract combines the access check with part1 of chksum
|
# this subtract combines the access check with part1 of chksum
|
||||||
u.mov(0o123456, 'r1')
|
u.mov(0o123456, 'r1')
|
||||||
u.sub('(r0)', 'r1')
|
u.sub('(r0)', 'r1')
|
||||||
u.cmp('r0', 'r1')
|
u.cmp('r0', 'r1')
|
||||||
u.beq(1)
|
u.beq(1)
|
||||||
u.trap(0o77)
|
u.trap(0o77) # trap 77 indicates miscompare
|
||||||
u.trap(1) # indicate good status
|
u.trap(1) # indicate good status
|
||||||
|
# the kernel puts the PC back to zero after the good trap
|
||||||
|
# and also bumps r0. This is how the loop loops.
|
||||||
u.halt() # never get here, this is illegal
|
u.halt() # never get here, this is illegal
|
||||||
self.loadphysmem(p, u.instructions(), user_phys_ISPACEaddr)
|
|
||||||
|
|
||||||
# set the physical memory that will be mapped to user D
|
# this code is executed one time at startup (see kernel code)
|
||||||
# space to this pattern so the test can verify the mapping
|
u.label('setup')
|
||||||
checksum = 0o123456 # arbitrary
|
|
||||||
user_phys_DSPACEbase = 0o200000
|
# Initialize the user D space pattern that will be checked
|
||||||
words = (checksum - (user_phys_DSPACEbase + o) & 0o177777
|
# by the user code. In python this was:
|
||||||
for o in range(0, 65536, 2))
|
#
|
||||||
self.loadphysmem(p, words, user_phys_DSPACEbase)
|
# checksum = 0o123456 # arbitrary
|
||||||
|
# user_phys_DSPACEbase = 0o200000
|
||||||
|
# words = (checksum - (user_phys_DSPACEbase + o) & 0o177777
|
||||||
|
# for o in range(0, 65536, 2))
|
||||||
|
# self.loadphysmem(p, words, user_phys_DSPACEbase)
|
||||||
|
u.clr('r0')
|
||||||
|
u.mov(0o123456, 'r1')
|
||||||
|
|
||||||
|
u.label('pattern')
|
||||||
|
u.mov('r1', '(r0)+')
|
||||||
|
u.sub(2, 'r1')
|
||||||
|
u.tst('r0')
|
||||||
|
u.bne('pattern')
|
||||||
|
# the kernel code looks for this in r1 as success flag
|
||||||
|
u.mov(0o3333, 'r1')
|
||||||
|
u.trap(0)
|
||||||
|
|
||||||
|
# The kernel-mode code that drives the whole test
|
||||||
|
with ASM() as k:
|
||||||
|
k.mov(0o20000, 'sp') # start system stack at 8k
|
||||||
|
# KERNEL I SPACE
|
||||||
|
# PAR 0 to physical 0
|
||||||
|
# PAR 7 to physical 760000 and 22bit not turned on
|
||||||
|
#
|
||||||
|
# PDR 77406 = read/write, full length
|
||||||
|
k.clr(k.ptr(cn.KISA0))
|
||||||
|
k.mov(0o760000 >> 6, k.ptr(cn.KISA7))
|
||||||
|
k.mov(0o077406, k.ptr(cn.KISD0))
|
||||||
|
k.mov(0o077406, k.ptr(cn.KISD7))
|
||||||
|
|
||||||
|
# USER I SPACE
|
||||||
|
k.mov(uaddr >> 6, k.ptr(cn.UISA0))
|
||||||
|
k.mov(0o077406, k.ptr(cn.UISD0))
|
||||||
|
|
||||||
|
# USER D SPACE... first set it up to be simply/fully
|
||||||
|
# accessible at its physical home, so that the pattern
|
||||||
|
# can be set. Then come back and limit the page lengths later.
|
||||||
|
|
||||||
|
k.mov(cn.UDSD0, 'r3') # will walk through D0 .. D7
|
||||||
|
# NOTE: A0 .. A7 is 040(r3)
|
||||||
|
k.mov(uphysdata >> 6, 'r4') # phys addr base
|
||||||
|
k.mov(8, 'r0')
|
||||||
|
k.label('utmp')
|
||||||
|
k.mov('r4', '040(r3)') # set U PAR; don't bump r3 yet
|
||||||
|
k.add(0o200, 'r4') # 0o200 = 8192>>6
|
||||||
|
k.mov(0o77406, '(r3)+') # set U PDR and bump to next
|
||||||
|
k.sob(0, 'utmp')
|
||||||
|
|
||||||
|
k.bis(1, k.ptr(cn.MMR3)) # enable I/D sep just for USER
|
||||||
|
k.mov(1, k.ptr(cn.MMR0)) # turn on MMU
|
||||||
|
|
||||||
|
# certainly could have used mtpd to set up the user mode
|
||||||
|
# pattern but this is just another excuse to test more things
|
||||||
|
# jump to the user 'setup' code, but first establish a handler
|
||||||
|
# for the trap it will execute when done.
|
||||||
|
k.mov(taddr + (2 * tr.labels['trap_usersetup']), '*$34')
|
||||||
|
k.mov(0o340, '*$36')
|
||||||
|
|
||||||
|
# compute where the trap handler should resume
|
||||||
|
# XXX NEED BETTER FORWARD LABEL SUPPORT IN asmhelper
|
||||||
|
k.mov('pc', '-(sp)')
|
||||||
|
k.add(14, '(sp)') # hand calculated to be just after rtt
|
||||||
|
k.mov(0o140340, '-(sp)') # push user-ish PSW to K stack
|
||||||
|
k.mov(u.labels['setup'] * 2, '-(sp)') # PC for setup code
|
||||||
|
k.rtt()
|
||||||
|
|
||||||
|
# user code dropped this magic value into r1 on success
|
||||||
|
k.cmp(0o3333, 'r1')
|
||||||
|
k.beq(1)
|
||||||
|
k.halt()
|
||||||
|
|
||||||
|
# and now set the length limits on the user D space
|
||||||
|
k.mov(cn.UDSD0, 'r3') # will walk through D0 .. D7
|
||||||
|
k.clr('r0') # r0: segno*2 = (0, 2, 4, .., 14)
|
||||||
|
|
||||||
|
k.label('PDRloop')
|
||||||
|
k.mov('r0', 'r2') # r2 = segno*2
|
||||||
|
k.ash(3, 'r2') # r2 = segno*16
|
||||||
|
k.swab('r2') # really (segno*16)<<8
|
||||||
|
k.add(0o06, 'r2') # ACF r/w segment
|
||||||
|
k.mov('r2', '(r3)+') # set U PDR
|
||||||
|
k.inc('r0') # bump r0 by two
|
||||||
|
k.inc('r0')
|
||||||
|
k.cmp('r0', 16) # and loop until done all 8 segments
|
||||||
|
k.blt('PDRloop')
|
||||||
|
|
||||||
|
# create the test table, just push it onto the stack (yeehah!)
|
||||||
|
k.mov(0, '-(sp)') # this is a PAD (not really needed)
|
||||||
|
k.mov(0o666, '-(sp)') # this is a sentinel
|
||||||
|
k.mov(0o176100, '-(sp)')
|
||||||
|
k.mov(1, '-(sp)')
|
||||||
|
k.mov(0o160000, '-(sp)')
|
||||||
|
k.mov(0, '-(sp)')
|
||||||
|
k.mov(0o154100, '-(sp)')
|
||||||
|
k.mov(1, '-(sp)')
|
||||||
|
k.mov(0o140000, '-(sp)')
|
||||||
|
k.mov(0, '-(sp)')
|
||||||
|
k.mov(0o132100, '-(sp)')
|
||||||
|
k.mov(1, '-(sp)')
|
||||||
|
k.mov(0o120000, '-(sp)')
|
||||||
|
k.mov(0, '-(sp)')
|
||||||
|
k.mov(0o110100, '-(sp)')
|
||||||
|
k.mov(1, '-(sp)')
|
||||||
|
k.mov(0o100000, '-(sp)')
|
||||||
|
k.mov(0, '-(sp)')
|
||||||
|
k.mov(0o66100, '-(sp)')
|
||||||
|
k.mov(1, '-(sp)')
|
||||||
|
k.mov(0o60000, '-(sp)')
|
||||||
|
k.mov(0, '-(sp)')
|
||||||
|
k.mov(0o44100, '-(sp)')
|
||||||
|
k.mov(1, '-(sp)')
|
||||||
|
k.mov(0o40000, '-(sp)')
|
||||||
|
k.mov(0, '-(sp)')
|
||||||
|
k.mov(0o22100, '-(sp)')
|
||||||
|
k.mov(1, '-(sp)')
|
||||||
|
k.mov(0o20000, '-(sp)')
|
||||||
|
k.mov(0, '-(sp)')
|
||||||
|
k.mov(0o100, '-(sp)')
|
||||||
|
k.mov(1, '-(sp)')
|
||||||
|
|
||||||
|
# the test table for the trap handler is now here:
|
||||||
|
k.mov('sp', 'r5')
|
||||||
|
# test starts in the region at the start of the table
|
||||||
|
k.mov('(r5)', 'r2')
|
||||||
|
|
||||||
|
# poke the MMU trap handler vector (250)
|
||||||
|
k.mov(taddr + (tr.labels['TrapMMU'] * 2), '*$250')
|
||||||
|
k.mov(0o340, '*$252')
|
||||||
|
|
||||||
|
# same for the "trap N" handler
|
||||||
|
k.mov(taddr + (tr.labels['UTrap'] * 2), '*$34')
|
||||||
|
k.mov(0o340, '*$36')
|
||||||
|
|
||||||
|
# ok, now ready to start the user program
|
||||||
|
k.mov(0o140340, '-(sp)') # push user-ish PSW to K stack
|
||||||
|
k.clr('-(sp)') # new user PC = 0
|
||||||
|
k.clr('r0') # user test expects r0 to start zero
|
||||||
|
k.rtt()
|
||||||
|
|
||||||
|
# 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
|
||||||
|
# pln calculations are different.
|
||||||
|
|
||||||
|
k.label('DOWN')
|
||||||
|
k.mov(cn.UDSD0, 'r3')
|
||||||
|
k.clr('r0')
|
||||||
|
k.label('PARloopDOWN')
|
||||||
|
# compute segno * 8 in r2 (r0 starts as segno*2)
|
||||||
|
k.mov('r0', 'r2')
|
||||||
|
k.ash(3, 'r2')
|
||||||
|
# pln = 0o177 - (segno * 16)
|
||||||
|
k.mov(0o177, 'r1')
|
||||||
|
k.sub('r2', 'r1')
|
||||||
|
k.mov('r1', 'r2')
|
||||||
|
k.swab('r2')
|
||||||
|
k.add(0o16, 'r2') # the downward growing case
|
||||||
|
k.mov('r2', '(r3)+') # set U PDR
|
||||||
|
k.inc('r0')
|
||||||
|
k.inc('r0')
|
||||||
|
k.cmp('r0', 16)
|
||||||
|
k.blt('PARloopDOWN')
|
||||||
|
|
||||||
|
# this halt will be right before the first run of user mode test
|
||||||
|
k.halt()
|
||||||
|
|
||||||
|
k.clr('r0') # initial loop condition
|
||||||
|
k.clr('(sp)') # just knows the user loop starts at zero
|
||||||
|
k.rtt()
|
||||||
|
|
||||||
|
# Now for something extra frosty... relocate just segment 4
|
||||||
|
# (arbitrarily chosen) of the user memory to a different
|
||||||
|
# physical page and run the test again to ensure it still works.
|
||||||
|
# This will make use of KERNEL A1 and A2 segments to map the
|
||||||
|
# relocation (note: I space because no sep I/D for kernel here)
|
||||||
|
k.label('BONUS')
|
||||||
|
|
||||||
|
# copy UDSA4 into KISA1 - mapping old segment into kernel space
|
||||||
|
k.mov(k.ptr(cn.UDSA0 + 4*2), k.ptr(cn.KISA0 + 2)) # i.e., A1
|
||||||
|
|
||||||
|
# the new location for this data will be physical 0o600000
|
||||||
|
# (not a typo) which becomes 0o6000 in the PAR
|
||||||
|
k.mov(0o6000, k.ptr(cn.KISA0 + 4)) # i.e., A2
|
||||||
|
|
||||||
|
# the standard PDR access/full-length/etc bits
|
||||||
|
k.mov(0o077406, k.ptr(cn.KISD0 + 2))
|
||||||
|
k.mov(0o077406, k.ptr(cn.KISD0 + 4))
|
||||||
|
|
||||||
|
# count r0, source address r1, destination r2
|
||||||
|
k.mov(4096, 'r0')
|
||||||
|
k.mov(8192, 'r1')
|
||||||
|
k.mov(8192*2, 'r2')
|
||||||
|
k.mov('(r1)+', '(r2)+')
|
||||||
|
k.literal(0o077002) # SOB to the copy
|
||||||
|
|
||||||
|
# switch the user page to the new mapping
|
||||||
|
k.mov(0o6000, k.ptr(cn.UDSA0 + 4*2))
|
||||||
|
|
||||||
|
# and the standard initialization/resume dance
|
||||||
|
k.halt()
|
||||||
|
k.clr('r0')
|
||||||
|
k.clr('(sp)') # just knows the user loop starts at zero
|
||||||
|
k.rtt()
|
||||||
|
|
||||||
|
return (taddr, tr), (uaddr, u), (kaddr, k)
|
||||||
|
|
||||||
|
def test_mmu_updown(self):
|
||||||
|
# test the page length field support in both up and down directions
|
||||||
|
|
||||||
|
# This somewhat-irresponsible magnum opus of assembly code would
|
||||||
|
# have been much easier to write, understand, and maintain if it
|
||||||
|
# used the PDP1170/mmu/etc methods directly and was mostly
|
||||||
|
# written at the python level rather than elaborate machine code.
|
||||||
|
# For example, it could have looped over calls to mmu.v2p()
|
||||||
|
# instead of performing an elaborate dance of user mode code
|
||||||
|
# and kernel trap handlers to analyze the same thing.
|
||||||
|
#
|
||||||
|
# HOWEVER, doing it as machine code allowed the same instructions
|
||||||
|
# to be run through SIMH for cross-verification of the test itself
|
||||||
|
# and the machine behavior. Was the juice worth the squeeze?
|
||||||
|
# Someone else will have to decide; the deed is done.
|
||||||
|
#
|
||||||
|
# Note that the assembly of three code segments
|
||||||
|
# (the trap handlers, the user code, and the "kernel") are
|
||||||
|
# in a separate method, which is helpful for getting at the
|
||||||
|
# instructions in the "import to SIMH" usage scenario.
|
||||||
|
#
|
||||||
|
|
||||||
|
cn = self.usefulconstants()
|
||||||
|
p = self.make_pdp()
|
||||||
|
|
||||||
|
# On these addresses: the code isn't fully general (mostly in
|
||||||
|
# how the MMU is set up). The kernel stack will start at 8K
|
||||||
|
# (0o20000 physical) and work downwards. The traps and kernel
|
||||||
|
# code can be "anywhere" so long as it is in that first 8K
|
||||||
|
# of memory and leaves room for trap vectors at the bottom and
|
||||||
|
# stack at the top.
|
||||||
|
#
|
||||||
|
# The user code can be on any 64-byte (!) physical boundary.
|
||||||
|
# The kernel knows the virtual address for the start will be zero.
|
||||||
|
#
|
||||||
|
|
||||||
|
# this are all PHYSICAL addresses. The code is not fully general,
|
||||||
|
# there are constraints: taddr and kaddr must be in the first
|
||||||
|
# 8K of physical memory (if only because that's how the trivial
|
||||||
|
# kernel mapping is set up). The kernel stack, which also
|
||||||
|
# requires room for the test tables, must start at the tail end
|
||||||
|
# of the first 8K.
|
||||||
|
#
|
||||||
|
# The uaddr must be on an 8K boundary
|
||||||
|
taddr = 0o4000
|
||||||
|
kaddr = 0o6000 # make sure enough room for the traps code
|
||||||
|
uaddr = 0o20000
|
||||||
|
|
||||||
|
for addr, b in self._make_updown(taddr, uaddr, kaddr):
|
||||||
|
self.loadphysmem(p, b.instructions(), addr)
|
||||||
|
|
||||||
# finally ready to run the whole shebang!
|
# finally ready to run the whole shebang!
|
||||||
p.run(pc=kernel_addr)
|
p.run(pc=kaddr)
|
||||||
|
|
||||||
# a halt was encountered, verify r2 is the end sentinel
|
# a halt was encountered, verify r2 is the end sentinel
|
||||||
self.assertEqual(p.r[2], 0o666)
|
self.assertEqual(p.r[2], 0o666)
|
||||||
|
|
Loading…
Add table
Reference in a new issue