move padlookup into JTAG class (hacked at the moment)
[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 # record resource lookup between core IO names and pads
128 self.jtag.padlookup = {}
129 memory = Memory(width=32, depth=16)
130 self.sram = SRAM(memory=memory, bus=self.jtag.wb)
131
132
133 def elaborate(self, platform):
134 m = Module()
135 m.submodules.jtag = self.jtag
136 m.submodules.sram = self.sram
137
138 count = Signal(5)
139 m.d.sync += count.eq(5)
140 print ("resources", platform.resources.items())
141 gpio = platform.request('gpio')
142 print (gpio, gpio.layout, gpio.fields)
143 # get the GPIO bank, mess about with some of the pins
144 m.d.comb += gpio.gpio0.io.o.eq(1)
145 m.d.comb += gpio.gpio1.io.o.eq(gpio.gpio2.io.i)
146 m.d.comb += gpio.gpio1.io.oe.eq(count[4])
147 m.d.sync += count[0].eq(gpio.gpio1.io.i)
148 # get the UART resource, mess with the output tx
149 uart = platform.request('uart')
150 print (uart, uart.fields)
151 intermediary = Signal()
152 m.d.comb += uart.tx.eq(intermediary)
153 m.d.comb += intermediary.eq(uart.rx)
154
155 # wire up JTAG otherwise we are in trouble (no clock)
156 jtag = platform.request('jtag')
157 m.d.comb += self.jtag.bus.tdi.eq(jtag.tdi)
158 m.d.comb += self.jtag.bus.tck.eq(jtag.tck)
159 m.d.comb += self.jtag.bus.tms.eq(jtag.tms)
160 m.d.comb += jtag.tdo.eq(self.jtag.bus.tdo)
161
162 return m
163
164
165 '''
166 _trellis_command_templates = [
167 r"""
168 {{invoke_tool("yosys")}}
169 {{quiet("-q")}}
170 {{get_override("yosys_opts")|options}}
171 -l {{name}}.rpt
172 {{name}}.ys
173 """,
174 ]
175 '''
176
177 # sigh, have to create a dummy platform for now.
178 # TODO: investigate how the heck to get it to output ilang. or verilog.
179 # or, anything, really. but at least it doesn't barf
180 class ASICPlatform(TemplatedPlatform):
181 connectors = []
182 resources = OrderedDict()
183 required_tools = []
184 command_templates = ['/bin/true'] # no command needed: stops barfing
185 file_templates = {
186 **TemplatedPlatform.build_script_templates,
187 "{{name}}.il": r"""
188 # {{autogenerated}}
189 {{emit_rtlil()}}
190 """,
191 "{{name}}.debug.v": r"""
192 /* {{autogenerated}} */
193 {{emit_debug_verilog()}}
194 """,
195 }
196 toolchain = None
197 default_clk = "clk" # should be picked up / overridden by platform sys.clk
198 default_rst = "rst" # should be picked up / overridden by platform sys.rst
199
200 def __init__(self, resources, jtag):
201 self.jtag = jtag
202 super().__init__()
203
204 # create set of pin resources based on the pinset, this is for the core
205 self.add_resources(resources)
206
207 # add JTAG without scan
208 self.add_resources([JTAGResource('jtag', 0)], no_boundary_scan=True)
209
210 def request(self, name, number=0, *, dir=None, xdr=None):
211 """request a Resource (e.g. name="uart", number=0) which will
212 return a data structure containing Records of all the pins.
213
214 this override will also - automatically - create a JTAG Boundary Scan
215 connection *without* any change to the actual Platform.request() API
216 """
217 core_mgr = self.jtag.core_mgr
218 padlookup = self.jtag.padlookup
219 # okaaaay, bit of shenanigens going on: the important data structure
220 # here is Resourcemanager._ports. requests add to _ports, which is
221 # what needs redirecting. therefore what has to happen is to
222 # capture the number of ports *before* the request. sigh.
223 start_ports = len(self._ports)
224 value = super().request(name, number, dir=dir, xdr=xdr)
225 end_ports = len(self._ports)
226
227 # now make a corresponding (duplicate) request to the pad manager
228 # BUT, if it doesn't exist, don't sweat it: all it means is, the
229 # application did not request Boundary Scan for that resource.
230 pad_start_ports = len(core_mgr._ports)
231 try:
232 pvalue = core_mgr.request(name, number, dir=dir, xdr=xdr)
233 except ResourceError:
234 return value
235 pad_end_ports = len(core_mgr._ports)
236
237 # ok now we have the lengths: now create a lookup between the pad
238 # and the core, so that JTAG boundary scan can be inserted in between
239 core = self._ports[start_ports:end_ports]
240 pads = core_mgr._ports[pad_start_ports:pad_end_ports]
241 # oops if not the same numbers added. it's a duplicate. shouldn't happen
242 assert len(core) == len(pads), "argh, resource manager error"
243 print ("core", core)
244 print ("pads", pads)
245
246 # pad/core each return a list of tuples of (res, pin, port, attrs)
247 for pad, core in zip(pads, core):
248 # create a lookup on pin name to get at the hidden pad instance
249 # this pin name will be handed to get_input, get_output etc.
250 # and without the padlookup you can't find the (duplicate) pad.
251 # note that self.padlookup and self.jtag.ios use the *exact* same
252 # pin.name per pin
253 pin = pad[1]
254 corepin = core[1]
255 if pin is None: continue # skip when pin is None
256 assert corepin is not None # if pad was None, core should be too
257 print ("iter", pad, pin.name)
258 print ("existing pads", padlookup.keys())
259 assert pin.name not in padlookup # no overwrites allowed!
260 assert pin.name == corepin.name # has to be the same!
261 padlookup[pin.name] = pad # store pad by pin name
262
263 # now add the IO Shift Register. first identify the type
264 # then request a JTAG IOConn. we can't wire it up (yet) because
265 # we don't have a Module() instance. doh. that comes in get_input
266 # and get_output etc. etc.
267 iotype = resiotypes[pin.dir] # look up the C4M-JTAG IOType
268 io = self.jtag.add_io(iotype=iotype, name=pin.name) # create IOConn
269 self.jtag.ios[pin.name] = io # store IOConn Record by pin name
270
271 # finally return the value just like ResourceManager.request()
272 return value
273
274 def add_resources(self, resources, no_boundary_scan=False):
275 super().add_resources(resources)
276 if no_boundary_scan:
277 return
278 # make a *second* - identical - set of pin resources for the IO ring
279 padres = deepcopy(resources)
280 self.jtag.core_mgr.add_resources(padres)
281
282 #def iter_ports(self):
283 # yield from super().iter_ports()
284 # for io in self.jtag.ios.values():
285 # print ("iter ports", io.layout, io)
286 # for field in io.core.fields:
287 # yield getattr(io.core, field)
288 # for field in io.pad.fields:
289 # yield getattr(io.pad, field)
290
291 # XXX these aren't strictly necessary right now but the next
292 # phase is to add JTAG Boundary Scan so it maaay be worth adding?
293 # at least for the print statements
294 def get_input(self, pin, port, attrs, invert):
295 padlookup = self.jtag.padlookup
296 self._check_feature("single-ended input", pin, attrs,
297 valid_xdrs=(0,), valid_attrs=None)
298
299 m = Module()
300 print (" get_input", pin, "port", port, port.layout)
301 if pin.name in ['clk_0', 'rst_0']: # sigh
302 # simple pass-through from port to pin
303 print("No JTAG chain in-between")
304 m.d.comb += pin.i.eq(self._invert_if(invert, port))
305 return m
306 if pin.name not in padlookup:
307 print("No pin named %s, not connecting to JTAG BS" % pin.name)
308 m.d.comb += pin.i.eq(self._invert_if(invert, port))
309 return m
310 (padres, padpin, padport, padattrs) = padlookup[pin.name]
311 io = self.jtag.ios[pin.name]
312 print (" pad", padres, padpin, padport, attrs)
313 print (" padpin", padpin.layout)
314 print (" jtag", io.core.layout, io.pad.layout)
315 m.d.comb += pin.i.eq(self._invert_if(invert, port))
316 m.d.comb += padpin.i.eq(padport)
317 m.d.comb += padport.io.eq(io.core.i)
318 m.d.comb += io.pad.i.eq(pin.i)
319
320 print("+=+=+= pin: ", pin)
321 print("+=+=+= port: ", port.layout)
322 print("+=+=+= pad pin: ", padpin)
323 print("+=+=+= pad port: ", padport)
324 return m
325
326 def get_output(self, pin, port, attrs, invert):
327 padlookup = self.jtag.padlookup
328 self._check_feature("single-ended output", pin, attrs,
329 valid_xdrs=(0,), valid_attrs=None)
330
331 m = Module()
332 print (" get_output", pin, "port", port, port.layout)
333 if pin.name in ['clk_0', 'rst_0']: # sigh
334 # simple pass-through from pin to port
335 print("No JTAG chain in-between")
336 m.d.comb += port.eq(self._invert_if(invert, pin.o))
337 return m
338 if pin.name not in padlookup:
339 print("No pin named %s, not connecting to JTAG BS" % pin.name)
340 m.d.comb += port.eq(self._invert_if(invert, pin.o))
341 return m
342 (padres, padpin, padport, padattrs) = padlookup[pin.name]
343 io = self.jtag.ios[pin.name]
344 print (" pad", padres, padpin, padport, padattrs)
345 print (" pin", padpin.layout)
346 print (" jtag", io.core.layout, io.pad.layout)
347 m.d.comb += port.eq(self._invert_if(invert, pin.o))
348 m.d.comb += padport.io.eq(padpin.o)
349 m.d.comb += io.core.o.eq(port.io)
350 m.d.comb += padpin.o.eq(io.pad.o)
351 return m
352
353 def get_tristate(self, pin, port, attrs, invert):
354 padlookup = self.jtag.padlookup
355 self._check_feature("single-ended tristate", pin, attrs,
356 valid_xdrs=(0,), valid_attrs=None)
357
358 print (" get_tristate", pin, "port", port, port.layout)
359 m = Module()
360 if pin.name in ['clk_0', 'rst_0']: # sigh
361 print("No JTAG chain in-between")
362 m.submodules += Instance("$tribuf",
363 p_WIDTH=pin.width,
364 i_EN=pin.oe,
365 i_A=self._invert_if(invert, pin.o),
366 o_Y=port,
367 )
368 return m
369 (res, pin, port, attrs) = padlookup[pin.name]
370 io = self.jtag.ios[pin.name]
371 print (" pad", res, pin, port, attrs)
372 print (" pin", pin.layout)
373 print (" jtag", io.core.layout, io.pad.layout)
374 #m.submodules += Instance("$tribuf",
375 # p_WIDTH=pin.width,
376 # i_EN=io.pad.oe,
377 # i_A=self._invert_if(invert, io.pad.o),
378 # o_Y=port,
379 #)
380 m.d.comb += io.core.o.eq(pin.o)
381 m.d.comb += io.core.oe.eq(pin.oe)
382 m.d.comb += pin.i.eq(io.core.i)
383 m.d.comb += io.pad.i.eq(port.i)
384 m.d.comb += port.o.eq(io.pad.o)
385 m.d.comb += port.oe.eq(io.pad.oe)
386 return m
387
388 def get_input_output(self, pin, port, attrs, invert):
389 padlookup = self.jtag.padlookup
390 self._check_feature("single-ended input/output", pin, attrs,
391 valid_xdrs=(0,), valid_attrs=None)
392
393 print (" get_input_output", pin, "port", port, port.layout)
394 m = Module()
395 if pin.name in ['clk_0', 'rst_0']: # sigh
396 print("No JTAG chain in-between")
397 m.submodules += Instance("$tribuf",
398 p_WIDTH=pin.width,
399 i_EN=pin.oe,
400 i_A=self._invert_if(invert, pin.o),
401 o_Y=port,
402 )
403 m.d.comb += pin.i.eq(self._invert_if(invert, port))
404 return m
405 (padres, padpin, padport, padattrs) = padlookup[pin.name]
406 io = self.jtag.ios[pin.name]
407 print (" pad", padres, padpin, padport, padattrs)
408 print (" pin", padpin.layout)
409 print (" port layout", port.layout)
410 print (" jtag", io.core.layout, io.pad.layout)
411 #m.submodules += Instance("$tribuf",
412 # p_WIDTH=pin.width,
413 # i_EN=io.pad.oe,
414 # i_A=self._invert_if(invert, io.pad.o),
415 # o_Y=port,
416 #)
417 # Create aliases for the port sub-signals
418 port_i = port.io[0]
419 port_o = port.io[1]
420 port_oe = port.io[2]
421
422 padport_i = padport.io[0]
423 padport_o = padport.io[1]
424 padport_oe = padport.io[2]
425
426 # Connect SoC pins to SoC port
427 m.d.comb += pin.i.eq(port_i)
428 m.d.comb += port_o.eq(pin.o)
429 m.d.comb += port_oe.eq(pin.oe)
430 # Connect SoC port to JTAG io.core side
431 m.d.comb += port_i.eq(io.core.i)
432 m.d.comb += io.core.o.eq(port_o)
433 m.d.comb += io.core.oe.eq(port_oe)
434 # Connect JTAG io.pad side to pad port
435 m.d.comb += io.pad.i.eq(padport_i)
436 m.d.comb += padport_o.eq(io.pad.o)
437 m.d.comb += padport_oe.eq(io.pad.oe)
438 # Connect pad port to pad pins
439 m.d.comb += padport_i.eq(padpin.i)
440 m.d.comb += padpin.o.eq(padport_o)
441 m.d.comb += padpin.oe.eq(padport_oe)
442 return m
443
444 def toolchain_prepare(self, fragment, name, **kwargs):
445 """override toolchain_prepare in order to grab the fragment
446 """
447 self.fragment = fragment
448 return super().toolchain_prepare(fragment, name, **kwargs)
449
450 """
451 and to create a Platform instance with that list, and build
452 something random
453
454 p=Platform()
455 p.resources=listofstuff
456 p.build(Blinker())
457 """
458 pinset = dummy_pinset()
459 top = Blinker(pinset)
460
461
462 # XXX these modules are all being added *AFTER* the build process links
463 # everything together. the expectation that this would work is... unrealistic.
464 # ordering, clearly, is important.
465
466 # dut = JTAG(test_pinset(), wb_data_wid=64, domain="sync")
467 top.jtag.stop = False
468 # rather than the client access the JTAG bus directly
469 # create an alternative that the client sets
470 class Dummy: pass
471 cdut = Dummy()
472 cdut.cbus = JTAGInterface()
473
474 # set up client-server on port 44843-something
475 top.jtag.s = JTAGServer()
476 cdut.c = JTAGClient()
477 top.jtag.s.get_connection()
478 #else:
479 # print ("running server only as requested, use openocd remote to test")
480 # sys.stdout.flush()
481 # top.jtag.s.get_connection(None) # block waiting for connection
482
483 # take copy of ir_width and scan_len
484 cdut._ir_width = top.jtag._ir_width
485 cdut.scan_len = top.jtag.scan_len
486
487 print(pinset)
488 resources = create_resources(pinset)
489 p = ASICPlatform (resources, top.jtag)
490 p.build(top)
491 # this is what needs to gets treated as "top", after "main module" top
492 # is augmented with IO pads with JTAG tacked on. the expectation that
493 # the get_input() etc functions will be called magically by some other
494 # function is unrealistic.
495 top_fragment = p.fragment
496
497 # XXX simulating top (the module that does not itself contain IO pads
498 # because that's covered by build) cannot possibly be expected to work
499 # particularly when modules have been added *after* the platform build()
500 # function has been called.
501
502 sim = Simulator(top_fragment)
503 sim.add_clock(1e-6, domain="sync") # standard clock
504
505 sim.add_sync_process(wrap(jtag_srv(top))) #? jtag server
506 #if len(sys.argv) != 2 or sys.argv[1] != 'server':
507 sim.add_sync_process(wrap(jtag_sim(cdut, top.jtag))) # actual jtag tester
508 sim.add_sync_process(wrap(dmi_sim(top.jtag))) # handles (pretends to be) DMI
509
510 with sim.write_vcd("dmi2jtag_test_srv.vcd"):
511 sim.run()