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