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)
|
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)
|
|
||||||
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
|
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
|
||||||
|
self.straps &= ~self.STRAPBITS.YELLOW
|
||||||
return PDPTraps.AddressError(
|
return PDPTraps.AddressError(
|
||||||
cpuerr=self.CPUERR_BITS.REDZONE)
|
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):
|
||||||
|
|
49
pdptests.py
49
pdptests.py
|
@ -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:
|
||||||
|
@ -1363,11 +1396,23 @@ if __name__ == "__main__":
|
||||||
# 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()
|
||||||
|
|
Loading…
Add table
Reference in a new issue