diff --git a/machine.py b/machine.py index d52183f..a430c5d 100644 --- a/machine.py +++ b/machine.py @@ -628,8 +628,6 @@ class PDP11: if self.r[self.SP] < lim: if not (self.straps & self.STRAPBITS.YELLOW): 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! @@ -638,6 +636,8 @@ class PDP11: # and this trap is executed self.r[self.SP] = 4 # !! just enough room for... raise PDPTraps.AddressError(cpuerr=self.CPUERR_BITS.REDZONE) + else: + self.straps |= self.STRAPBITS.YELLOW def get_synchronous_trap(self, abort_trap): """Return a synchronous trap, or possibly None. @@ -724,18 +724,7 @@ class PDP11: self.psw = newps self.psw_prevmode = saved_curmode # i.e., override newps<13:12> - prepushSP = self.r[6] - try: - self.stackpush(saved_psw) - self.stackpush(self.r[self.PC]) - 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 - # very bad mistakes by the kernel code. It is a fatal halt - # NOTE: The stack register is restored - self.logger.info(f"Trap pushing trap onto stack") - self.r[6] = prepushSP - self.halted = self.HALTED_STACK + self._trappush(self.r[self.PC], saved_psw) # The error register records (accumulates) reasons (if given) self.error_register |= trap.cpuerr @@ -805,6 +794,27 @@ class PDP11: self.r[6] = self.u16add(self.r[6], 2) return w + # this is special because stack limit checking is disabled + # during pushes of trap/interrupt frames. Any other types of + # traps during a stack trap push are a fatal CPU halt and represent + # a serious kernel programming error (invalid kernel stack) + def _trappush(self, pc, psw): + try: + # NOTE: The stack pointer is only modified if + # both of these succeed with no mmu/addressing traps + self.mmu.wordRW_KD(self.u16add(self.r[self.SP], -2), psw) + self.mmu.wordRW_KD(self.u16add(self.r[self.SP], -4), pc) + 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 + # very bad mistakes by the kernel code. It is a fatal halt + self.logger.info(f"Trap ({e}) pushing trap {trap} onto stack") + self.logger.info(f"Machine state: {self.machinestate()}") + self.logger.info("HALTING") + self.halted = self.HALTED_STACK + else: + self.r[self.SP] = self.u16add(self.r[self.SP], -4) + class PDP1170(PDP11): diff --git a/pdptests.py b/pdptests.py index d02ff22..f093a21 100644 --- a/pdptests.py +++ b/pdptests.py @@ -1309,13 +1309,7 @@ class TestMethods(unittest.TestCase): p.run(pc=aa) self.assertEqual(p.r[0], 3) # confirm made it all the way through - def test_stacklim1(self): - # Set the stack at the top of the yellow zone and then use it. - # This test should cause loopcount (3) YELLOW synchronous traps. - # Behavior and expected results verified by running identical - # machine code in SIMH - p = self.make_pdp() - + def _stacklimcode(self, go_red=False): # memory usage: # 0o4000.. is the test code # 0o6000.. is the trap handler @@ -1324,17 +1318,27 @@ class TestMethods(unittest.TestCase): # r5 is used to walk through the 0o7000+ storage, it is initialized # in the test code and used in the trap handler + p = self.make_pdp() + with ASM() as tr: # record... - tr.mov('r2', '(r5)+') # ...separator/entry number - tr.mov('sp', '(r5)+') # ...the sp - tr.mov('(sp)', '(r5)+') # ...the trap-saved pc - tr.mov(tr.ptr(0o177766), '(r5)+') # ...cpu error register - tr.mov('r2', '(r5)+') # ...separator/entry number + tr.mov('r2', '(r5)+') # ...separator/entry number + tr.mov('sp', '(r5)+') # ...the sp + tr.mov('(sp)', '(r5)+') # ...the trap-saved pc + tr.mov(tr.ptr(0o177766), 'r1') # (will be used later) + tr.mov('r1', '(r5)+') # ...cpu error register + tr.mov('r2', '(r5)+') # ...separator/entry number # indicate successfully completed the above, bump entry number tr.inc('r2') + + # but if RED trap, stop here. + tr.bit(p.CPUERR_BITS.REDZONE, 'r1') + tr.beq('rtt') + tr.halt() + tr.label('rtt') tr.rtt() + tra = 0o6000 self.loadphysmem(p, tr.instructions(), tra) @@ -1355,7 +1359,7 @@ class TestMethods(unittest.TestCase): a.mov(tra, a.ptr(0o4)) a.mov(0o340, a.ptr(0o6)) - loopcount = 3 + loopcount = 3 if not go_red else 30 # will never get to 30 a.mov(loopcount, 'r0') a.label('push') a.clr('-(sp)') @@ -1364,10 +1368,22 @@ class TestMethods(unittest.TestCase): aa = 0o4000 self.loadphysmem(p, a.instructions(), aa) + p.r[p.PC] = aa + return p - p.run(pc=aa) + def test_stacklim1(self): + # Set the stack at the top of the yellow zone and then use it. + # This test should cause loopcount (3) YELLOW synchronous traps. + # Behavior and expected results verified by running identical + # machine code in SIMH - # obtained by running above in SIMH + # r5 is used to walk through the 0o7000+ storage, it is initialized + # in the test code and used in the trap handler + + p = self._stacklimcode() + p.run() + + # obtained by running machine code in SIMH expected_7000 = [ # MARKER SP PC CPUERR MARKER 0o066000, 0o000372, 0o004052, 0o000010, 0o066000, @@ -1380,6 +1396,45 @@ class TestMethods(unittest.TestCase): with self.subTest(i=i, val=val): self.assertEqual(val, p.physmem[recbase + i]) + def test_stacklim_red(self): + p = self._stacklimcode(go_red=True) + p.run() + + # Behavior/results verified by running machine code on SIMH; + # however, SIMH halts the simulation on the red stack trap and + # requires intervention to continue into the actual trap handler. + # Doing that (i.e., "CONTINUE") leads to these same results. + self.assertEqual(p.r[1], 0o14) # RED|YELLOW + self.assertEqual(p.r[2], 0o66021) # known magic iteration marker + self.assertEqual(p.r[6], 0) # stack should be at zero + + # obtained by running machine code in SIMH + expected_7000 = [ + # MARKER SP PC CPUERR MARKER + 0o066000, 0o000372, 0o004052, 0o000010, 0o066000, + 0o066001, 0o000370, 0o004052, 0o000010, 0o066001, + 0o066002, 0o000366, 0o004052, 0o000010, 0o066002, + 0o066003, 0o000364, 0o004052, 0o000010, 0o066003, + 0o066004, 0o000362, 0o004052, 0o000010, 0o066004, + 0o066005, 0o000360, 0o004052, 0o000010, 0o066005, + 0o066006, 0o000356, 0o004052, 0o000010, 0o066006, + 0o066007, 0o000354, 0o004052, 0o000010, 0o066007, + 0o066010, 0o000352, 0o004052, 0o000010, 0o066010, + 0o066011, 0o000350, 0o004052, 0o000010, 0o066011, + 0o066012, 0o000346, 0o004052, 0o000010, 0o066012, + 0o066013, 0o000344, 0o004052, 0o000010, 0o066013, + 0o066014, 0o000342, 0o004052, 0o000010, 0o066014, + 0o066015, 0o000340, 0o004052, 0o000010, 0o066015, + 0o066016, 0o000336, 0o004052, 0o000010, 0o066016, + 0o066017, 0o000334, 0o004052, 0o000010, 0o066017, + 0o066020, 0o000000, 0o004052, 0o000014, 0o066020, + 0] + + recbase = 0o7000//2 # word address in phys mem + for i, val in enumerate(expected_7000): + with self.subTest(i=i, val=val): + self.assertEqual(val, p.physmem[recbase + i]) + def test_ubmap(self): p = self.make_pdp()