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