first pass at matching red/yellow zone stack violations to simh
This commit is contained in:
parent
1d20c37a5c
commit
eaa5e2f161
2 changed files with 95 additions and 37 deletions
77
machine.py
77
machine.py
|
@ -445,8 +445,6 @@ class PDP11:
|
|||
addr = self.u16add(self.r[Rn], autocrement)
|
||||
if addrmode == 0o50:
|
||||
addr = self.mmu.wordRW(addr, space=self.mmu.DSPACE)
|
||||
if Rn == self.SP:
|
||||
self.strapcheck = True # XXX THIS IS A NO-OP LOOK
|
||||
|
||||
# X(Rn) and @X(Rn)
|
||||
case 0, (0o60 | 0o70) as addrmode, Rn:
|
||||
|
@ -471,6 +469,8 @@ class PDP11:
|
|||
# for instruction recovery if there is a page error.
|
||||
self.mmu.MMR1mod(((autocrement & 0o37) << 3) | Rn)
|
||||
self.r[Rn] = self.u16add(self.r[Rn], autocrement)
|
||||
if Rn == self.SP and autocrement < 0:
|
||||
self.redyellowcheck() # may raise a RED zone exception
|
||||
|
||||
if rmw and (value is None) and (extendedb6 is None):
|
||||
extendedb6 = 0xFF_0000_27 | (addr << 8) | (space << 6)
|
||||
|
@ -611,6 +611,30 @@ class PDP11:
|
|||
g = _evalstop()
|
||||
return lambda: next(g)
|
||||
|
||||
def redyellowcheck(self):
|
||||
"""stack limits: possibly sets YELLOW straps, or go RED."""
|
||||
|
||||
# only applies to kernel stack operations
|
||||
if self.psw_curmode != self.KERNEL:
|
||||
return
|
||||
|
||||
# Note special semantic of zero which means 0o400
|
||||
# (as defined by hardware book)
|
||||
lim = self.stack_limit_register or 0o400
|
||||
if self.r[self.SP] <lim:
|
||||
self.logger.info(f"YELLOW ZONE, {list(map(oct, self.r))}")
|
||||
# definitely in at least a yellow condition
|
||||
self.straps |= self.STRAPBITS.YELLOW
|
||||
|
||||
# how about red?
|
||||
if self.r[self.SP] + 32 < lim: # uh oh - below the yellow!
|
||||
# this is a red zone trap which is immediate
|
||||
# the stack pointer is set to location 4
|
||||
# and this trap is executed
|
||||
self.r[self.SP] = 4 # !! just enough room for...
|
||||
raise PDPTraps.AddressError(
|
||||
cpuerr=self.CPUERR_BITS.REDZONE, is_redyellow=True)
|
||||
|
||||
def get_synchronous_trap(self, abort_trap):
|
||||
"""Return a synchronous trap, or possibly None.
|
||||
|
||||
|
@ -650,36 +674,21 @@ class PDP11:
|
|||
if self.error_register & ignores:
|
||||
return None
|
||||
|
||||
# The stack limit yellow bit is a little different ... it gets
|
||||
# set when there is the *possibility* of a stack limit violation.
|
||||
# (because the stack pointer changed, or because the limits changed).
|
||||
# This is where the actual limit test gets checked.
|
||||
# The stack limit yellow bit is a little different ... have
|
||||
# to also check for the red zone here.
|
||||
if self.straps & self.STRAPBITS.YELLOW:
|
||||
# Note special semantic of zero which means 0o400
|
||||
# (as defined by hardware book)
|
||||
lim = self.stack_limit_register or 0o400
|
||||
if self.r[self.SP] >= lim:
|
||||
self.straps &= ~self.STRAPBITS.YELLOW # never mind, all good!
|
||||
else:
|
||||
self.logger.info(f"YELLOW ZONE, {list(map(oct, self.r))}")
|
||||
# yup definitely in at least a yellow condition
|
||||
# at a minimum, it's a yellow zone fault
|
||||
self.error_register |= self.CPUERR_BITS.YELLOW
|
||||
|
||||
# how about red?
|
||||
if self.r[self.SP] + 32 < lim: # uh oh - below the yellow!
|
||||
# this is a red zone trap which is immediate
|
||||
# the stack pointer is set to location 4
|
||||
# and this trap is executed
|
||||
self.r[6] = 4 # !! just enough room for...
|
||||
# note that these are tested in priority order. With only two
|
||||
# cases here, if/elif seemed better than iterating a table
|
||||
if self.straps & self.STRAPBITS.MEMMGT:
|
||||
self.straps &= ~self.STRAPBITS.MEMMGT
|
||||
return PDPTraps.MMU()
|
||||
elif self.straps & self.STRAPBITS.YELLOW: # red handled as an abort
|
||||
self.straps &= ~self.STRAPBITS.YELLOW
|
||||
return PDPTraps.AddressError(
|
||||
cpuerr=self.CPUERR_BITS.REDZONE)
|
||||
|
||||
# note that only the first (should be highest) will fire
|
||||
for bit, trapcl in ((self.STRAPBITS.MEMMGT, PDPTraps.MMU),
|
||||
(self.STRAPBITS.YELLOW, PDPTraps.AddressError)):
|
||||
if self.straps & bit:
|
||||
self.straps &= ~bit
|
||||
return trapcl()
|
||||
cpuerr=self.CPUERR_BITS.YELLOW, is_redyellow=True)
|
||||
return None
|
||||
|
||||
def go_trap(self, trap):
|
||||
|
@ -718,9 +727,10 @@ class PDP11:
|
|||
self.psw_prevmode = saved_curmode # i.e., override newps<13:12>
|
||||
|
||||
prepushSP = self.r[6]
|
||||
skip_redyellow = trap.trapinfo.get('is_redyellow', False)
|
||||
try:
|
||||
self.stackpush(saved_psw)
|
||||
self.stackpush(self.r[self.PC])
|
||||
self.stackpush(saved_psw, skip_redyellow=skip_redyellow)
|
||||
self.stackpush(self.r[self.PC], skip_redyellow=skip_redyellow)
|
||||
except PDPTrap as e:
|
||||
# again this is a pretty egregious error it means the kernel
|
||||
# stack is not mapped, or the stack pointer is odd, or similar
|
||||
|
@ -779,9 +789,12 @@ class PDP11:
|
|||
self.straps |= self.STRAPBITS.YELLOW
|
||||
self._stklim = v & 0o177400
|
||||
|
||||
def stackpush(self, w):
|
||||
# XXX YELLOW CHECK ???
|
||||
def stackpush(self, w, skip_redyellow=False):
|
||||
self.r[6] = self.u16add(self.r[6], -2)
|
||||
# stacklimit checks only apply to the kernel and do not
|
||||
# apply when pushing the frame for a stacklimit fault (!)
|
||||
if self.psw_curmode == self.KERNEL and not skip_redyellow:
|
||||
self.redyellowcheck() # may raise a RED exception
|
||||
self.mmu.wordRW(self.r[6], w, space=self.mmu.DSPACE)
|
||||
|
||||
def stackpop(self):
|
||||
|
|
49
pdptests.py
49
pdptests.py
|
@ -1288,6 +1288,38 @@ class TestMethods(unittest.TestCase):
|
|||
p.run(pc=base_address)
|
||||
self.assertEqual(p.r[0], 0)
|
||||
|
||||
def test_stacklim(self):
|
||||
p = self.make_pdp()
|
||||
|
||||
magic = 0o123456
|
||||
# build the trap handler for testing stacklim
|
||||
with ASM() as tr:
|
||||
tr.mov('(sp)', 'r5')
|
||||
tr.mov(magic, 'r0')
|
||||
tr.mov(tr.ptr(0o177766), 'r1')
|
||||
tr.halt()
|
||||
tra = 0o6000
|
||||
self.loadphysmem(p, tr.instructions(), tra)
|
||||
|
||||
with ASM() as a:
|
||||
a.mov(0o400, 'sp')
|
||||
a.mov(tra, a.ptr(0o4))
|
||||
a.mov(0o340, a.ptr(0o6))
|
||||
a.clr('r0')
|
||||
a.clr('-(sp)')
|
||||
a.label('fault')
|
||||
a.halt()
|
||||
aa = 0o4000
|
||||
self.loadphysmem(p, a.instructions(), aa)
|
||||
|
||||
p.run(pc=aa)
|
||||
self.assertEqual(p.r[0], magic)
|
||||
self.assertEqual(p.r[1], p.CPUERR_BITS.YELLOW)
|
||||
self.assertEqual(p.r[5], aa + a.getlabel('fault'))
|
||||
|
||||
# this stack result was hand-verified in SIMH
|
||||
self.assertEqual(p.r[6], 0o372)
|
||||
|
||||
def test_ubmap(self):
|
||||
p = self.make_pdp()
|
||||
|
||||
|
@ -1354,6 +1386,7 @@ if __name__ == "__main__":
|
|||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('-p', '--performance', action="store_true")
|
||||
parser.add_argument('-i', '--instruction', default=movr1r0, type=int)
|
||||
parser.add_argument('--clr', action="store_true")
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.performance:
|
||||
|
@ -1363,11 +1396,23 @@ if __name__ == "__main__":
|
|||
# loopcount=20 ... means "1000" inst instructions
|
||||
# number=1000 ... do that 1000 times, for 1M instructions
|
||||
|
||||
# simple way to test CLR instruction vs default MOV.
|
||||
# The CLR instruction is not optimized the way MOV is so
|
||||
# this shows the difference.
|
||||
if args.clr:
|
||||
args.instruction = 0o005000
|
||||
|
||||
t = TestMethods()
|
||||
p, pc = t.speed_test_setup(loopcount=20, inst=args.instruction)
|
||||
ta = timeit.repeat(stmt='t.speed_test_run(p, pc)',
|
||||
number=1000, globals=globals(), repeat=5)
|
||||
number=1000, globals=globals(), repeat=10)
|
||||
tnsec = round(1000 * min(*ta), 1)
|
||||
print(f"Instruction {oct(args.instruction)} took {tnsec} nsecs")
|
||||
if args.instruction == movr1r0:
|
||||
instr = 'MOV R1,R0'
|
||||
elif (args.instruction & 0o177770) == 0o005000:
|
||||
instr = f'CLR R{args.instruction & 7}'
|
||||
else:
|
||||
instr = oct(args.instruction)
|
||||
print(f"Instruction {instr} took {tnsec} nsecs")
|
||||
else:
|
||||
unittest.main()
|
||||
|
|
Loading…
Add table
Reference in a new issue