move ResourceManager into JTAG object, rename it to core_mgr
[pinmux.git] / src / spec / testing_stage1.py
1 #!/usr/bin/env python3
2 from nmigen.build.dsl import Resource, Subsignal, Pins
3 from nmigen.build.plat import TemplatedPlatform
4 from nmigen.build.res import ResourceManager, ResourceError
5 from nmigen import Elaboratable, Signal, Module, Instance
6 from collections import OrderedDict
7 from jtag import JTAG, resiotypes
8 from copy import deepcopy
9
10 # extra dependencies for jtag testing (?)
11 from soc.bus.sram import SRAM
12
13 from nmigen import Memory
14 from nmigen.sim import Simulator, Delay, Settle, Tick
15
16 from nmutil.util import wrap
17
18 from soc.debug.jtagutils import (jtag_read_write_reg,
19 jtag_srv, jtag_set_reset,
20 jtag_set_ir, jtag_set_get_dr)
21
22 from c4m.nmigen.jtag.tap import TAP, IOType
23 from c4m.nmigen.jtag.bus import Interface as JTAGInterface
24 from soc.debug.dmi import DMIInterface, DBGCore
25 from soc.debug.test.dmi_sim import dmi_sim
26 from soc.debug.test.jtagremote import JTAGServer, JTAGClient
27 from nmigen.build.res import ResourceError
28
29 # Was thinking of using these functions, but skipped for simplicity for now
30 # XXX nope. the output from JSON file.
31 #from pinfunctions import (i2s, lpc, emmc, sdmmc, mspi, mquadspi, spi,
32 # quadspi, i2c, mi2c, jtag, uart, uartfull, rgbttl, ulpi, rgmii, flexbus1,
33 # flexbus2, sdram1, sdram2, sdram3, vss, vdd, sys, eint, pwm, gpio)
34
35 # File for stage 1 pinmux tested proposed by Luke,
36 # https://bugs.libre-soc.org/show_bug.cgi?id=50#c10
37
38
39 def dummy_pinset():
40 # sigh this needs to come from pinmux.
41 gpios = []
42 for i in range(4):
43 gpios.append("%d*" % i)
44 return {'uart': ['tx+', 'rx-'],
45 'gpio': gpios,
46 #'jtag': ['tms-', 'tdi-', 'tdo+', 'tck+'],
47 'i2c': ['sda*', 'scl+']}
48
49 """
50 a function is needed which turns the results of dummy_pinset()
51 into:
52
53 [UARTResource("uart", 0, tx=..., rx=..),
54 I2CResource("i2c", 0, scl=..., sda=...),
55 Resource("gpio", 0, Subsignal("i"...), Subsignal("o"...)
56 Resource("gpio", 1, Subsignal("i"...), Subsignal("o"...)
57 ...
58 ]
59 """
60
61
62 def create_resources(pinset):
63 resources = []
64 for periph, pins in pinset.items():
65 print(periph, pins)
66 if periph == 'i2c':
67 #print("I2C required!")
68 resources.append(I2CResource('i2c', 0, sda='sda', scl='scl'))
69 elif periph == 'uart':
70 #print("UART required!")
71 resources.append(UARTResource('uart', 0, tx='tx', rx='rx'))
72 elif periph == 'gpio':
73 #print("GPIO required!")
74 print ("GPIO is defined as '*' type, meaning i, o and oe needed")
75 ios = []
76 for pin in pins:
77 pname = "gpio"+pin[:-1] # strip "*" on end
78 # urrrr... tristsate and io assume a single pin which is
79 # of course exactly what we don't want in an ASIC: we want
80 # *all three* pins but the damn port is not outputted
81 # as a triplet, it's a single Record named "io". sigh.
82 # therefore the only way to get a triplet of i/o/oe
83 # is to *actually* create explicit triple pins
84 pad = Subsignal("io",
85 Pins("%s_i %s_o %s_oe" % (pname, pname, pname),
86 dir="io", assert_width=3))
87 ios.append(Resource(pname, 0, pad))
88 resources.append(Resource.family(periph, 0, default_name="gpio",
89 ios=ios))
90
91 # add clock and reset
92 clk = Resource("clk", 0, Pins("sys_clk", dir="i"))
93 rst = Resource("rst", 0, Pins("sys_rst", dir="i"))
94 resources.append(clk)
95 resources.append(rst)
96 return resources
97
98
99 def JTAGResource(*args):
100 io = []
101 io.append(Subsignal("tms", Pins("tms", dir="i", assert_width=1)))
102 io.append(Subsignal("tdi", Pins("tdi", dir="i", assert_width=1)))
103 io.append(Subsignal("tck", Pins("tck", dir="i", assert_width=1)))
104 io.append(Subsignal("tdo", Pins("tdo", dir="o", assert_width=1)))
105 return Resource.family(*args, default_name="jtag", ios=io)
106
107 def UARTResource(*args, rx, tx):
108 io = []
109 io.append(Subsignal("rx", Pins(rx, dir="i", assert_width=1)))
110 io.append(Subsignal("tx", Pins(tx, dir="o", assert_width=1)))
111 return Resource.family(*args, default_name="uart", ios=io)
112
113
114 def I2CResource(*args, scl, sda):
115 io = []
116 io.append(Subsignal("scl", Pins(scl, dir="io", assert_width=1)))
117 io.append(Subsignal("sda", Pins(sda, dir="io", assert_width=1)))
118 return Resource.family(*args, default_name="i2c", ios=io)
119
120
121 # ridiculously-simple top-level module. doesn't even have a sync domain
122 # and can't have one until a clock has been established by ASICPlatform.
123 class Blinker(Elaboratable):
124 def __init__(self, pinset):
125 self.jtag = JTAG({}, "sync")
126 self.jtag.core_mgr = ResourceManager([], [])
127 memory = Memory(width=32, depth=16)
128 self.sram = SRAM(memory=memory, bus=self.jtag.wb)
129
130 def elaborate(self, platform):
131 m = Module()
132 m.submodules.jtag = self.jtag
133 m.submodules.sram = self.sram
134
135 count = Signal(5)
136 m.d.sync += count.eq(5)
137 print ("resources", platform.resources.items())
138 gpio = platform.request('gpio')
139 print (gpio, gpio.layout, gpio.fields)
140 # get the GPIO bank, mess about with some of the pins
141 m.d.comb += gpio.gpio0.io.o.eq(1)
142 m.d.comb += gpio.gpio1.io.o.eq(gpio.gpio2.io.i)
143 m.d.comb += gpio.gpio1.io.oe.eq(count[4])
144 m.d.sync += count[0].eq(gpio.gpio1.io.i)
145 # get the UART resource, mess with the output tx
146 uart = platform.request('uart')
147 print (uart, uart.fields)
148 intermediary = Signal()
149 m.d.comb += uart.tx.eq(intermediary)
150 m.d.comb += intermediary.eq(uart.rx)
151
152 # wire up JTAG otherwise we are in trouble (no clock)
153 jtag = platform.request('jtag')
154 m.d.comb += self.jtag.bus.tdi.eq(jtag.tdi)
155 m.d.comb += self.jtag.bus.tck.eq(jtag.tck)
156 m.d.comb += self.jtag.bus.tms.eq(jtag.tms)
157 m.d.comb += jtag.tdo.eq(self.jtag.bus.tdo)
158
159 return m
160
161
162 '''
163 _trellis_command_templates = [
164 r"""
165 {{invoke_tool("yosys")}}
166 {{quiet("-q")}}
167 {{get_override("yosys_opts")|options}}
168 -l {{name}}.rpt
169 {{name}}.ys
170 """,
171 ]
172 '''
173
174 # sigh, have to create a dummy platform for now.
175 # TODO: investigate how the heck to get it to output ilang. or verilog.
176 # or, anything, really. but at least it doesn't barf
177 class ASICPlatform(TemplatedPlatform):
178 connectors = []
179 resources = OrderedDict()
180 required_tools = []
181 command_templates = ['/bin/true'] # no command needed: stops barfing
182 file_templates = {
183 **TemplatedPlatform.build_script_templates,
184 "{{name}}.il": r"""
185 # {{autogenerated}}
186 {{emit_rtlil()}}
187 """,
188 "{{name}}.debug.v": r"""
189 /* {{autogenerated}} */
190 {{emit_debug_verilog()}}
191 """,
192 }
193 toolchain = None
194 default_clk = "clk" # should be picked up / overridden by platform sys.clk
195 default_rst = "rst" # should be picked up / overridden by platform sys.rst
196
197 def __init__(self, resources, jtag):
198 self.jtag = jtag
199 super().__init__()
200
201 # create set of pin resources based on the pinset, this is for the core
202 self.add_resources(resources)
203 # record resource lookup between core IO names and pads
204 self.padlookup = {}
205
206 # add JTAG without scan
207 self.add_resources([JTAGResource('jtag', 0)], no_boundary_scan=True)
208
209 def request(self, name, number=0, *, dir=None, xdr=None):
210 """request a Resource (e.g. name="uart", number=0) which will
211 return a data structure containing Records of all the pins.
212
213 this override will also - automatically - create a JTAG Boundary Scan
214 connection *without* any change to the actual Platform.request() API
215 """
216 core_mgr = self.jtag.core_mgr
217 # okaaaay, bit of shenanigens going on: the important data structure
218 # here is Resourcemanager._ports. requests add to _ports, which is
219 # what needs redirecting. therefore what has to happen is to
220 # capture the number of ports *before* the request. sigh.
221 start_ports = len(self._ports)
222 value = super().request(name, number, dir=dir, xdr=xdr)
223 end_ports = len(self._ports)
224
225 # now make a corresponding (duplicate) request to the pad manager
226 # BUT, if it doesn't exist, don't sweat it: all it means is, the
227 # application did not request Boundary Scan for that resource.
228 pad_start_ports = len(core_mgr._ports)
229 try:
230 pvalue = core_mgr.request(name, number, dir=dir, xdr=xdr)
231 except ResourceError:
232 return value
233 pad_end_ports = len(core_mgr._ports)
234
235 # ok now we have the lengths: now create a lookup between the pad
236 # and the core, so that JTAG boundary scan can be inserted in between
237 core = self._ports[start_ports:end_ports]
238 pads = core_mgr._ports[pad_start_ports:pad_end_ports]
239 # oops if not the same numbers added. it's a duplicate. shouldn't happen
240 assert len(core) == len(pads), "argh, resource manager error"
241 print ("core", core)
242 print ("pads", pads)
243
244 # pad/core each return a list of tuples of (res, pin, port, attrs)
245 for pad, core in zip(pads, core):
246 # create a lookup on pin name to get at the hidden pad instance
247 # this pin name will be handed to get_input, get_output etc.
248 # and without the padlookup you can't find the (duplicate) pad.
249 # note that self.padlookup and self.jtag.ios use the *exact* same
250 # pin.name per pin
251 pin = pad[1]
252 corepin = core[1]
253 if pin is None: continue # skip when pin is None
254 assert corepin is not None # if pad was None, core should be too
255 print ("iter", pad, pin.name)
256 print ("existing pads", self.padlookup.keys())
257 assert pin.name not in self.padlookup # no overwrites allowed!
258 assert pin.name == corepin.name # has to be the same!
259 self.padlookup[pin.name] = pad # store pad by pin name
260
261 # now add the IO Shift Register. first identify the type
262 # then request a JTAG IOConn. we can't wire it up (yet) because
263 # we don't have a Module() instance. doh. that comes in get_input
264 # and get_output etc. etc.
265 iotype = resiotypes[pin.dir] # look up the C4M-JTAG IOType
266 io = self.jtag.add_io(iotype=iotype, name=pin.name) # create IOConn
267 self.jtag.ios[pin.name] = io # store IOConn Record by pin name
268
269 # finally return the value just like ResourceManager.request()
270 return value
271
272 def add_resources(self, resources, no_boundary_scan=False):
273 super().add_resources(resources)
274 if no_boundary_scan:
275 return
276 # make a *second* - identical - set of pin resources for the IO ring
277 padres = deepcopy(resources)
278 self.jtag.core_mgr.add_resources(padres)
279
280 #def iter_ports(self):
281 # yield from super().iter_ports()
282 # for io in self.jtag.ios.values():
283 # print ("iter ports", io.layout, io)
284 # for field in io.core.fields:
285 # yield getattr(io.core, field)
286 # for field in io.pad.fields:
287 # yield getattr(io.pad, field)
288
289 # XXX these aren't strictly necessary right now but the next
290 # phase is to add JTAG Boundary Scan so it maaay be worth adding?
291 # at least for the print statements
292 def get_input(self, pin, port, attrs, invert):
293 self._check_feature("single-ended input", pin, attrs,
294 valid_xdrs=(0,), valid_attrs=None)
295
296 m = Module()
297 print (" get_input", pin, "port", port, port.layout)
298 if pin.name in ['clk_0', 'rst_0']: # sigh
299 # simple pass-through from port to pin
300 print("No JTAG chain in-between")
301 m.d.comb += pin.i.eq(self._invert_if(invert, port))
302 return m
303 if pin.name not in self.padlookup:
304 print("No pin named %s, not connecting to JTAG BS" % pin.name)
305 m.d.comb += pin.i.eq(self._invert_if(invert, port))
306 return m
307 (padres, padpin, padport, padattrs) = self.padlookup[pin.name]
308 io = self.jtag.ios[pin.name]
309 print (" pad", padres, padpin, padport, attrs)
310 print (" padpin", padpin.layout)
311 print (" jtag", io.core.layout, io.pad.layout)
312 m.d.comb += pin.i.eq(self._invert_if(invert, port))
313 m.d.comb += padpin.i.eq(padport)
314 m.d.comb += padport.io.eq(io.core.i)
315 m.d.comb += io.pad.i.eq(pin.i)
316
317 print("+=+=+= pin: ", pin)
318 print("+=+=+= port: ", port.layout)
319 print("+=+=+= pad pin: ", padpin)
320 print("+=+=+= pad port: ", padport)
321 return m
322
323 def get_output(self, pin, port, attrs, invert):
324 self._check_feature("single-ended output", pin, attrs,
325 valid_xdrs=(0,), valid_attrs=None)
326
327 m = Module()
328 print (" get_output", pin, "port", port, port.layout)
329 if pin.name in ['clk_0', 'rst_0']: # sigh
330 # simple pass-through from pin to port
331 print("No JTAG chain in-between")
332 m.d.comb += port.eq(self._invert_if(invert, pin.o))
333 return m
334 if pin.name not in self.padlookup:
335 print("No pin named %s, not connecting to JTAG BS" % pin.name)
336 m.d.comb += port.eq(self._invert_if(invert, pin.o))
337 return m
338 (padres, padpin, padport, padattrs) = self.padlookup[pin.name]
339 io = self.jtag.ios[pin.name]
340 print (" pad", padres, padpin, padport, padattrs)
341 print (" pin", padpin.layout)
342 print (" jtag", io.core.layout, io.pad.layout)
343 m.d.comb += port.eq(self._invert_if(invert, pin.o))
344 m.d.comb += padport.io.eq(padpin.o)
345 m.d.comb += io.core.o.eq(port.io)
346 m.d.comb += padpin.o.eq(io.pad.o)
347 return m
348
349 def get_tristate(self, pin, port, attrs, invert):
350 self._check_feature("single-ended tristate", pin, attrs,
351 valid_xdrs=(0,), valid_attrs=None)
352
353 print (" get_tristate", pin, "port", port, port.layout)
354 m = Module()
355 if pin.name in ['clk_0', 'rst_0']: # sigh
356 print("No JTAG chain in-between")
357 m.submodules += Instance("$tribuf",
358 p_WIDTH=pin.width,
359 i_EN=pin.oe,
360 i_A=self._invert_if(invert, pin.o),
361 o_Y=port,
362 )
363 return m
364 (res, pin, port, attrs) = self.padlookup[pin.name]
365 io = self.jtag.ios[pin.name]
366 print (" pad", res, pin, port, attrs)
367 print (" pin", pin.layout)
368 print (" jtag", io.core.layout, io.pad.layout)
369 #m.submodules += Instance("$tribuf",
370 # p_WIDTH=pin.width,
371 # i_EN=io.pad.oe,
372 # i_A=self._invert_if(invert, io.pad.o),
373 # o_Y=port,
374 #)
375 m.d.comb += io.core.o.eq(pin.o)
376 m.d.comb += io.core.oe.eq(pin.oe)
377 m.d.comb += pin.i.eq(io.core.i)
378 m.d.comb += io.pad.i.eq(port.i)
379 m.d.comb += port.o.eq(io.pad.o)
380 m.d.comb += port.oe.eq(io.pad.oe)
381 return m
382
383 def get_input_output(self, pin, port, attrs, invert):
384 self._check_feature("single-ended input/output", pin, attrs,
385 valid_xdrs=(0,), valid_attrs=None)
386
387 print (" get_input_output", pin, "port", port, port.layout)
388 m = Module()
389 if pin.name in ['clk_0', 'rst_0']: # sigh
390 print("No JTAG chain in-between")
391 m.submodules += Instance("$tribuf",
392 p_WIDTH=pin.width,
393 i_EN=pin.oe,
394 i_A=self._invert_if(invert, pin.o),
395 o_Y=port,
396 )
397 m.d.comb += pin.i.eq(self._invert_if(invert, port))
398 return m
399 (padres, padpin, padport, padattrs) = self.padlookup[pin.name]
400 io = self.jtag.ios[pin.name]
401 print (" pad", padres, padpin, padport, padattrs)
402 print (" pin", padpin.layout)
403 print (" port layout", port.layout)
404 print (" jtag", io.core.layout, io.pad.layout)
405 #m.submodules += Instance("$tribuf",
406 # p_WIDTH=pin.width,
407 # i_EN=io.pad.oe,
408 # i_A=self._invert_if(invert, io.pad.o),
409 # o_Y=port,
410 #)
411 # Create aliases for the port sub-signals
412 port_i = port.io[0]
413 port_o = port.io[1]
414 port_oe = port.io[2]
415
416 padport_i = padport.io[0]
417 padport_o = padport.io[1]
418 padport_oe = padport.io[2]
419
420 # Connect SoC pins to SoC port
421 m.d.comb += pin.i.eq(port_i)
422 m.d.comb += port_o.eq(pin.o)
423 m.d.comb += port_oe.eq(pin.oe)
424 # Connect SoC port to JTAG io.core side
425 m.d.comb += port_i.eq(io.core.i)
426 m.d.comb += io.core.o.eq(port_o)
427 m.d.comb += io.core.oe.eq(port_oe)
428 # Connect JTAG io.pad side to pad port
429 m.d.comb += io.pad.i.eq(padport_i)
430 m.d.comb += padport_o.eq(io.pad.o)
431 m.d.comb += padport_oe.eq(io.pad.oe)
432 # Connect pad port to pad pins
433 m.d.comb += padport_i.eq(padpin.i)
434 m.d.comb += padpin.o.eq(padport_o)
435 m.d.comb += padpin.oe.eq(padport_oe)
436 return m
437
438 def toolchain_prepare(self, fragment, name, **kwargs):
439 """override toolchain_prepare in order to grab the fragment
440 """
441 self.fragment = fragment
442 return super().toolchain_prepare(fragment, name, **kwargs)
443
444 """
445 and to create a Platform instance with that list, and build
446 something random
447
448 p=Platform()
449 p.resources=listofstuff
450 p.build(Blinker())
451 """
452 pinset = dummy_pinset()
453 top = Blinker(pinset)
454
455
456 # XXX these modules are all being added *AFTER* the build process links
457 # everything together. the expectation that this would work is... unrealistic.
458 # ordering, clearly, is important.
459
460 # dut = JTAG(test_pinset(), wb_data_wid=64, domain="sync")
461 top.jtag.stop = False
462 # rather than the client access the JTAG bus directly
463 # create an alternative that the client sets
464 class Dummy: pass
465 cdut = Dummy()
466 cdut.cbus = JTAGInterface()
467
468 # set up client-server on port 44843-something
469 top.jtag.s = JTAGServer()
470 cdut.c = JTAGClient()
471 top.jtag.s.get_connection()
472 #else:
473 # print ("running server only as requested, use openocd remote to test")
474 # sys.stdout.flush()
475 # top.jtag.s.get_connection(None) # block waiting for connection
476
477 # take copy of ir_width and scan_len
478 cdut._ir_width = top.jtag._ir_width
479 cdut.scan_len = top.jtag.scan_len
480
481 print(pinset)
482 resources = create_resources(pinset)
483 p = ASICPlatform (resources, top.jtag)
484 p.build(top)
485 # this is what needs to gets treated as "top", after "main module" top
486 # is augmented with IO pads with JTAG tacked on. the expectation that
487 # the get_input() etc functions will be called magically by some other
488 # function is unrealistic.
489 top_fragment = p.fragment
490
491 # XXX simulating top (the module that does not itself contain IO pads
492 # because that's covered by build) cannot possibly be expected to work
493 # particularly when modules have been added *after* the platform build()
494 # function has been called.
495
496 sim = Simulator(top_fragment)
497 sim.add_clock(1e-6, domain="sync") # standard clock
498
499 sim.add_sync_process(wrap(jtag_srv(top))) #? jtag server
500 #if len(sys.argv) != 2 or sys.argv[1] != 'server':
501 sim.add_sync_process(wrap(jtag_sim(cdut, top.jtag))) # actual jtag tester
502 sim.add_sync_process(wrap(dmi_sim(top.jtag))) # handles (pretends to be) DMI
503
504 with sim.write_vcd("dmi2jtag_test_srv.vcd"):
505 sim.run()