From 8904999e9d901a79d514dc39386daf7168e493c2 Mon Sep 17 00:00:00 2001 From: Luke Kenneth Casson Leighton Date: Sun, 28 Nov 2021 15:02:16 +0000 Subject: [PATCH] add a core-mgr *and* a pad-mgr to JTAG class so that an *entire* set of "resources" is connected inside the module... *outside* of the Platform. the Platform then only needs a straight port-to-port connection *NOT* "the Platform connects up JTAG Boundary Scan" --- src/spec/testing_stage1.py | 208 ++++++++++++++++++++++++------------- 1 file changed, 134 insertions(+), 74 deletions(-) diff --git a/src/spec/testing_stage1.py b/src/spec/testing_stage1.py index 4daa986..b4531b3 100644 --- a/src/spec/testing_stage1.py +++ b/src/spec/testing_stage1.py @@ -6,6 +6,8 @@ from nmigen import Elaboratable, Signal, Module, Instance from collections import OrderedDict from jtag import JTAG, resiotypes from copy import deepcopy +from nmigen.cli import rtlil +import sys # extra dependencies for jtag testing (?) from soc.bus.sram import SRAM @@ -123,7 +125,9 @@ def I2CResource(*args, scl, sda): class Blinker(Elaboratable): def __init__(self, pinset, resources): self.jtag = JTAG({}, "sync") + self.jtag.pad_mgr = ResourceManager([], []) self.jtag.core_mgr = ResourceManager([], []) + self.jtag.pad_mgr.add_resources(resources) self.jtag.core_mgr.add_resources(resources) # record resource lookup between core IO names and pads self.jtag.padlookup = {} @@ -132,15 +136,16 @@ class Blinker(Elaboratable): self.sram = SRAM(memory=memory, bus=self.jtag.wb) def elaborate(self, platform): - jtag_resources = self.jtag.core_mgr.resources + jtag_resources = self.jtag.pad_mgr.resources + core_resources = self.jtag.core_mgr.resources m = Module() m.submodules.jtag = self.jtag m.submodules.sram = self.sram count = Signal(5) m.d.sync += count.eq(count+1) - print ("resources", jtag_resources.items()) - gpio = self.jtag_request('gpio') + print ("resources", platform, jtag_resources.items()) + gpio = self.jtag_request(m, 'gpio') print (gpio, gpio.layout, gpio.fields) # get the GPIO bank, mess about with some of the pins m.d.comb += gpio.gpio0.io.o.eq(1) @@ -148,7 +153,7 @@ class Blinker(Elaboratable): m.d.comb += gpio.gpio1.io.oe.eq(count[4]) m.d.sync += count[0].eq(gpio.gpio1.io.i) # get the UART resource, mess with the output tx - uart = self.jtag_request('uart') + uart = self.jtag_request(m, 'uart') print (uart, uart.fields) intermediary = Signal() m.d.comb += uart.tx.eq(intermediary) @@ -169,22 +174,32 @@ class Blinker(Elaboratable): return m - def jtag_request(self, name, number=0, *, dir=None, xdr=None): + def ports(self): + return list(self) + + def __iter__(self): + yield self.jtag.bus.tdi + yield self.jtag.bus.tdo + yield self.jtag.bus.tck + yield self.jtag.bus.tms + + def jtag_request(self, m, name, number=0, *, dir=None, xdr=None): """request a Resource (e.g. name="uart", number=0) which will return a data structure containing Records of all the pins. this override will also - automatically - create a JTAG Boundary Scan connection *without* any change to the actual Platform.request() API """ + pad_mgr = self.jtag.pad_mgr core_mgr = self.jtag.core_mgr padlookup = self.jtag.padlookup # okaaaay, bit of shenanigens going on: the important data structure # here is Resourcemanager._ports. requests add to _ports, which is # what needs redirecting. therefore what has to happen is to # capture the number of ports *before* the request. sigh. - # start_ports = len(core_mgr._ports) - # value = core_mgr.request(name, number, dir=dir, xdr=xdr) - # end_ports = len(core_mgr._ports) + start_ports = len(core_mgr._ports) + value = core_mgr.request(name, number, dir=dir, xdr=xdr) + end_ports = len(core_mgr._ports) # take a copy of the requests made self.jtag.requests_made.append((name, number, dir, xdr)) @@ -192,47 +207,85 @@ class Blinker(Elaboratable): # now make a corresponding (duplicate) request to the pad manager # BUT, if it doesn't exist, don't sweat it: all it means is, the # application did not request Boundary Scan for that resource. - pad_start_ports = len(core_mgr._ports) - pvalue = core_mgr.request(name, number, dir=dir, xdr=xdr) - pad_end_ports = len(core_mgr._ports) + pad_start_ports = len(pad_mgr._ports) + pvalue = pad_mgr.request(name, number, dir=dir, xdr=xdr) + pad_end_ports = len(pad_mgr._ports) # ok now we have the lengths: now create a lookup between the pad # and the core, so that JTAG boundary scan can be inserted in between - #core = self._ports[start_ports:end_ports] - pads = core_mgr._ports[pad_start_ports:pad_end_ports] + core = core_mgr._ports[start_ports:end_ports] + pads = pad_mgr._ports[pad_start_ports:pad_end_ports] # oops if not the same numbers added. it's a duplicate. shouldn't happen - #assert len(core) == len(pads), "argh, resource manager error" - #print ("core", core) + assert len(core) == len(pads), "argh, resource manager error" + print ("core", core) print ("pads", pads) # pad/core each return a list of tuples of (res, pin, port, attrs) - #for pad, core in zip(pads, core): - for pad in pads: + for pad, core in zip(pads, core): # create a lookup on pin name to get at the hidden pad instance # this pin name will be handed to get_input, get_output etc. # and without the padlookup you can't find the (duplicate) pad. # note that self.padlookup and self.jtag.ios use the *exact* same # pin.name per pin - pin = pad[1] - #corepin = core[1] - if pin is None: continue # skip when pin is None - #assert corepin is not None # if pad was None, core should be too - print ("iter", pad, pin.name) + padpin = pad[1] + corepin = core[1] + if padpin is None: continue # skip when pin is None + assert corepin is not None # if pad was None, core should be too + print ("iter", pad, padpin.name) print ("existing pads", padlookup.keys()) - assert pin.name not in padlookup # no overwrites allowed! - #assert pin.name == corepin.name # has to be the same! - padlookup[pin.name] = pad # store pad by pin name + assert padpin.name not in padlookup # no overwrites allowed! + assert padpin.name == corepin.name # has to be the same! + padlookup[padpin.name] = (core, pad) # store pad by pin name # now add the IO Shift Register. first identify the type # then request a JTAG IOConn. we can't wire it up (yet) because # we don't have a Module() instance. doh. that comes in get_input # and get_output etc. etc. - iotype = resiotypes[pin.dir] # look up the C4M-JTAG IOType - io = self.jtag.add_io(iotype=iotype, name=pin.name) # create IOConn - self.jtag.ios[pin.name] = io # store IOConn Record by pin name - - # finally return the value just like ResourceManager.request() - return pvalue + iotype = resiotypes[padpin.dir] # look up the C4M-JTAG IOType + io = self.jtag.add_io(iotype=iotype, name=padpin.name) # IOConn + self.jtag.ios[padpin.name] = io # store IOConn Record by pin name + + # and connect up core to pads based on type + if padpin.dir == 'i': + print ("jtag_request add input pin", padpin) + print (" corepin", corepin) + print (" jtag io core", io.core) + print (" jtag io pad", io.pad) + # corepin is to be returned, here. so, connect jtag corein to it + m.d.comb += corepin.i.eq(io.core.i) + # and padpin to JTAG pad + m.d.comb += io.pad.i.eq(padpin.i) + elif padpin.dir == 'o': + print ("jtag_request add output pin", padpin) + print (" corepin", corepin) + print (" jtag io core", io.core) + print (" jtag io pad", io.pad) + # corepin is to be returned, here. connect it to jtag core out + m.d.comb += io.core.o.eq(corepin.o) + # and JTAG pad to padpin + m.d.comb += padpin.o.eq(io.pad.o) + elif padpin.dir == 'io': + print ("jtag_request add io pin", padpin) + print (" corepin", corepin) + print (" jtag io core", io.core) + print (" jtag io pad", io.pad) + # corepin is to be returned, here. so, connect jtag corein to it + m.d.comb += corepin.i.eq(io.core.i) + # and padpin to JTAG pad + m.d.comb += io.pad.i.eq(padpin.i) + # corepin is to be returned, here. connect it to jtag core out + m.d.comb += io.core.o.eq(corepin.o) + # and JTAG pad to padpin + m.d.comb += padpin.o.eq(io.pad.o) + # corepin is to be returned, here. connect it to jtag core out + m.d.comb += io.core.oe.eq(corepin.oe) + # and JTAG pad to padpin + m.d.comb += padpin.oe.eq(io.pad.oe) + + # finally return the *CORE* value just like ResourceManager.request() + # so that the module using this can connect to *CORE* i/o to the + # resource. pads are taken care of + return value ''' @@ -275,7 +328,7 @@ class ASICPlatform(TemplatedPlatform): super().__init__() # create set of pin resources based on the pinset, this is for the core - #jtag_resources = self.jtag.core_mgr.resources + #jtag_resources = self.jtag.pad_mgr.resources self.add_resources(resources) # add JTAG without scan @@ -288,7 +341,8 @@ class ASICPlatform(TemplatedPlatform): this override will also - automatically - create a JTAG Boundary Scan connection *without* any change to the actual Platform.request() API """ - core_mgr = self.jtag.core_mgr + pad_mgr = self.jtag.pad_mgr + pad_mgr = self.jtag.pad_mgr padlookup = self.jtag.padlookup # okaaaay, bit of shenanigens going on: the important data structure # here is Resourcemanager._ports. requests add to _ports, which is @@ -301,17 +355,17 @@ class ASICPlatform(TemplatedPlatform): # now make a corresponding (duplicate) request to the pad manager # BUT, if it doesn't exist, don't sweat it: all it means is, the # application did not request Boundary Scan for that resource. - pad_start_ports = len(core_mgr._ports) + pad_start_ports = len(pad_mgr._ports) try: - pvalue = core_mgr.request(name, number, dir=dir, xdr=xdr) + pvalue = pad_mgr.request(name, number, dir=dir, xdr=xdr) except ResourceError: return value - pad_end_ports = len(core_mgr._ports) + pad_end_ports = len(pad_mgr._ports) # ok now we have the lengths: now create a lookup between the pad # and the core, so that JTAG boundary scan can be inserted in between core = self._ports[start_ports:end_ports] - pads = core_mgr._ports[pad_start_ports:pad_end_ports] + pads = pad_mgr._ports[pad_start_ports:pad_end_ports] # oops if not the same numbers added. it's a duplicate. shouldn't happen assert len(core) == len(pads), "argh, resource manager error" print ("core", core) @@ -353,7 +407,7 @@ class ASICPlatform(TemplatedPlatform): return # make a *second* - identical - set of pin resources for the IO ring padres = deepcopy(resources) - self.jtag.core_mgr.add_resources(padres) + self.jtag.pad_mgr.add_resources(padres) #def iter_ports(self): # yield from super().iter_ports() @@ -508,8 +562,8 @@ class ASICPlatform(TemplatedPlatform): padport_oe = padport.io[2] # connect i - m.d.comb += pin.i[0].eq(io.core.i) - m.d.comb += padpin.i[0].eq(pin.i[0]) + m.d.comb += pin.i.eq(io.core.i) + m.d.comb += padpin.i.eq(pin.i) m.d.comb += padport_i.eq(self._invert_if(invert, port_i)) m.d.comb += io.pad.i.eq(padport_i) @@ -546,46 +600,52 @@ print(pinset) resources = create_resources(pinset) top = Blinker(pinset, resources) - -# XXX these modules are all being added *AFTER* the build process links -# everything together. the expectation that this would work is... unrealistic. -# ordering, clearly, is important. - -# dut = JTAG(test_pinset(), wb_data_wid=64, domain="sync") -top.jtag.stop = False -# rather than the client access the JTAG bus directly -# create an alternative that the client sets -class Dummy: pass -cdut = Dummy() -cdut.cbus = JTAGInterface() - -# set up client-server on port 44843-something -top.jtag.s = JTAGServer() -cdut.c = JTAGClient() -top.jtag.s.get_connection() -#else: -# print ("running server only as requested, use openocd remote to test") -# sys.stdout.flush() -# top.jtag.s.get_connection(None) # block waiting for connection - -# take copy of ir_width and scan_len -cdut._ir_width = top.jtag._ir_width -cdut.scan_len = top.jtag.scan_len - -p = ASICPlatform (resources, top.jtag) -p.build(top) -# this is what needs to gets treated as "top", after "main module" top -# is augmented with IO pads with JTAG tacked on. the expectation that -# the get_input() etc functions will be called magically by some other -# function is unrealistic. -top_fragment = p.fragment +vl = rtlil.convert(top, ports=top.ports()) +with open("test_jtag_blinker.il", "w") as f: + f.write(vl) + +sys.exit(0) + +if False: + # XXX these modules are all being added *AFTER* the build process links + # everything together. the expectation that this would work is... + # unrealistic. ordering, clearly, is important. + + # dut = JTAG(test_pinset(), wb_data_wid=64, domain="sync") + top.jtag.stop = False + # rather than the client access the JTAG bus directly + # create an alternative that the client sets + class Dummy: pass + cdut = Dummy() + cdut.cbus = JTAGInterface() + + # set up client-server on port 44843-something + top.jtag.s = JTAGServer() + cdut.c = JTAGClient() + top.jtag.s.get_connection() + #else: + # print ("running server only as requested, use openocd remote to test") + # sys.stdout.flush() + # top.jtag.s.get_connection(None) # block waiting for connection + + # take copy of ir_width and scan_len + cdut._ir_width = top.jtag._ir_width + cdut.scan_len = top.jtag.scan_len + + p = ASICPlatform (resources, top.jtag) + p.build(top) + # this is what needs to gets treated as "top", after "main module" top + # is augmented with IO pads with JTAG tacked on. the expectation that + # the get_input() etc functions will be called magically by some other + # function is unrealistic. + top_fragment = p.fragment # XXX simulating top (the module that does not itself contain IO pads # because that's covered by build) cannot possibly be expected to work # particularly when modules have been added *after* the platform build() # function has been called. -sim = Simulator(top_fragment) +sim = Simulator(top) sim.add_clock(1e-6, domain="sync") # standard clock sim.add_sync_process(wrap(jtag_srv(top))) #? jtag server -- 2.30.2