clock tests and adc

This commit is contained in:
Neil Webber 2024-10-24 23:45:11 -05:00
parent 29af35d50d
commit 9b0c1b906f

View file

@ -25,11 +25,13 @@ from types import SimpleNamespace
import breakpoints as BKP import breakpoints as BKP
from machine import PDP1170 from machine import PDP1170
from unibus import BusCycle from unibus import BusCycle
from kw11 import KW11
from kl11 import KL11 from kl11 import KL11
from branches import BRANCH_CODES from branches import BRANCH_CODES
from pdptraps import PDPTraps from pdptraps import PDPTraps
import unittest import unittest
import random import random
import time
import os import os
import io import io
import hashlib import hashlib
@ -38,6 +40,13 @@ import boot
from pdpasmhelper import InstructionBlock from pdpasmhelper import InstructionBlock
# subclass of KW11 to allow for selectable HZ rate
class KW11HZ(KW11):
def __init__(self, *args, hz, **kwargs):
self.HZ = hz
super().__init__(*args, **kwargs)
class TestMethods(unittest.TestCase): class TestMethods(unittest.TestCase):
PDPLOGLEVEL = 'INFO' PDPLOGLEVEL = 'INFO'
@ -45,9 +54,11 @@ class TestMethods(unittest.TestCase):
# used to create various instances, collects all the options # used to create various instances, collects all the options
# detail into this one place... mostly this is about loglevel # detail into this one place... mostly this is about loglevel
@classmethod @classmethod
def make_pdp(cls, memwords=64*1024): def make_pdp(cls, memwords=64*1024, loglevel=None):
if loglevel is None:
loglevel = cls.PDPLOGLEVEL
m128 = [0] * memwords m128 = [0] * memwords
return PDP1170(loglevel=cls.PDPLOGLEVEL, physmem=m128) return PDP1170(loglevel=loglevel, physmem=m128)
@staticmethod @staticmethod
def ioaddr(p, offs): def ioaddr(p, offs):
@ -200,7 +211,7 @@ class TestMethods(unittest.TestCase):
self.loadphysmem(p, a, instloc) self.loadphysmem(p, a, instloc)
return p, instloc return p, instloc
def u64mapped_pdp(self, p=None, /): def u64mapped_pdp(self, p=None, /, *pdpargs, **pdpkwargs):
# Make a PDP11 with: # Make a PDP11 with:
# * kernel space mapped to first 56K of memory (straight through) # * kernel space mapped to first 56K of memory (straight through)
# * top of kernel space mapped to I/O page # * top of kernel space mapped to I/O page
@ -209,7 +220,7 @@ class TestMethods(unittest.TestCase):
# No I/D separation. # No I/D separation.
# #
if p is None: if p is None:
p = self.make_pdp() p = self.make_pdp(*pdpargs, **pdpkwargs)
cn = self.usefulconstants() cn = self.usefulconstants()
@ -2414,6 +2425,110 @@ class TestMethods(unittest.TestCase):
for i, t in enumerate(cbx.arglog): for i, t in enumerate(cbx.arglog):
self.assertEqual(t, gold[i]) self.assertEqual(t, gold[i])
def test_adc(self):
p = self.make_pdp()
a = InstructionBlock()
a.clr('r1')
a.mov(0o177777, 'r0')
# NOTE WELL: INC DOES NOT SET C (!!!)
a.add(1, 'r0')
a.adc('r1')
a.halt()
self.loadphysmem(p, a, 0o10000)
p.run(pc=0o10000)
self.assertEqual(p.r[0], 0)
self.assertEqual(p.r[1], 1)
# not automatically tested
def clocktests_basic(self):
# test clock overhead
# memory layout
# 0o100 VECTOR for KW11 interrupt handler
# 0o102 PSW for KW11 interrupt handler
#
# 0o10000 stack (grows down)
# 0o10000 start address (set up clock, vectors, etc)
# 0o20000 interrupt handler
#
# REGISTER USAGE
# r0-r3 scratch
# r3 count down (# of interrupts to take until HALT)
# r4:r5 32-bit count up performance tracker (r5 high)
mc = InstructionBlock()
mc_addr = 0o10000
ih = InstructionBlock()
ih_addr = 0o20000
mc.mov(mc_addr, 'sp')
mc.mov(0o340, mc.ptr(0o177776)) # PSW .. pri 7
mc.mov(ih_addr, mc.ptr(0o100))
mc.mov(0o340, mc.ptr(0o102))
mc.mov(10, 'r3')
mc.clr('r4')
mc.clr('r5')
# enable interrupts and start the counter loop
mc.mov(0o100, mc.ptr(0o177546))
mc.clr(mc.ptr(0o177776))
mc.label('loop')
mc.inc('r4') # NOTE: INC does not set C (!!)
mc.bne('loop')
mc.inc('r5')
mc.br('loop')
ih.dec('r3')
ih.beq('done')
ih.rti()
ih.label('done')
ih.halt()
# this test is a little questionable in that what it assumes
# is that an increasing line clock frequency will correspond to
# an increase in overhead, and the
#
# self.assertTrue(thisrun < prev)
#
# test depends on that (i.e., fewer increments get to happen if the
# clock interrupt is going off more often). Obviously, random
# performance interference issues in the test environment could
# cause a spurious failure.
#
prev = 0x7fffffff
for hz in [2, 5, 10, 25, 50, 100]:
p = self.make_pdp()
clk = KW11HZ(p.ub, hz=hz)
self.loadphysmem(p, mc, mc_addr)
self.loadphysmem(p, ih, ih_addr)
p.run(pc=mc_addr)
thisrun = (p.r[5] << 16) | p.r[4]
with self.subTest(hz=hz, prev=prev, thisrun=thisrun):
self.assertTrue(thisrun < prev)
prev = thisrun
# not automatically tested
def clocktests_hz(self):
for hz in [5, 10, 25, 50, 100]:
p = self.make_pdp()
clk = KW11HZ(p.ub, hz=hz)
arbitrary_delay = 1
time.sleep(arbitrary_delay)
stamps = list(clk.tslog)
# However many time stamps there are, the average delay over
# those readings should be 1/hz seconds. It won't be that,
# of course, but see if it is plausible.
calculated_hz = (len(stamps) - 1) / (stamps[-1] - stamps[0])
# it will (presumably!) always run slow, and this is the
# empirically-determined reasonable slop factor
minratio = 0.8
with self.subTest(hz=hz, calculated_hz=calculated_hz):
self.assertTrue(calculated_hz/hz > minratio)
def test_breakpoints1(self): def test_breakpoints1(self):
# test the steps=N breakpoint capability # test the steps=N breakpoint capability
@ -2710,7 +2825,8 @@ class TestMethods(unittest.TestCase):
# are all just dummied up right now # are all just dummied up right now
# this is not a unit test, invoke it using timeit etc # this is not a unit test, invoke it using timeit etc
def speed_test_setup(self, instwords, /, *, loopcount=200, mmu=True): def speed_test_setup(self, instwords, /, *,
loopcount=200, usemmu=True, hz=0):
"""Set up a test run of the instwords (1 word or 2) """Set up a test run of the instwords (1 word or 2)
Returns tuple: p, pc, per .. see speed_test_run() Returns tuple: p, pc, per .. see speed_test_run()
@ -2718,28 +2834,52 @@ class TestMethods(unittest.TestCase):
If len == 2 (i.e., an instruction + operand), 24 instructions + SOB If len == 2 (i.e., an instruction + operand), 24 instructions + SOB
""" """
p, pc = self.u64mapped_pdp() # use loglevel WARNING to avoid a zillion start/halt logs
p, pc = self.u64mapped_pdp(loglevel='WARNING')
# the returned pdp is loaded with instructions for setting up # the returned pdp is loaded with instructions for setting up
# the mmu; only do them if that's what is wanted # the mmu; only do them if that's what is wanted
# # NOTE: the test code is run in USER mode.
# NOTE: the test code is run in USER mode Because Reasons
# (was experimenting with virtual caches and this was helpful). if usemmu:
if hz:
clk = KW11HZ(p.ub, hz=hz)
p.associate_device(clk, 'KW')
if mmu:
p.run(pc=pc) # set up all those mappings p.run(pc=pc) # set up all those mappings
usermode_base = 0o10000 # phys 0o210000 maps here in USER mode usermode_base = 0o10000 # phys 0o210000 maps here in USER mode
user_physloc = 0o210000 user_physloc = 0o210000
if hz:
ihloc = 0o4000
# the interrupt handler itself
ih = InstructionBlock()
ih.rti()
for a2, w in enumerate(ih):
p.mmu.wordRW(ihloc + (2 * a2), w)
# set up the vector
p.mmu.wordRW(0o100, ihloc)
p.mmu.wordRW(0o102, 0o340)
# start the clock!
p.mmu.wordRW(0o177546, 0o100)
else: else:
if hz:
raise ValueError("Use of KW11 requires MMU")
user_physloc = 0o20000 user_physloc = 0o20000
usermode_base = user_physloc usermode_base = user_physloc
# kernel startup code at 0o5000 (NOTE: ih was at 0o4000)
kloc = 0o5000
# this is the tiny kernel code used to set up and start # this is the tiny kernel code used to set up and start
# each major iteration of the user mode timing code. The one-time # each major iteration of the user mode timing code. The one-time
# overhead of these few instructions is irrelevant in the timing test. # overhead of these few instructions is irrelevant in the timing test.
k = InstructionBlock() k = InstructionBlock()
k.mov(0o20000, 'sp') # establish proper kernel stack k.mov(0o20000, 'sp') # establish proper kernel stack
k.mov(0o140340, '-(sp)') # USER mode, no interrupts k.mov(0o140000, '-(sp)') # USER mode, spl 0
k.mov(usermode_base, '-(sp)') # pc start for loop/USER code k.mov(usermode_base, '-(sp)') # pc start for loop/USER code
# these environmental "knowns" are available for the test inst # these environmental "knowns" are available for the test inst
k.mov(0o1000, 'r5') # usable writeable addr k.mov(0o1000, 'r5') # usable writeable addr
@ -2747,7 +2887,6 @@ class TestMethods(unittest.TestCase):
k.clr('r1') k.clr('r1')
k.rtt() # off to the races! k.rtt() # off to the races!
kloc = 0o4000
for a2, w in enumerate(k): for a2, w in enumerate(k):
p.mmu.wordRW(kloc + (2 * a2), w) p.mmu.wordRW(kloc + (2 * a2), w)
@ -2826,9 +2965,11 @@ 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('--clocktest', action="store_true")
parser.add_argument('-i', '--instruction', default=movr1r0, type=asminst, parser.add_argument('-i', '--instruction', default=movr1r0, type=asminst,
help="Test instruction, in octal. E.g.: 010203") help="Test instruction, in octal. E.g.: 010203")
parser.add_argument('--nommu', action="store_true") parser.add_argument('--nommu', action="store_true")
parser.add_argument('--hz', type=int, default=0)
parser.add_argument('tests', nargs="*") parser.add_argument('tests', nargs="*")
args = parser.parse_args() args = parser.parse_args()
@ -2851,7 +2992,8 @@ if __name__ == "__main__":
instwords = args.instruction instwords = args.instruction
loopcount = 20 loopcount = 20
p, pc, per = t.speed_test_setup( p, pc, per = t.speed_test_setup(
instwords, loopcount=loopcount, mmu=mmu) instwords, loopcount=loopcount, usemmu=mmu, hz=args.hz)
number = 1000000 // (loopcount * per) number = 1000000 // (loopcount * per)
ta = timeit.repeat(stmt='t.speed_test_run(p, pc)', ta = timeit.repeat(stmt='t.speed_test_run(p, pc)',
number=number, globals=globals(), repeat=50) number=number, globals=globals(), repeat=50)
@ -2859,4 +3001,12 @@ if __name__ == "__main__":
ws = list(map(lambda w: f"{oct(w)[2:]:0>6s}", args.instruction)) ws = list(map(lambda w: f"{oct(w)[2:]:0>6s}", args.instruction))
print(f"Instruction {ws} took {tnsec} nsecs") print(f"Instruction {ws} took {tnsec} nsecs")
else: else:
unittest.main() argv = None
if args.clocktest:
argv = [parser.prog]
tests = [s for s in dir(TestMethods)
if s.startswith('clocktests_')]
argv = [parser.prog] + ['TestMethods.' + s for s in tests]
if tests:
print(f"Tests to run: {tests}")
unittest.main(argv=argv)