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