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