diff --git a/machine.py b/machine.py index 2ad2b45..3493086 100644 --- a/machine.py +++ b/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.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 + # 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... - 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() + # 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.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): diff --git a/pdptests.py b/pdptests.py index 624da3b..c86d17e 100644 --- a/pdptests.py +++ b/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: @@ -1361,13 +1394,25 @@ if __name__ == "__main__": # instructions and 1 sob (which taken together are considered as 50). # Want to drive "number=" up more than loopcount, so use # 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() 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()