soc: add automatic bus data width convertion to add_master/add_slave
[litex.git] / litex / soc / integration / soc.py
1 #!/usr/bin/env python3
2
3 # This file is Copyright (c) 2020 Florent Kermarrec <florent@enjoy-digital.fr>
4 # License: BSD
5
6 import logging
7 import time
8 import datetime
9
10 from migen import *
11
12 from litex.soc.interconnect.csr import *
13 from litex.soc.interconnect import wishbone
14
15 # TODO:
16 # - replace raise with exit on logging error.
17 # - add configurable CSR paging.
18 # - manage IO/Linker regions.
19
20 logging.basicConfig(level=logging.INFO)
21
22 # Helpers ------------------------------------------------------------------------------------------
23 def colorer(s, color="bright"):
24 header = {
25 "bright": "\x1b[1m",
26 "green": "\x1b[32m",
27 "cyan": "\x1b[36m",
28 "red": "\x1b[31m",
29 "yellow": "\x1b[33m",
30 "underline": "\x1b[4m"}[color]
31 trailer = "\x1b[0m"
32 return header + str(s) + trailer
33
34 def buildtime(with_time=True):
35 fmt = "%Y-%m-%d %H:%M:%S" if with_time else "%Y-%m-%d"
36 return datetime.datetime.fromtimestamp(time.time()).strftime("%Y-%m-%d %H:%M:%S")
37
38 # SoCRegion ----------------------------------------------------------------------------------------
39
40 class SoCRegion:
41 def __init__(self, origin=None, size=None, cached=True):
42 self.logger = logging.getLogger("SoCRegion")
43 self.origin = origin
44 self.size = size
45 self.cached = cached
46
47 def decoder(self):
48 origin = self.origin
49 size = self.size
50 origin &= ~0x80000000
51 size = 2**log2_int(size, False)
52 if (origin & (size - 1)) != 0:
53 self.logger.error("Origin needs to be aligned on size:")
54 self.logger.error(self)
55 raise
56 origin >>= 2 # bytes to words aligned
57 size >>= 2 # bytes to words aligned
58 return lambda a: (a[log2_int(size):-1] == (origin >> log2_int(size)))
59
60 def __str__(self):
61 r = ""
62 if self.origin is not None:
63 r += "Origin: {}, ".format(colorer("0x{:08x}".format(self.origin)))
64 if self.size is not None:
65 r += "Size: {}, ".format(colorer("0x{:08x}".format(self.size)))
66 r += "Cached: {}".format(colorer(self.cached))
67 return r
68
69
70 class SoCLinkerRegion(SoCRegion):
71 pass
72
73 # SoCBusHandler ------------------------------------------------------------------------------------
74
75 class SoCBusHandler(Module):
76 supported_standard = ["wishbone"]
77 supported_data_width = [32, 64]
78 supported_address_width = [32]
79
80 # Creation -------------------------------------------------------------------------------------
81 def __init__(self, standard, data_width=32, address_width=32, timeout=1e6, reserved_regions={}):
82 self.logger = logging.getLogger("SoCBusHandler")
83 self.logger.info(colorer("Creating new Bus Handler...", color="cyan"))
84
85 # Check Standard
86 if standard not in self.supported_standard:
87 self.logger.error("Unsupported Standard: {} supporteds: {:s}".format(
88 colorer(standard, color="red"),
89 colorer(", ".join(self.supported_standard), color="green")))
90 raise
91
92 # Check Data Width
93 if data_width not in self.supported_data_width:
94 self.logger.error("Unsupported Data_Width: {} supporteds: {:s}".format(
95 colorer(data_width, color="red"),
96 colorer(", ".join(str(x) for x in self.supported_data_width), color="green")))
97 raise
98
99 # Check Address Width
100 if address_width not in self.supported_address_width:
101 self.logger.error("Unsupported Address Width: {} supporteds: {:s}".format(
102 colorer(data_width, color="red"),
103 colorer(", ".join(str(x) for x in self.supported_address_width), color="green")))
104 raise
105
106 # Create Bus
107 self.standard = standard
108 self.data_width = data_width
109 self.address_width = address_width
110 self.masters = {}
111 self.slaves = {}
112 self.regions = {}
113 self.timeout = timeout
114 self.logger.info("{}-bit {} Bus, {}GiB Address Space.".format(
115 colorer(data_width), colorer(standard), colorer(2**address_width/2**30)))
116
117 # Adding reserved regions
118 self.logger.info("Adding {} Regions...".format(colorer("reserved")))
119 for name, region in reserved_regions.items():
120 if isinstance(region, int):
121 region = SoCRegion(origin=region, size=0x1000000)
122 self.add_region(name, region)
123
124 self.logger.info(colorer("Bus Handler created.", color="cyan"))
125
126 # Add/Allog/Check Regions ----------------------------------------------------------------------
127 def add_region(self, name, region):
128 allocated = False
129 # Check if SoCLinkerRegion
130 if isinstance(region, SoCLinkerRegion):
131 self.logger.info("FIXME: SoCLinkerRegion")
132 # Check if SoCRegion
133 elif isinstance(region, SoCRegion):
134 # If no origin specified, allocate region.
135 if region.origin is None:
136 allocated = True
137 region = self.alloc_region(region.size, region.cached)
138 self.regions[name] = region
139 # Else add region and check for overlaps.
140 else:
141 self.regions[name] = region
142 overlap = self.check_region(self.regions)
143 if overlap is not None:
144 self.logger.error("Region overlap between {} and {}:".format(
145 colorer(overlap[0], color="red"),
146 colorer(overlap[1], color="red")))
147 self.logger.error(str(self.regions[overlap[0]]))
148 self.logger.error(str(self.regions[overlap[1]]))
149 raise
150 self.logger.info("{} Region {} {}.".format(
151 colorer(name, color="underline"),
152 colorer("allocated" if allocated else "added", color="yellow" if allocated else "green"),
153 str(region)))
154 else:
155 self.logger.error("{} is not a supported Region".format(colorer(name, color="red")))
156 raise
157
158 def alloc_region(self, size, cached=True):
159 self.logger.info("Allocating {} Region of size {}...".format(
160 colorer("Cached" if cached else "IO"),
161 colorer("0x{:08x}".format(size))))
162
163 # Limit Search Regions
164 uncached_regions = {}
165 for _, region in self.regions.items():
166 if region.cached == False:
167 uncached_regions[name] = region
168 if cached == False:
169 search_regions = uncached_regions
170 else:
171 search_regions = {"main": SoCRegion(origin=0x00000000, size=2**self.address_width-1)}
172
173 # Iterate on Search_Regions to find a Candidate
174 for _, search_region in search_regions.items():
175 origin = search_region.origin
176 while (origin + size) < (search_region.origin + search_region.size):
177 # Create a Candicate.
178 candidate = SoCRegion(origin=origin, size=size, cached=cached)
179 overlap = False
180 # Check Candidate does not overlap with allocated existing regions
181 for _, allocated in self.regions.items():
182 if self.check_region({"0": allocated, "1": candidate}) is not None:
183 origin = allocated.origin + allocated.size
184 overlap = True
185 break
186 if not overlap:
187 # If no overlap, the Candidate is selected
188 return candidate
189
190 self.logger.error("Not enough Address Space to allocate Region")
191 raise
192
193 def check_region(self, regions):
194 i = 0
195 while i < len(regions):
196 n0 = list(regions.keys())[i]
197 r0 = regions[n0]
198 for n1 in list(regions.keys())[i+1:]:
199 r1 = regions[n1]
200 if isinstance(r0, SoCLinkerRegion) or isinstance(r1, SoCLinkerRegion):
201 continue
202 if r0.origin >= (r1.origin + r1.size):
203 continue
204 if r1.origin >= (r0.origin + r0.size):
205 continue
206 return (n0, n1)
207 i += 1
208 return None
209
210 # Add Master/Slave -----------------------------------------------------------------------------
211 def add_master(self, name=None, master=None, io_regions={}):
212 if name is None:
213 name = "master{:d}".format(len(self.masters))
214 if name in self.masters.keys():
215 self.logger.error("{} already declared as Bus Master:".format(colorer(name, color="red")))
216 self.logger.error(self)
217 raise
218 if master.data_width != self.data_width:
219 self.logger.error("{} Bus Master {} from {}-bit to {}-bit.".format(
220 colorer(name),
221 colorer("converted", color="yellow"),
222 colorer(master.data_width),
223 colorer(self.data_width)))
224 new_master = wishbone.Interface(data_width=self.data_width)
225 self.submodules += wishbone.Converter(master, new_master)
226 master = new_master
227 self.masters[name] = master
228 self.logger.info("{} {} as Bus Master.".format(colorer(name, color="underline"), colorer("added", color="green")))
229 # FIXME: handle IO regions
230
231 def add_slave(self, name=None, slave=None, region=None):
232 no_name = name is None
233 no_region = region is None
234 if no_name and no_region:
235 self.logger.error("Please specify at least {} or {} of Bus Slave".format(
236 colorer("name", color="red"),
237 colorer("region", color="red")))
238 raise
239 if no_name:
240 name = "slave{:d}".format(len(self.slaves))
241 if no_region:
242 region = self.regions.get(name, None)
243 if region is None:
244 self.logger.error("Unable to find Region {}".format(colorer(name, color="red")))
245 raise
246 else:
247 self.add_region(name, region)
248 if name in self.slaves.keys():
249 self.logger.error("{} already declared as Bus Slave:".format(colorer(name, color="red")))
250 self.logger.error(self)
251 raise
252 if slave.data_width != self.data_width:
253 self.logger.error("{} Bus Slave {} from {}-bit to {}-bit.".format(
254 colorer(name),
255 colorer("converted", color="yellow"),
256 colorer(slave.data_width),
257 colorer(self.data_width)))
258 new_slave = wishbone.Interface(data_width=self.data_width)
259 self.submodules += wishbone.Converter(slave, new_slave)
260 slave = new_slave
261 self.slaves[name] = slave
262 self.logger.info("{} {} as Bus Slave.".format(
263 colorer(name, color="underline"),
264 colorer("added", color="green")))
265
266 # Str ------------------------------------------------------------------------------------------
267 def __str__(self):
268 r = "{}-bit {} Bus, {}GiB Address Space.\n".format(
269 colorer(self.data_width), colorer(self.standard), colorer(2**self.address_width/2**30))
270 r += "Bus Regions: ({})\n".format(len(self.regions.keys())) if len(self.regions.keys()) else ""
271 for name, region in self.regions.items():
272 r += colorer(name, color="underline") + " "*(20-len(name)) + ": " + str(region) + "\n"
273 r += "Bus Masters: ({})\n".format(len(self.masters.keys())) if len(self.masters.keys()) else ""
274 for name in self.masters.keys():
275 r += "- {}\n".format(colorer(name, color="underline"))
276 r += "Bus Slaves: ({})\n".format(len(self.slaves.keys())) if len(self.slaves.keys()) else ""
277 for name in self.slaves.keys():
278 r += "- {}\n".format(colorer(name, color="underline"))
279 r = r[:-1]
280 return r
281
282 # SoCLocHandler --------------------------------------------------------------------------------------
283
284 class SoCLocHandler(Module):
285 # Creation -------------------------------------------------------------------------------------
286 def __init__(self, name, n_locs):
287 self.name = name
288 self.locs = {}
289 self.n_locs = n_locs
290
291 # Add ------------------------------------------------------------------------------------------
292 def add(self, name, n=None, use_loc_if_exists=False):
293 allocated = False
294 if not (use_loc_if_exists and name in self.locs.keys()):
295 if name in self.locs.keys():
296 self.logger.error("{} {} name already used.".format(colorer(name, "red"), self.name))
297 self.logger.error(self)
298 raise
299 if n in self.locs.values():
300 self.logger.error("{} {} Location already used.".format(colorer(n, "red"), self.name))
301 self.logger.error(self)
302 raise
303 if n is None:
304 allocated = True
305 n = self.alloc(name)
306 else:
307 if n < 0:
308 self.logger.error("{} {} Location should be positive.".format(
309 colorer(n, color="red"),
310 self.name))
311 raise
312 if n > self.n_locs:
313 self.logger.error("{} {} Location too high (Up to {}).".format(
314 colorer(n, color="red"),
315 self.name,
316 colorer(self.n_csrs, color="green")))
317 raise
318 self.locs[name] = n
319 else:
320 n = self.locs[name]
321 self.logger.info("{} {} {} at Location {}.".format(
322 colorer(name, color="underline"),
323 self.name,
324 colorer("allocated" if allocated else "added", color="yellow" if allocated else "green"),
325 colorer(n)))
326
327 # Alloc ----------------------------------------------------------------------------------------
328 def alloc(self, name):
329 for n in range(self.n_locs):
330 if n not in self.locs.values():
331 return n
332 self.logger.error("Not enough Locations.")
333 self.logger.error(self)
334 raise
335
336 # Str ------------------------------------------------------------------------------------------
337 def __str__(self):
338 r = "{} Locations: ({})\n".format(self.name, len(self.locs.keys())) if len(self.locs.keys()) else ""
339 for name in self.locs.keys():
340 r += "- {}{}: {}\n".format(colorer(name, color="underline"), " "*(20-len(name)), colorer(self.locs[name]))
341 return r
342
343 # SoCCSRHandler ------------------------------------------------------------------------------------
344
345 class SoCCSRHandler(SoCLocHandler):
346 supported_data_width = [8, 32]
347 supported_address_width = [14, 15]
348 supported_alignment = [32, 64]
349 supported_paging = [0x800]
350
351 # Creation -------------------------------------------------------------------------------------
352 def __init__(self, data_width=32, address_width=14, alignment=32, paging=0x800, reserved_csrs={}):
353 SoCLocHandler.__init__(self, "CSR", n_locs=4*2**address_width//paging) # FIXME
354 self.logger = logging.getLogger("SoCCSRHandler")
355 self.logger.info(colorer("Creating new CSR Handler...", color="cyan"))
356
357 # Check Data Width
358 if data_width not in self.supported_data_width:
359 self.logger.error("Unsupported data_width: {} supporteds: {:s}".format(
360 colorer(data_width, color="red"),
361 colorer(", ".join(str(x) for x in self.supported_data_width)), color="green"))
362 raise
363
364 # Check Address Width
365 if address_width not in self.supported_address_width:
366 self.logger.error("Unsupported address_width: {} supporteds: {:s}".format(
367 colorer(address_width, color="red"),
368 colorer(", ".join(str(x) for x in self.supported_address_width), color="green")))
369 raise
370
371 # Check Alignment
372 if alignment not in self.supported_alignment:
373 self.logger.error("Unsupported alignment: {} supporteds: {:s}".format(
374 colorer(alignment, color="red"),
375 colorer(", ".join(str(x) for x in self.supported_alignment), color="green")))
376 raise
377
378 # Check Paging
379 if paging not in self.supported_paging:
380 self.logger.error("Unsupported paging: {} supporteds: {:s}".format(
381 colorer(paging, color="red"),
382 colorer(", ".join(str(x) for x in self.supported_paging), color="green")))
383 raise
384
385 # Create CSR Handler
386 self.data_width = data_width
387 self.address_width = address_width
388 self.alignment = alignment
389 self.paging = paging
390 self.logger.info("{}-bit CSR Bus, {}KiB Address Space, {}B Paging (Up to {} Locations).\n".format(
391 colorer(self.data_width),
392 colorer(2**self.address_width/2**10),
393 colorer(self.paging),
394 colorer(self.n_locs)))
395
396 # Adding reserved CSRs
397 self.logger.info("Adding {} CSRs...".format(colorer("reserved")))
398 for name, n in reserved_csrs.items():
399 self.add(name, n)
400
401 self.logger.info(colorer("CSR Bus Handler created.", color="cyan"))
402
403 # Str ------------------------------------------------------------------------------------------
404 def __str__(self):
405 r = "{}-bit CSR Bus, {}KiB Address Space, {}B Paging (Up to {} Locations).\n".format(
406 colorer(self.data_width),
407 colorer(2**self.address_width/2**10),
408 colorer(self.paging),
409 colorer(self.n_locs))
410 r += SoCLocHandler.__str__(self)
411 r = r[:-1]
412 return r
413
414 # SoCIRQHandler ------------------------------------------------------------------------------------
415
416 class SoCIRQHandler(SoCLocHandler):
417 # Creation -------------------------------------------------------------------------------------
418 def __init__(self, n_irqs=32, reserved_irqs={}):
419 SoCLocHandler.__init__(self, "IRQ", n_locs=n_irqs)
420 self.logger = logging.getLogger("SoCIRQHandler")
421 self.logger.info(colorer("Creating new SoC IRQ Handler...", color="cyan"))
422
423 # Check IRQ Number
424 if n_irqs > 32:
425 self.logger.error("Unsupported IRQs number: {} supporteds: {:s}".format(
426 colorer(n, color="red"), colorer("Up to 32", color="green")))
427 raise
428
429 # Create IRQ Handler
430 self.logger.info("IRQ Handler (up to {} Locations).".format(colorer(n_irqs)))
431
432 # Adding reserved IRQs
433 self.logger.info("Adding {} IRQs...".format(colorer("reserved")))
434 for name, n in reserved_irqs.items():
435 self.add(name, n)
436
437 self.logger.info(colorer("IRQ Handler created.", color="cyan"))
438
439 # Str ------------------------------------------------------------------------------------------
440 def __str__(self):
441 r ="IRQ Handler (up to {} Locations).\n".format(colorer(self.n_locs))
442 r += SoCLocHandler.__str__(self)
443 r = r[:-1]
444 return r
445
446 # SoCController ------------------------------------------------------------------------------------
447
448 class SoCController(Module, AutoCSR):
449 def __init__(self):
450 self._reset = CSRStorage(1, description="""
451 Write a ``1`` to this register to reset the SoC.""")
452 self._scratch = CSRStorage(32, reset=0x12345678, description="""
453 Use this register as a scratch space to verify that software read/write accesses
454 to the Wishbone/CSR bus are working correctly. The initial reset value of 0x1234578
455 can be used to verify endianness.""")
456 self._bus_errors = CSRStatus(32, description="""
457 Total number of Wishbone bus errors (timeouts) since last reset.""")
458
459 # # #
460
461 # Reset
462 self.reset = Signal()
463 self.comb += self.reset.eq(self._reset.re)
464
465 # Bus errors
466 self.bus_error = Signal()
467 bus_errors = Signal(32)
468 self.sync += \
469 If(bus_errors != (2**len(bus_errors)-1),
470 If(self.bus_error, bus_errors.eq(bus_errors + 1))
471 )
472 self.comb += self._bus_errors.status.eq(bus_errors)
473
474 # SoC ----------------------------------------------------------------------------------------------
475
476 class SoC(Module):
477 def __init__(self,
478 bus_standard = "wishbone",
479 bus_data_width = 32,
480 bus_address_width = 32,
481 bus_timeout = 1e6,
482 bus_reserved_regions = {},
483
484 csr_data_width = 32,
485 csr_address_width = 14,
486 csr_alignment = 32,
487 csr_paging = 0x800,
488 csr_reserved_csrs = {},
489
490 irq_n_irqs = 32,
491 irq_reserved_irqs = {},
492 ):
493
494 self.logger = logging.getLogger("SoC")
495 self.logger.info(colorer(" __ _ __ _ __ ", color="bright"))
496 self.logger.info(colorer(" / / (_) /____ | |/_/ ", color="bright"))
497 self.logger.info(colorer(" / /__/ / __/ -_)> < ", color="bright"))
498 self.logger.info(colorer(" /____/_/\\__/\\__/_/|_| ", color="bright"))
499 self.logger.info(colorer(" Build your hardware, easily!", color="bright"))
500
501 self.logger.info(colorer("-"*80, color="bright"))
502 self.logger.info(colorer("Creating new SoC... ({})".format(buildtime()), color="cyan"))
503 self.logger.info(colorer("-"*80, color="bright"))
504
505 # SoC Bus Handler --------------------------------------------------------------------------
506 self.submodules.bus = SoCBusHandler(
507 standard = bus_standard,
508 data_width = bus_data_width,
509 address_width = bus_address_width,
510 timeout = bus_timeout,
511 reserved_regions = bus_reserved_regions,
512 )
513
514 # SoC Bus Handler --------------------------------------------------------------------------
515 self.submodules.csr = SoCCSRHandler(
516 data_width = csr_data_width,
517 address_width = csr_address_width,
518 alignment = csr_alignment,
519 paging = csr_paging,
520 reserved_csrs = csr_reserved_csrs,
521 )
522
523 # SoC IRQ Handler --------------------------------------------------------------------------
524 self.submodules.irq = SoCIRQHandler(
525 n_irqs = irq_n_irqs,
526 reserved_irqs = irq_reserved_irqs
527 )
528
529 self.logger.info(colorer("-"*80, color="bright"))
530 self.logger.info(colorer("Initial SoC:", color="cyan"))
531 self.logger.info(colorer("-"*80, color="bright"))
532 self.logger.info(self.bus)
533 self.logger.info(self.csr)
534 self.logger.info(self.irq)
535 self.logger.info(colorer("-"*80, color="bright"))
536
537 def do_finalize(self):
538 self.logger.info(colorer("-"*80, color="bright"))
539 self.logger.info(colorer("Finalized SoC:", color="cyan"))
540 self.logger.info(colorer("-"*80, color="bright"))
541 self.logger.info(self.bus)
542 self.logger.info(self.csr)
543 self.logger.info(self.irq)
544 self.logger.info(colorer("-"*80, color="bright"))
545
546 # SoC Bus Interconnect ---------------------------------------------------------------------
547 bus_masters = self.bus.masters.values()
548 bus_slaves = [(self.bus.regions[n].decoder(), s) for n, s in self.bus.slaves.items()]
549 if len(bus_masters) and len(bus_slaves):
550 self.submodules.bus_interconnect = wishbone.InterconnectShared(
551 masters = bus_masters,
552 slaves = bus_slaves,
553 register = True,
554 timeout_cycles = self.bus.timeout)
555
556 #exit()
557
558 # Test (FIXME: move to litex/text and improve) -----------------------------------------------------
559
560 if __name__ == "__main__":
561 bus = SoCBusHandler("wishbone", reserved_regions={
562 "rom": SoCRegion(origin=0x00000100, size=1024),
563 "ram": SoCRegion(size=512),
564 }
565 )
566 bus.add_master("cpu", None)
567 bus.add_slave("rom", None, SoCRegion(size=1024))
568 bus.add_slave("ram", None, SoCRegion(size=1024))
569
570
571 csr = SoCCSRHandler(reserved_csrs={"ctrl": 0, "uart": 1})
572 csr.add("csr0")
573 csr.add("csr1", 0)
574 #csr.add("csr2", 46)
575 csr.add("csr3", -1)
576 print(bus)
577 print(csr)
578
579 irq = SoCIRQHandler(reserved_irqs={"uart": 1})
580
581 soc = SoC()