3 # This file is Copyright (c) 2020 Florent Kermarrec <florent@enjoy-digital.fr>
12 from litex
.soc
.interconnect
import wishbone
15 # - replace raise with exit on logging error.
16 # - use common module for SoCCSR/SoCIRQ.
17 # - add configurable CSR paging.
18 # - manage IO/Linker regions.
20 logging
.basicConfig(level
=logging
.INFO
)
22 # Helpers ------------------------------------------------------------------------------------------
23 def colorer(s
, color
="bright"):
30 "underline": "\x1b[4m"}[color
]
32 return header
+ str(s
) + trailer
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")
38 # SoCRegion ----------------------------------------------------------------------------------------
41 def __init__(self
, origin
=None, size
=None, cached
=True):
42 self
.logger
= logging
.getLogger("SoCRegion")
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
)
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
)))
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
))
70 class SoCLinkerRegion(SoCRegion
):
73 # SoCBus -------------------------------------------------------------------------------------------
76 supported_standard
= ["wishbone"]
77 supported_data_width
= [32, 64]
78 supported_address_width
= [32]
80 # Creation -------------------------------------------------------------------------------------
81 def __init__(self
, standard
, data_width
=32, address_width
=32, timeout
=1e6
, reserved_regions
={}):
82 self
.logger
= logging
.getLogger("SoCBus")
83 self
.logger
.info(colorer("Creating new Bus Handler...", color
="cyan"))
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")))
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")))
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")))
107 self
.standard
= standard
108 self
.data_width
= data_width
109 self
.address_width
= address_width
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)))
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
)
124 self
.logger
.info(colorer("Bus Handler created.", color
="cyan"))
126 # Add/Allog/Check Regions ----------------------------------------------------------------------
127 def add_region(self
, name
, region
):
129 # Check if SoCLinkerRegion
130 if isinstance(region
, SoCLinkerRegion
):
131 self
.logger
.info("FIXME: SoCLinkerRegion")
133 elif isinstance(region
, SoCRegion
):
134 # If no origin specified, allocate region.
135 if region
.origin
is None:
137 region
= self
.alloc_region(region
.size
, region
.cached
)
138 self
.regions
[name
] = region
139 # Else add region and check for overlaps.
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]]))
150 self
.logger
.info("{} Region {} {}.".format(
151 colorer(name
, color
="underline"),
152 colorer("allocated" if allocated
else "added", color
="yellow" if allocated
else "green"),
155 self
.logger
.error("{} is not a supported Region".format(colorer(name
, color
="red")))
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
))))
163 # Limit Search Regions
164 uncached_regions
= {}
165 for _
, region
in self
.regions
.items():
166 if region
.cached
== False:
167 uncached_regions
[name
] = region
169 search_regions
= uncached_regions
171 search_regions
= {"main": SoCRegion(origin
=0x00000000, size
=2**self
.address_width
-1)}
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
)
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
187 # If no overlap, the Candidate is selected
190 self
.logger
.error("Not enough Address Space to allocate Region")
193 def check_region(self
, regions
):
195 while i
< len(regions
):
196 n0
= list(regions
.keys())[i
]
198 for n1
in list(regions
.keys())[i
+1:]:
200 if isinstance(r0
, SoCLinkerRegion
) or isinstance(r1
, SoCLinkerRegion
):
202 if r0
.origin
>= (r1
.origin
+ r1
.size
):
204 if r1
.origin
>= (r0
.origin
+ r0
.size
):
210 # Add Master/Slave -----------------------------------------------------------------------------
211 def add_master(self
, name
=None, master
=None, io_regions
={}):
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
)
218 self
.masters
[name
] = master
219 self
.logger
.info("{} {} as Bus Master.".format(colorer(name
, color
="underline"), colorer("added", color
="green")))
220 # FIXME: handle IO regions
222 def add_slave(self
, name
=None, slave
=None, region
=None):
223 no_name
= name
is None
224 no_region
= region
is None
225 if no_name
and no_region
:
226 self
.logger
.error("Please specify at least {} or {} of Bus Slave".format(
227 colorer("name", color
="red"),
228 colorer("region", color
="red")))
231 name
= "slave{:d}".format(len(self
.slaves
))
233 region
= self
.regions
.get(name
, None)
235 self
.logger
.error("Unable to find Region {}".format(colorer(name
, color
="red")))
238 self
.add_region(name
, region
)
239 if name
in self
.slaves
.keys():
240 self
.logger
.error("{} already declared as Bus Slave:".format(colorer(name
, color
="red")))
241 self
.logger
.error(self
)
243 self
.slaves
[name
] = slave
244 self
.logger
.info("{} {} as Bus Slave.".format(
245 colorer(name
, color
="underline"),
246 colorer("added", color
="green")))
248 # Str ------------------------------------------------------------------------------------------
250 r
= "{}-bit {} Bus, {}GiB Address Space.\n".format(
251 colorer(self
.data_width
), colorer(self
.standard
), colorer(2**self
.address_width
/2**30))
252 r
+= "Bus Regions: ({})\n".format(len(self
.regions
.keys())) if len(self
.regions
.keys()) else ""
253 for name
, region
in self
.regions
.items():
254 r
+= colorer(name
, color
="underline") + " "*(20-len(name
)) + ": " + str(region
) + "\n"
255 r
+= "Bus Masters: ({})\n".format(len(self
.masters
.keys())) if len(self
.masters
.keys()) else ""
256 for name
in self
.masters
.keys():
257 r
+= "- {}\n".format(colorer(name
, color
="underline"))
258 r
+= "Bus Slaves: ({})\n".format(len(self
.slaves
.keys())) if len(self
.slaves
.keys()) else ""
259 for name
in self
.slaves
.keys():
260 r
+= "- {}\n".format(colorer(name
, color
="underline"))
264 # SoCCSR ----------------------------------------------------------------------------------------
267 supported_data_width
= [8, 32]
268 supported_address_width
= [14, 15]
269 supported_alignment
= [32, 64]
270 supported_paging
= [0x800]
272 # Creation -------------------------------------------------------------------------------------
273 def __init__(self
, data_width
=32, address_width
=14, alignment
=32, paging
=0x800, reserved_csrs
={}):
274 self
.logger
= logging
.getLogger("SoCCSR")
275 self
.logger
.info(colorer("Creating new CSR Handler...", color
="cyan"))
278 if data_width
not in self
.supported_data_width
:
279 self
.logger
.error("Unsupported data_width: {} supporteds: {:s}".format(
280 colorer(data_width
, color
="red"),
281 colorer(", ".join(str(x
) for x
in self
.supported_data_width
)), color
="green"))
284 # Check Address Width
285 if address_width
not in self
.supported_address_width
:
286 self
.logger
.error("Unsupported address_width: {} supporteds: {:s}".format(
287 colorer(address_width
, color
="red"),
288 colorer(", ".join(str(x
) for x
in self
.supported_address_width
), color
="green")))
292 if alignment
not in self
.supported_alignment
:
293 self
.logger
.error("Unsupported alignment: {} supporteds: {:s}".format(
294 colorer(alignment
, color
="red"),
295 colorer(", ".join(str(x
) for x
in self
.supported_alignment
), color
="green")))
299 if paging
not in self
.supported_paging
:
300 self
.logger
.error("Unsupported paging: {} supporteds: {:s}".format(
301 colorer(paging
, color
="red"),
302 colorer(", ".join(str(x
) for x
in self
.supported_paging
), color
="green")))
306 self
.data_width
= data_width
307 self
.address_width
= address_width
308 self
.alignment
= alignment
311 self
.n_csrs
= 4*2**address_width
//paging
# FIXME
312 self
.logger
.info("{}-bit CSR Bus, {}KiB Address Space, {}B Paging (Up to {} Locations).\n".format(
313 colorer(self
.data_width
),
314 colorer(2**self
.address_width
/2**10),
315 colorer(self
.paging
),
318 # Adding reserved CSRs
319 self
.logger
.info("Adding {} CSRs...".format(colorer("reserved")))
320 for name
, n
in reserved_csrs
.items():
323 self
.logger
.info(colorer("CSR Bus Handler created.", color
="cyan"))
325 # Add ------------------------------------------------------------------------------------------
326 def add(self
, name
, n
=None, use_loc_if_exists
=False):
328 if not (use_loc_if_exists
and name
in self
.csrs
.keys()):
329 if name
in self
.csrs
.keys():
330 self
.logger
.error("{} CSR name already used.".format(colorer(name
, "red")))
331 self
.logger
.error(self
)
333 if n
in self
.csrs
.values():
334 self
.logger
.error("{} CSR Location already used.".format(colorer(n
, "red")))
335 self
.logger
.error(self
)
342 self
.logger
.error("{} CSR Location should be positive.".format(
343 colorer(n
, color
="red")))
346 self
.logger
.error("{} CSR Location too high (Up to {}).".format(
347 colorer(n
, color
="red"),
348 colorer(self
.n_csrs
, color
="green")))
353 self
.logger
.info("{} CSR {} at Location {}.".format(
354 colorer(name
, color
="underline"),
355 colorer("allocated" if allocated
else "added", color
="yellow" if allocated
else "green"),
358 # Alloc ----------------------------------------------------------------------------------------
359 def alloc(self
, name
):
360 for n
in range(self
.data_width
//8*2**self
.address_width
//self
.paging
):
361 if n
not in self
.csrs
.values():
363 self
.logger
.error("Not enough CSR Locations.")
364 self
.logger
.error(self
)
367 # Str ------------------------------------------------------------------------------------------
369 r
= "{}-bit CSR Bus, {}KiB Address Space, {}B Paging (Up to {} Locations).\n".format(
370 colorer(self
.data_width
),
371 colorer(2**self
.address_width
/2**10),
372 colorer(self
.paging
),
374 r
+= "CSR Locations: ({})\n".format(len(self
.csrs
.keys())) if len(self
.csrs
.keys()) else ""
375 for name
in self
.csrs
.keys():
376 r
+= "- {}{}: {}\n".format(colorer(name
, color
="underline"), " "*(20-len(name
)), colorer(self
.csrs
[name
]))
380 # SoCIRQ -------------------------------------------------------------------------------------------
383 # Creation -------------------------------------------------------------------------------------
384 def __init__(self
, n_irqs
=32, reserved_irqs
={}):
385 self
.logger
= logging
.getLogger("SoCIRQ")
386 self
.logger
.info(colorer("Creating new SoC IRQ Handler...", color
="cyan"))
390 self
.logger
.error("Unsupported IRQs number: {} supporteds: {:s}".format(
391 colorer(n
, color
="red"), colorer("Up to 32", color
="green")))
397 self
.logger
.info("IRQ Handler (up to {} Locations).".format(colorer(n_irqs
)))
399 # Adding reserved IRQs
400 self
.logger
.info("Adding {} IRQs...".format(colorer("reserved")))
401 for name
, n
in reserved_irqs
.items():
404 self
.logger
.info(colorer("IRQ Handler created.", color
="cyan"))
406 # Add ------------------------------------------------------------------------------------------
407 def add(self
, name
, n
=None, use_loc_if_exists
=False):
409 if not (use_loc_if_exists
and name
in self
.irqs
.keys()):
410 if name
in self
.irqs
.keys():
411 self
.logger
.error("{} IRQ name already used.".format(colorer(name
, "red")))
412 self
.logger
.error(self
)
414 if n
in self
.irqs
.values():
415 self
.logger
.error("{} IRQ Location already used.".format(colorer(n
, "red")))
416 self
.logger
.error(self
)
423 self
.logger
.error("{} IRQ Location should be positive.".format(
424 colorer(n
, color
="red")))
427 self
.logger
.error("{} IRQ Location too high (Up to {}).".format(
428 colorer(n
, color
="red"),
429 colorer(self
.n_csrs
, color
="green")))
434 self
.logger
.info("{} IRQ {} at Location {}.".format(
435 colorer(name
, color
="underline"),
436 colorer("allocated" if allocated
else "added", color
="yellow" if allocated
else "green"),
439 # Alloc ----------------------------------------------------------------------------------------
440 def alloc(self
, name
):
441 for n
in range(self
.n_irqs
):
442 if n
not in self
.irqs
.values():
444 self
.logger
.error("Not enough Locations.")
445 self
.logger
.error(self
)
448 # Str ------------------------------------------------------------------------------------------
450 r
="IRQ Handler (up to {} Locations).\n".format(colorer(self
.n_irqs
))
451 r
+= "IRQs Locations:\n" if len(self
.irqs
.keys()) else ""
452 for name
in self
.irqs
.keys():
453 r
+= "- {}{}: {}\n".format(colorer(name
, color
="underline"), " "*(20-len(name
)), colorer(self
.irqs
[name
]))
457 # SoC ----------------------------------------------------------------------------------------------
461 bus_standard
= "wishbone",
463 bus_address_width
= 32,
465 bus_reserved_regions
= {},
468 csr_address_width
= 14,
471 csr_reserved_csrs
= {},
474 irq_reserved_irqs
= {},
477 self
.logger
= logging
.getLogger("SoC")
478 self
.logger
.info(colorer(" __ _ __ _ __ ", color
="bright"))
479 self
.logger
.info(colorer(" / / (_) /____ | |/_/ ", color
="bright"))
480 self
.logger
.info(colorer(" / /__/ / __/ -_)> < ", color
="bright"))
481 self
.logger
.info(colorer(" /____/_/\\__/\\__/_/|_| ", color
="bright"))
482 self
.logger
.info(colorer(" Build your hardware, easily!", color
="bright"))
484 self
.logger
.info(colorer("-"*80, color
="bright"))
485 self
.logger
.info(colorer("Creating new SoC... ({})".format(buildtime()), color
="cyan"))
486 self
.logger
.info(colorer("-"*80, color
="bright"))
488 # SoC Bus Handler --------------------------------------------------------------------------
490 standard
= bus_standard
,
491 data_width
= bus_data_width
,
492 address_width
= bus_address_width
,
493 timeout
= bus_timeout
,
494 reserved_regions
= bus_reserved_regions
,
497 # SoC Bus Handler --------------------------------------------------------------------------
499 data_width
= csr_data_width
,
500 address_width
= csr_address_width
,
501 alignment
= csr_alignment
,
503 reserved_csrs
= csr_reserved_csrs
,
506 # SoC IRQ Handler --------------------------------------------------------------------------
509 reserved_irqs
= irq_reserved_irqs
512 self
.logger
.info(colorer("-"*80, color
="bright"))
513 self
.logger
.info(colorer("Initial SoC:", color
="cyan"))
514 self
.logger
.info(colorer("-"*80, color
="bright"))
515 self
.logger
.info(self
.bus
)
516 self
.logger
.info(self
.csr
)
517 self
.logger
.info(self
.irq
)
518 self
.logger
.info(colorer("-"*80, color
="bright"))
521 def do_finalize(self
):
522 self
.logger
.info(colorer("-"*80, color
="bright"))
523 self
.logger
.info(colorer("Finalized SoC:", color
="cyan"))
524 self
.logger
.info(colorer("-"*80, color
="bright"))
525 self
.logger
.info(self
.bus
)
526 self
.logger
.info(self
.csr
)
527 self
.logger
.info(self
.irq
)
528 self
.logger
.info(colorer("-"*80, color
="bright"))
530 # SoC Bus Interconnect ---------------------------------------------------------------------
531 bus_masters
= self
.bus
.masters
.values()
532 bus_slaves
= [(self
.bus
.regions
[n
].decoder(), s
) for n
, s
in self
.bus
.slaves
.items()]
533 if len(bus_masters
) and len(bus_slaves
):
534 self
.submodules
.bus_interconnect
= wishbone
.InterconnectShared(
535 masters
= bus_masters
,
538 timeout_cycles
= self
.bus
.timeout
)
542 # Test (FIXME: move to litex/text and improve) -----------------------------------------------------
544 if __name__
== "__main__":
545 bus
= SoCBus("wishbone", reserved_regions
={
546 "rom": SoCRegion(origin
=0x00000100, size
=1024),
547 "ram": SoCRegion(size
=512),
550 bus
.add_master("cpu", None)
551 bus
.add_slave("rom", None, SoCRegion(size
=1024))
552 bus
.add_slave("ram", None, SoCRegion(size
=1024))
555 csr
= SoCCSR(reserved_csrs
={"ctrl": 0, "uart": 1})
563 irq
= SoCIRQ(reserved_irqs
={"uart": 1})