first pass at matching red/yellow zone stack violations to simh

This commit is contained in:
Neil Webber 2023-09-26 17:13:11 -05:00
parent 1d20c37a5c
commit eaa5e2f161
2 changed files with 95 additions and 37 deletions

View file

@ -445,8 +445,6 @@ class PDP11:
addr = self.u16add(self.r[Rn], autocrement) addr = self.u16add(self.r[Rn], autocrement)
if addrmode == 0o50: if addrmode == 0o50:
addr = self.mmu.wordRW(addr, space=self.mmu.DSPACE) 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) # X(Rn) and @X(Rn)
case 0, (0o60 | 0o70) as addrmode, Rn: case 0, (0o60 | 0o70) as addrmode, Rn:
@ -471,6 +469,8 @@ class PDP11:
# for instruction recovery if there is a page error. # for instruction recovery if there is a page error.
self.mmu.MMR1mod(((autocrement & 0o37) << 3) | Rn) self.mmu.MMR1mod(((autocrement & 0o37) << 3) | Rn)
self.r[Rn] = self.u16add(self.r[Rn], autocrement) 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): if rmw and (value is None) and (extendedb6 is None):
extendedb6 = 0xFF_0000_27 | (addr << 8) | (space << 6) extendedb6 = 0xFF_0000_27 | (addr << 8) | (space << 6)
@ -611,6 +611,30 @@ class PDP11:
g = _evalstop() g = _evalstop()
return lambda: next(g) 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): def get_synchronous_trap(self, abort_trap):
"""Return a synchronous trap, or possibly None. """Return a synchronous trap, or possibly None.
@ -650,36 +674,21 @@ class PDP11:
if self.error_register & ignores: if self.error_register & ignores:
return None return None
# The stack limit yellow bit is a little different ... it gets # The stack limit yellow bit is a little different ... have
# set when there is the *possibility* of a stack limit violation. # to also check for the red zone here.
# (because the stack pointer changed, or because the limits changed).
# This is where the actual limit test gets checked.
if self.straps & self.STRAPBITS.YELLOW: if self.straps & self.STRAPBITS.YELLOW:
# Note special semantic of zero which means 0o400 # at a minimum, it's a yellow zone fault
# (as defined by hardware book) self.error_register |= self.CPUERR_BITS.YELLOW
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
self.error_register |= self.CPUERR_BITS.YELLOW
# how about red? # note that these are tested in priority order. With only two
if self.r[self.SP] + 32 < lim: # uh oh - below the yellow! # cases here, if/elif seemed better than iterating a table
# this is a red zone trap which is immediate if self.straps & self.STRAPBITS.MEMMGT:
# the stack pointer is set to location 4 self.straps &= ~self.STRAPBITS.MEMMGT
# and this trap is executed return PDPTraps.MMU()
self.r[6] = 4 # !! just enough room for... elif self.straps & self.STRAPBITS.YELLOW: # red handled as an abort
return PDPTraps.AddressError( self.straps &= ~self.STRAPBITS.YELLOW
cpuerr=self.CPUERR_BITS.REDZONE) return PDPTraps.AddressError(
cpuerr=self.CPUERR_BITS.YELLOW, is_redyellow=True)
# 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()
return None return None
def go_trap(self, trap): def go_trap(self, trap):
@ -718,9 +727,10 @@ class PDP11:
self.psw_prevmode = saved_curmode # i.e., override newps<13:12> self.psw_prevmode = saved_curmode # i.e., override newps<13:12>
prepushSP = self.r[6] prepushSP = self.r[6]
skip_redyellow = trap.trapinfo.get('is_redyellow', False)
try: try:
self.stackpush(saved_psw) self.stackpush(saved_psw, skip_redyellow=skip_redyellow)
self.stackpush(self.r[self.PC]) self.stackpush(self.r[self.PC], skip_redyellow=skip_redyellow)
except PDPTrap as e: except PDPTrap as e:
# again this is a pretty egregious error it means the kernel # again this is a pretty egregious error it means the kernel
# stack is not mapped, or the stack pointer is odd, or similar # stack is not mapped, or the stack pointer is odd, or similar
@ -779,9 +789,12 @@ class PDP11:
self.straps |= self.STRAPBITS.YELLOW self.straps |= self.STRAPBITS.YELLOW
self._stklim = v & 0o177400 self._stklim = v & 0o177400
def stackpush(self, w): def stackpush(self, w, skip_redyellow=False):
# XXX YELLOW CHECK ???
self.r[6] = self.u16add(self.r[6], -2) 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) self.mmu.wordRW(self.r[6], w, space=self.mmu.DSPACE)
def stackpop(self): def stackpop(self):

View file

@ -1288,6 +1288,38 @@ class TestMethods(unittest.TestCase):
p.run(pc=base_address) p.run(pc=base_address)
self.assertEqual(p.r[0], 0) 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): def test_ubmap(self):
p = self.make_pdp() p = self.make_pdp()
@ -1354,6 +1386,7 @@ if __name__ == "__main__":
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument('-p', '--performance', action="store_true") parser.add_argument('-p', '--performance', action="store_true")
parser.add_argument('-i', '--instruction', default=movr1r0, type=int) parser.add_argument('-i', '--instruction', default=movr1r0, type=int)
parser.add_argument('--clr', action="store_true")
args = parser.parse_args() args = parser.parse_args()
if args.performance: if args.performance:
@ -1361,13 +1394,25 @@ if __name__ == "__main__":
# instructions and 1 sob (which taken together are considered as 50). # instructions and 1 sob (which taken together are considered as 50).
# Want to drive "number=" up more than loopcount, so use # Want to drive "number=" up more than loopcount, so use
# loopcount=20 ... means "1000" inst instructions # loopcount=20 ... means "1000" inst instructions
# number=1000 ... do that 1000 times, for 1M 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() t = TestMethods()
p, pc = t.speed_test_setup(loopcount=20, inst=args.instruction) p, pc = t.speed_test_setup(loopcount=20, inst=args.instruction)
ta = timeit.repeat(stmt='t.speed_test_run(p, pc)', 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) 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: else:
unittest.main() unittest.main()