soc: Don't update CSR alignment when there is no CPU
[litex.git] / litex / soc / integration / soc.py
old mode 100755 (executable)
new mode 100644 (file)
index 1c3534e..651f450
@@ -6,13 +6,15 @@
 import logging
 import time
 import datetime
-from math import log2
+from math import log2, ceil
 
 from migen import *
 
 from litex.soc.cores import cpu
 from litex.soc.cores.identifier import Identifier
 from litex.soc.cores.timer import Timer
+from litex.soc.cores.spi_flash import SpiFlash
+from litex.soc.cores.spi import SPIMaster
 
 from litex.soc.interconnect.csr import *
 from litex.soc.interconnect import csr_bus
@@ -20,18 +22,17 @@ from litex.soc.interconnect import wishbone
 from litex.soc.interconnect import wishbone2csr
 from litex.soc.interconnect import axi
 
-from litedram.core import LiteDRAMCore
-from litedram.frontend.wishbone import LiteDRAMWishbone2Native
-
 # TODO:
 # - replace raise with exit on logging error.
-# - add configurable CSR paging.
-# - manage SoCLinkerRegion
 # - cleanup SoCCSRRegion
 
 logging.basicConfig(level=logging.INFO)
 
 # Helpers ------------------------------------------------------------------------------------------
+
+def auto_int(x):
+    return int(x, 0)
+
 def colorer(s, color="bright"):
     header  = {
         "bright": "\x1b[1m",
@@ -55,17 +56,23 @@ def SoCConstant(value):
 # SoCRegion ----------------------------------------------------------------------------------------
 
 class SoCRegion:
-    def __init__(self, origin=None, size=None, mode="rw", cached=True):
+    def __init__(self, origin=None, size=None, mode="rw", cached=True, linker=False):
         self.logger    = logging.getLogger("SoCRegion")
         self.origin    = origin
         self.size      = size
+        if size != 2**log2_int(size, False):
+            self.logger.info("Region size {} internally from {} to {}.".format(
+                colorer("rounded", color="cyan"),
+                colorer("0x{:08x}".format(size)),
+                colorer("0x{:08x}".format(2**log2_int(size, False)))))
+        self.size_pow2 = 2**log2_int(size, False)
         self.mode      = mode
         self.cached    = cached
+        self.linker    = linker
 
     def decoder(self, bus):
         origin = self.origin
-        size   = self.size
-        size   = 2**log2_int(size, False)
+        size   = self.size_pow2
         if (origin & (size - 1)) != 0:
             self.logger.error("Origin needs to be aligned on size:")
             self.logger.error(self)
@@ -81,13 +88,12 @@ class SoCRegion:
         if self.size is not None:
             r += "Size: {}, ".format(colorer("0x{:08x}".format(self.size)))
         r += "Mode: {}, ".format(colorer(self.mode.upper()))
-        r += "Cached: {}".format(colorer(self.cached))
+        r += "Cached: {} ".format(colorer(self.cached))
+        r += "Linker: {}".format(colorer(self.linker))
         return r
 
 class SoCIORegion(SoCRegion): pass
 
-class SoCLinkerRegion(SoCRegion): pass
-
 # SoCCSRRegion -------------------------------------------------------------------------------------
 
 class SoCCSRRegion:
@@ -110,23 +116,26 @@ class SoCBusHandler(Module):
 
         # Check Standard
         if standard not in self.supported_standard:
-            self.logger.error("Unsupported Standard: {} supporteds: {:s}".format(
-                colorer(standard, color="red"),
-                colorer(", ".join(self.supported_standard), color="green")))
+            self.logger.error("Unsupported {} {}, supporteds: {:s}".format(
+                colorer("Bus standard", color="red"),
+                colorer(standard),
+                colorer(", ".join(self.supported_standard))))
             raise
 
         # Check Data Width
         if data_width not in self.supported_data_width:
-            self.logger.error("Unsupported Data_Width: {} supporteds: {:s}".format(
-                colorer(data_width, color="red"),
-                colorer(", ".join(str(x) for x in self.supported_data_width), color="green")))
+            self.logger.error("Unsupported {} {}, supporteds: {:s}".format(
+                colorer("Data Width", color="red"),
+                colorer(data_width),
+                colorer(", ".join(str(x) for x in self.supported_data_width))))
             raise
 
         # Check Address Width
         if address_width not in self.supported_address_width:
-            self.logger.error("Unsupported Address Width: {} supporteds: {:s}".format(
-                colorer(data_width, color="red"),
-                colorer(", ".join(str(x) for x in self.supported_address_width), color="green")))
+            self.logger.error("Unsupported {} {}, supporteds: {:s}".format(
+                colorer("Address Width", color="red"),
+                colorer(data_width),
+                colorer(", ".join(str(x) for x in self.supported_address_width))))
             raise
 
         # Create Bus
@@ -137,7 +146,6 @@ class SoCBusHandler(Module):
         self.slaves        = {}
         self.regions       = {}
         self.io_regions    = {}
-        self.ld_regions    = {}
         self.timeout       = timeout
         self.logger.info("{}-bit {} Bus, {}GiB Address Space.".format(
             colorer(data_width), colorer(standard), colorer(2**address_width/2**30)))
@@ -154,41 +162,23 @@ class SoCBusHandler(Module):
     # Add/Allog/Check Regions ----------------------------------------------------------------------
     def add_region(self, name, region):
         allocated = False
+        if name in self.regions.keys() or name in self.io_regions.keys():
+            self.logger.error("{} already declared as Region:".format(colorer(name, color="red")))
+            self.logger.error(self)
+            raise
         # Check if SoCIORegion
         if isinstance(region, SoCIORegion):
-            if name in self.masters.keys():
-                self.logger.error("{} already declared as IO Region:".format(colorer(name, color="red")))
-                self.logger.error(self)
-                raise
             self.io_regions[name] = region
             overlap = self.check_regions_overlap(self.io_regions)
             if overlap is not None:
-                self.logger.error("IO Region overlap between {} and {}:".format(
-                    colorer(overlap[0], color="red"),
-                    colorer(overlap[1], color="red")))
-                self.logger.error(str(self.regions[overlap[0]]))
-                self.logger.error(str(self.regions[overlap[1]]))
-                raise
-            self.logger.info("{} Region {} {}.".format(
-                colorer(name,    color="underline"),
-                colorer("added", color="green"),
-                str(region)))
-        # Check if SoCLinkerRegion
-        elif isinstance(region, SoCLinkerRegion):
-            if name in self.masters.keys():
-                self.logger.error("{} already declared as Linker Region:".format(colorer(name, color="red")))
-                self.logger.error(self)
-                raise
-            self.ld_regions[name] = region
-            overlap = self.check_regions_overlap(self.ld_regions)
-            if overlap is not None:
-                self.logger.error("Linker Region overlap between {} and {}:".format(
-                    colorer(overlap[0], color="red"),
-                    colorer(overlap[1], color="red")))
-                self.logger.error(str(self.regions[overlap[0]]))
-                self.logger.error(str(self.regions[overlap[1]]))
+                self.logger.error("IO Region {} between {} and {}:".format(
+                    colorer("overlap", color="red"),
+                    colorer(overlap[0]),
+                    colorer(overlap[1])))
+                self.logger.error(str(self.io_regions[overlap[0]]))
+                self.logger.error(str(self.io_regions[overlap[1]]))
                 raise
-            self.logger.info("{} Region {} {}.".format(
+            self.logger.info("{} Region {} at {}.".format(
                 colorer(name,    color="underline"),
                 colorer("added", color="green"),
                 str(region)))
@@ -203,27 +193,28 @@ class SoCBusHandler(Module):
             else:
                 if not region.cached:
                     if not self.check_region_is_io(region):
-                        self.logger.error("{} Region {}: {}".format(
-                            colorer(name, color="red"),
-                            colorer("not cached but not in IO region", color="red"),
+                        self.logger.error("{} Region {}: {}.".format(
+                            colorer(name),
+                            colorer("not in IO region", color="red"),
                             str(region)))
                         self.logger.error(self)
                         raise
                 self.regions[name] = region
                 overlap = self.check_regions_overlap(self.regions)
                 if overlap is not None:
-                    self.logger.error("Region overlap between {} and {}:".format(
-                        colorer(overlap[0], color="red"),
-                        colorer(overlap[1], color="red")))
+                    self.logger.error("Region {} between {} and {}:".format(
+                        colorer("overlap", color="red"),
+                        colorer(overlap[0]),
+                        colorer(overlap[1])))
                     self.logger.error(str(self.regions[overlap[0]]))
                     self.logger.error(str(self.regions[overlap[1]]))
                     raise
-            self.logger.info("{} Region {} {}.".format(
+            self.logger.info("{} Region {} at {}.".format(
                 colorer(name, color="underline"),
                 colorer("allocated" if allocated else "added", color="cyan" if allocated else "green"),
                 str(region)))
         else:
-            self.logger.error("{} is not a supported Region".format(colorer(name, color="red")))
+            self.logger.error("{} is not a supported Region.".format(colorer(name, color="red")))
             raise
 
     def alloc_region(self, name, size, cached=True):
@@ -240,35 +231,36 @@ class SoCBusHandler(Module):
         # Iterate on Search_Regions to find a Candidate
         for _, search_region in search_regions.items():
             origin = search_region.origin
-            while (origin + size) < (search_region.origin + search_region.size):
+            while (origin + size) < (search_region.origin + search_region.size_pow2):
                 # Create a Candicate.
                 candidate = SoCRegion(origin=origin, size=size, cached=cached)
                 overlap   = False
                 # Check Candidate does not overlap with allocated existing regions
                 for _, allocated in self.regions.items():
                     if self.check_regions_overlap({"0": allocated, "1": candidate}) is not None:
-                        origin  = allocated.origin + allocated.size
+                        origin  = allocated.origin + allocated.size_pow2
                         overlap = True
                         break
                 if not overlap:
                     # If no overlap, the Candidate is selected
                     return candidate
 
-        self.logger.error("Not enough Address Space to allocate Region")
+        self.logger.error("Not enough Address Space to allocate Region.")
         raise
 
-    def check_regions_overlap(self, regions):
+    def check_regions_overlap(self, regions, check_linker=False):
         i = 0
         while i < len(regions):
             n0 =  list(regions.keys())[i]
             r0 = regions[n0]
             for n1 in list(regions.keys())[i+1:]:
                 r1 = regions[n1]
-                if isinstance(r0, SoCLinkerRegion) or isinstance(r1, SoCLinkerRegion):
-                    continue
-                if r0.origin >= (r1.origin + r1.size):
+                if r0.linker or r1.linker:
+                    if not check_linker:
+                        continue
+                if r0.origin >= (r1.origin + r1.size_pow2):
                     continue
-                if r1.origin >= (r0.origin + r0.size):
+                if r1.origin >= (r0.origin + r0.size_pow2):
                     continue
                 return (n0, n1)
             i += 1
@@ -290,7 +282,8 @@ class SoCBusHandler(Module):
         return is_io
 
     # Add Master/Slave -----------------------------------------------------------------------------
-    def add_adapter(self, name, interface):
+    def add_adapter(self, name, interface, direction="m2s"):
+        assert direction in ["m2s", "s2m"]
         if interface.data_width != self.data_width:
             self.logger.info("{} Bus {} from {}-bit to {}-bit.".format(
                 colorer(name),
@@ -298,7 +291,11 @@ class SoCBusHandler(Module):
                 colorer(interface.data_width),
                 colorer(self.data_width)))
             new_interface = wishbone.Interface(data_width=self.data_width)
-            self.submodules += wishbone.Converter(interface, new_interface)
+            if direction == "m2s":
+                converter = wishbone.Converter(master=interface, slave=new_interface)
+            if direction == "s2m":
+                converter = wishbone.Converter(master=new_interface, slave=interface)
+            self.submodules += converter
             return new_interface
         else:
             return interface
@@ -307,10 +304,12 @@ class SoCBusHandler(Module):
         if name is None:
             name = "master{:d}".format(len(self.masters))
         if name in self.masters.keys():
-            self.logger.error("{} already declared as Bus Master:".format(colorer(name, color="red")))
+            self.logger.error("{} {} as Bus Master:".format(
+                colorer(name),
+                colorer("already declared", color="red")))
             self.logger.error(self)
             raise
-        master = self.add_adapter(name, master)
+        master = self.add_adapter(name, master, "m2s")
         self.masters[name] = master
         self.logger.info("{} {} as Bus Master.".format(
             colorer(name,    color="underline"),
@@ -320,24 +319,29 @@ class SoCBusHandler(Module):
         no_name   = name is None
         no_region = region is None
         if no_name and no_region:
-            self.logger.error("Please specify at least {} or {} of Bus Slave".format(
-                colorer("name",   color="red"),
-                colorer("region", color="red")))
+            self.logger.error("Please {} {} or/and {} of Bus Slave.".format(
+                colorer("specify", color="red"),
+                colorer("name"),
+                colorer("region")))
             raise
         if no_name:
             name = "slave{:d}".format(len(self.slaves))
         if no_region:
             region = self.regions.get(name, None)
             if region is None:
-                self.logger.error("Unable to find Region {}".format(colorer(name, color="red")))
+                self.logger.error("{} Region {}.".format(
+                    colorer(name),
+                    colorer("not found", color="red")))
                 raise
         else:
              self.add_region(name, region)
         if name in self.slaves.keys():
-            self.logger.error("{} already declared as Bus Slave:".format(colorer(name, color="red")))
+            self.logger.error("{} {} as Bus Slave:".format(
+                colorer(name),
+                colorer("already declared", color="red")))
             self.logger.error(self)
             raise
-        slave = self.add_adapter(name, slave)
+        slave = self.add_adapter(name, slave, "s2m")
         self.slaves[name] = slave
         self.logger.info("{} {} as Bus Slave.".format(
             colorer(name, color="underline"),
@@ -351,10 +355,6 @@ class SoCBusHandler(Module):
         io_regions = {k: v for k, v in sorted(self.io_regions.items(), key=lambda item: item[1].origin)}
         for name, region in io_regions.items():
            r += colorer(name, color="underline") + " "*(20-len(name)) + ": " + str(region) + "\n"
-        r += "Linker Regions: ({})\n".format(len(self.ld_regions.keys())) if len(self.ld_regions.keys()) else ""
-        ld_regions = {k: v for k, v in sorted(self.ld_regions.items(), key=lambda item: item[1].origin)}
-        for name, region in ld_regions.items():
-           r += colorer(name, color="underline") + " "*(20-len(name)) + ": " + str(region) + "\n"
         r += "Bus Regions: ({})\n".format(len(self.regions.keys())) if len(self.regions.keys()) else ""
         regions = {k: v for k, v in sorted(self.regions.items(), key=lambda item: item[1].origin)}
         for name, region in regions.items():
@@ -382,11 +382,13 @@ class SoCLocHandler(Module):
         allocated = False
         if not (use_loc_if_exists and name in self.locs.keys()):
             if name in self.locs.keys():
-                self.logger.error("{} {} name already used.".format(colorer(name, "red"), self.name))
+                self.logger.error("{} {} name {}.".format(
+                    colorer(name), self.name, colorer("already used", color="red")))
                 self.logger.error(self)
                 raise
             if n in self.locs.values():
-                self.logger.error("{} {} Location already used.".format(colorer(n, "red"), self.name))
+                self.logger.error("{} {} Location {}.".format(
+                    colorer(n), self.name, colorer("already used", color="red")))
                 self.logger.error(self)
                 raise
             if n is None:
@@ -394,15 +396,17 @@ class SoCLocHandler(Module):
                 n = self.alloc(name)
             else:
                 if n < 0:
-                    self.logger.error("{} {} Location should be positive.".format(
-                        colorer(n, color="red"),
-                        self.name))
+                    self.logger.error("{} {} Location should be {}.".format(
+                        colorer(n),
+                        self.name,
+                        colorer("positive", color="red")))
                     raise
                 if n > self.n_locs:
-                    self.logger.error("{} {} Location too high (Up to {}).".format(
-                        colorer(n, color="red"),
+                    self.logger.error("{} {} Location {} than maximum: {}.".format(
+                        colorer(n),
                         self.name,
-                        colorer(self.n_csrs, color="green")))
+                        colorer("higher", color="red"),
+                        colorer(self.n_locs)))
                     raise
             self.locs[name] = n
         else:
@@ -426,55 +430,63 @@ class SoCLocHandler(Module):
     def __str__(self):
         r = "{} Locations: ({})\n".format(self.name, len(self.locs.keys())) if len(self.locs.keys()) else ""
         locs = {k: v for k, v in sorted(self.locs.items(), key=lambda item: item[1])}
+        length = 0
         for name in locs.keys():
-           r += "- {}{}: {}\n".format(colorer(name, color="underline"), " "*(20-len(name)), colorer(self.locs[name]))
+            if len(name) > length: length = len(name)
+        for name in locs.keys():
+           r += "- {}{}: {}\n".format(colorer(name, color="underline"), " "*(length + 1 - len(name)), colorer(self.locs[name]))
         return r
 
 # SoCCSRHandler ------------------------------------------------------------------------------------
 
 class SoCCSRHandler(SoCLocHandler):
     supported_data_width    = [8, 32]
-    supported_address_width = [14, 15]
+    supported_address_width = [14+i for i in range(4)]
     supported_alignment     = [32, 64]
-    supported_paging        = [0x800]
+    supported_paging        = [0x800*2**i for i in range(4)]
 
     # Creation -------------------------------------------------------------------------------------
     def __init__(self, data_width=32, address_width=14, alignment=32, paging=0x800, reserved_csrs={}):
-        SoCLocHandler.__init__(self, "CSR", n_locs=4*2**address_width//paging) # FIXME
+        SoCLocHandler.__init__(self, "CSR", n_locs=alignment//8*(2**address_width)//paging)
         self.logger = logging.getLogger("SoCCSRHandler")
         self.logger.info("Creating CSR Handler...")
 
         # Check Data Width
         if data_width not in self.supported_data_width:
-            self.logger.error("Unsupported data_width: {} supporteds: {:s}".format(
-                colorer(data_width, color="red"),
-                colorer(", ".join(str(x) for x in self.supported_data_width)), color="green"))
+            self.logger.error("Unsupported {} {}, supporteds: {:s}".format(
+                colorer("Data Width", color="red"),
+                colorer(data_width),
+                colorer(", ".join(str(x) for x in self.supported_data_width))))
             raise
 
         # Check Address Width
         if address_width not in self.supported_address_width:
-            self.logger.error("Unsupported address_width: {} supporteds: {:s}".format(
-                colorer(address_width, color="red"),
-                colorer(", ".join(str(x) for x in self.supported_address_width), color="green")))
+            self.logger.error("Unsupported {} {} supporteds: {:s}".format(
+                colorer("Address Width", color="red"),
+                colorer(address_width),
+                colorer(", ".join(str(x) for x in self.supported_address_width))))
             raise
 
         # Check Alignment
         if alignment not in self.supported_alignment:
-            self.logger.error("Unsupported alignment: {} supporteds: {:s}".format(
-                colorer(alignment, color="red"),
-                colorer(", ".join(str(x) for x in self.supported_alignment), color="green")))
+            self.logger.error("Unsupported {}: {} supporteds: {:s}".format(
+                colorer("Alignment", color="red"),
+                colorer(alignment),
+                colorer(", ".join(str(x) for x in self.supported_alignment))))
             raise
         if data_width > alignment:
-            self.logger.error("Alignment ({}) should be >= data_width ({})".format(
-                colorer(alignment,  color="red"),
-                colorer(data_width, color="red")))
+            self.logger.error("Alignment ({}) {} Data Width ({})".format(
+                colorer(alignment),
+                colorer("should be >=", color="red"),
+                colorer(data_width)))
             raise
 
         # Check Paging
         if paging not in self.supported_paging:
-            self.logger.error("Unsupported paging: {} supporteds: {:s}".format(
-                colorer(paging, color="red"),
-                colorer(", ".join(str(x) for x in self.supported_paging), color="green")))
+            self.logger.error("Unsupported {} 0x{}, supporteds: {:s}".format(
+                colorer("Paging", color="red"),
+                colorer("{:x}".format(paging)),
+                colorer(", ".join("0x{:x}".format(x) for x in self.supported_paging))))
             raise
 
         # Create CSR Handler
@@ -484,8 +496,9 @@ class SoCCSRHandler(SoCLocHandler):
         self.paging        = paging
         self.masters       = {}
         self.regions       = {}
-        self.logger.info("{}-bit CSR Bus, {}KiB Address Space, {}B Paging (Up to {} Locations).".format(
+        self.logger.info("{}-bit CSR Bus, {}-bit Aligned, {}KiB Address Space, {}B Paging (Up to {} Locations).".format(
             colorer(self.data_width),
+            colorer(self.alignment),
             colorer(2**self.address_width/2**10),
             colorer(self.paging),
             colorer(self.n_locs)))
@@ -497,20 +510,37 @@ class SoCCSRHandler(SoCLocHandler):
 
         self.logger.info("CSR Handler {}.".format(colorer("created", color="green")))
 
+    # Update CSR Alignment ----------------------------------------------------------------------------
+    def update_alignment(self, alignment):
+        # Check Alignment
+        if alignment not in self.supported_alignment:
+            self.logger.error("Unsupported {}: {} supporteds: {:s}".format(
+                colorer("Alignment", color="red"),
+                colorer(alignment),
+                colorer(", ".join(str(x) for x in self.supported_alignment))))
+            raise
+        self.logger.info("Alignment {} from {}-bit to {}-bit.".format(
+            colorer("updated", color="cyan"),
+            colorer(self.alignment),
+            colorer(alignment)))
+        self.alignment = alignment
+
     # Add Master -----------------------------------------------------------------------------------
     def add_master(self, name=None, master=None):
         if name is None:
             name = "master{:d}".format(len(self.masters))
         if name in self.masters.keys():
-            self.logger.error("{} already declared as CSR Master:".format(colorer(name, color="red")))
+            self.logger.error("{} {} as CSR Master:".format(
+                colorer(name),
+                colorer("already declared", color="red")))
             self.logger.error(self)
             raise
         if master.data_width != self.data_width:
-            self.logger.error("{} Master/Handler data_width {} ({} vs {}).".format(
+            self.logger.error("{} Master/Handler Data Width {} ({} vs {}).".format(
                 colorer(name),
-                colorer("missmatch"),
-                colorer(master.data_width, color="red"),
-                colorer(self.data_width,   color="red")))
+                colorer("missmatch", color="red"),
+                colorer(master.data_width),
+                colorer(self.data_width)))
             raise
         self.masters[name] = master
         self.logger.info("{} {} as CSR Master.".format(
@@ -527,14 +557,18 @@ class SoCCSRHandler(SoCLocHandler):
         if memory is not None:
             name = name + "_" + memory.name_override
         if self.locs.get(name, None) is None:
-            self.logger.error("Undefined {} CSR.".format(colorer(name, color="red")))
+            self.logger.error("CSR {} {}.".format(
+                colorer(name),
+                colorer("not found", color="red")))
+            self.logger.error(self)
             raise
         return self.locs[name]
 
     # Str ------------------------------------------------------------------------------------------
     def __str__(self):
-        r = "{}-bit CSR Bus, {}KiB Address Space, {}B Paging (Up to {} Locations).\n".format(
+        r = "{}-bit CSR Bus, {}-bit Aligned, {}KiB Address Space, {}B Paging (Up to {} Locations).\n".format(
             colorer(self.data_width),
+            colorer(self.alignment),
             colorer(2**self.address_width/2**10),
             colorer(self.paging),
             colorer(self.n_locs))
@@ -605,6 +639,7 @@ class SoCController(Module, AutoCSR):
 # SoC ----------------------------------------------------------------------------------------------
 
 class SoC(Module):
+    mem_map = {}
     def __init__(self, platform, sys_clk_freq,
 
         bus_standard         = "wishbone",
@@ -633,6 +668,8 @@ class SoC(Module):
         self.logger.info(colorer("-"*80, color="bright"))
         self.logger.info(colorer("Creating SoC... ({})".format(build_time())))
         self.logger.info(colorer("-"*80, color="bright"))
+        self.logger.info("FPGA device : {}.".format(platform.device))
+        self.logger.info("System clock: {:3.2f}MHz.".format(sys_clk_freq/1e6))
 
         # SoC attributes ---------------------------------------------------------------------------
         self.platform     = platform
@@ -677,17 +714,21 @@ class SoC(Module):
     # SoC Helpers ----------------------------------------------------------------------------------
     def check_if_exists(self, name):
         if hasattr(self, name):
-            self.logger.error("{} SubModule already declared.".format(colorer(name, "red")))
+            self.logger.error("{} SubModule already {}.".format(
+                colorer(name),
+                colorer("declared", color="red")))
             raise
 
     def add_constant(self, name, value=None):
         name = name.upper()
         if name in self.constants.keys():
-            self.logger.error("{} Constant already declared.".format(colorer(name, "red")))
+            self.logger.error("{} Constant already {}.".format(
+                colorer(name),
+                colorer("declared", color="red")))
             raise
         self.constants[name] = SoCConstant(value)
 
-    def add_config(self, name, value):
+    def add_config(self, name, value=None):
         name = "CONFIG_" + name
         if isinstance(value, str):
             self.add_constant(name + "_" + value)
@@ -717,8 +758,8 @@ class SoC(Module):
     def add_csr_bridge(self, origin):
         self.submodules.csr_bridge = wishbone2csr.WB2CSR(
             bus_csr       = csr_bus.Interface(
-            address_width = self.csr.address_width,
-            data_width    = self.csr.data_width))
+                address_width = self.csr.address_width,
+                data_width    = self.csr.data_width))
         csr_size   = 2**(self.csr.address_width + 2)
         csr_region = SoCRegion(origin=origin, size=csr_size, cached=False)
         self.bus.add_slave("csr", self.csr_bridge.wishbone, csr_region)
@@ -726,40 +767,55 @@ class SoC(Module):
         self.add_config("CSR_DATA_WIDTH", self.csr.data_width)
         self.add_config("CSR_ALIGNMENT",  self.csr.alignment)
 
-    def add_cpu(self, name="vexriscv", variant="standard", reset_address=None):
+    def add_cpu(self, name="vexriscv", variant="standard", cls=None, reset_address=None):
         if name not in cpu.CPUS.keys():
-            self.logger.error("{} CPU not supported, supporteds: {}".format(
-                colorer(name, color="red"),
-                colorer(", ".join(cpu.CPUS.keys()), color="green")))
+            self.logger.error("{} CPU {}, supporteds: {}".format(
+                colorer(name),
+                colorer("not supported", color="red"),
+                colorer(", ".join(cpu.CPUS.keys()))))
             raise
         # Add CPU
-        self.submodules.cpu = cpu.CPUS[name](self.platform, variant)
+        cpu_cls = cls if cls is not None else cpu.CPUS[name]
+        self.submodules.cpu = cpu_cls(self.platform, variant)
         # Update SoC with CPU constraints
         for n, (origin, size) in enumerate(self.cpu.io_regions.items()):
             self.bus.add_region("io{}".format(n), SoCIORegion(origin=origin, size=size, cached=False))
         self.mem_map.update(self.cpu.mem_map) # FIXME
+
+        # We don't want the CSR alignemnt reduced from 64-bit to 32-bit on
+        # a standalone system with a 64-bit WB and no CPU.
+        # Should we instead only update alignment if the CPU is *bigger*
+        # than the CSR ?
+        if name != "None":
+            self.csr.update_alignment(self.cpu.data_width)
         # Add Bus Masters/CSR/IRQs
         if not isinstance(self.cpu, cpu.CPUNone):
             if reset_address is None:
                 reset_address = self.mem_map["rom"]
             self.cpu.set_reset_address(reset_address)
-            for n, cpu_bus in enumerate(self.cpu.buses):
+            for n, cpu_bus in enumerate(self.cpu.periph_buses):
                 self.bus.add_master(name="cpu_bus{}".format(n), master=cpu_bus)
-            self.add_csr("cpu", use_loc_if_exists=True)
-            for name, loc in self.cpu.interrupts.items():
-                self.irq.add(name, loc)
+            self.csr.add("cpu", use_loc_if_exists=True)
+            if hasattr(self.cpu, "interrupt"):
+                for name, loc in self.cpu.interrupts.items():
+                    self.irq.add(name, loc)
+                self.add_config("CPU_HAS_INTERRUPT")
             if hasattr(self, "ctrl"):
                 self.comb += self.cpu.reset.eq(self.ctrl.reset)
             self.add_config("CPU_RESET_ADDR", reset_address)
         # Add constants
         self.add_config("CPU_TYPE",    str(name))
         self.add_config("CPU_VARIANT", str(variant.split('+')[0]))
+        self.add_constant("CONFIG_CPU_HUMAN_NAME", getattr(self.cpu, "human_name", "Unknown"))
+        if hasattr(self.cpu, "nop"):
+            self.add_constant("CONFIG_CPU_NOP", self.cpu.nop)
 
     def add_timer(self, name="timer0"):
         self.check_if_exists(name)
         setattr(self.submodules, name, Timer())
         self.csr.add(name, use_loc_if_exists=True)
-        self.irq.add(name, use_loc_if_exists=True)
+        if hasattr(self.cpu, "interrupt"):
+            self.irq.add(name, use_loc_if_exists=True)
 
     # SoC finalization -----------------------------------------------------------------------------
     def do_finalize(self):
@@ -774,7 +830,14 @@ class SoC(Module):
         # SoC Bus Interconnect ---------------------------------------------------------------------
         bus_masters = self.bus.masters.values()
         bus_slaves  = [(self.bus.regions[n].decoder(self.bus), s) for n, s in self.bus.slaves.items()]
-        if len(bus_masters) and len(bus_slaves):
+        # One master and one slave, use a point to point interconnect, this is useful for
+        # generating standalone components such as LiteDRAM whose external control
+        # interface is a wishbone.
+        if len(bus_masters) == 1 and len(bus_slaves) == 1:
+            self.submodules.bus_interconnect = wishbone.InterconnectPointToPoint(
+                master = list(bus_masters)[0],
+                slave  = list(self.bus.slaves.values())[0])
+        elif len(bus_masters) and len(bus_slaves):
             self.submodules.bus_interconnect = wishbone.InterconnectShared(
                 masters        = bus_masters,
                 slaves         = bus_slaves,
@@ -785,11 +848,12 @@ class SoC(Module):
 
         # SoC CSR Interconnect ---------------------------------------------------------------------
         self.submodules.csr_bankarray = csr_bus.CSRBankArray(self,
-            address_map   = self.csr.address_map,
-            data_width    = self.csr.data_width,
-            address_width = self.csr.address_width,
-            alignment     = self.csr.alignment
-        )
+            address_map        = self.csr.address_map,
+            data_width         = self.csr.data_width,
+            address_width      = self.csr.address_width,
+            alignment          = self.csr.alignment,
+            paging             = self.csr.paging,
+            soc_bus_data_width = self.bus.data_width)
         if len(self.csr.masters):
             self.submodules.csr_interconnect = csr_bus.InterconnectShared(
                 masters = list(self.csr.masters.values()),
@@ -805,9 +869,9 @@ class SoC(Module):
         # Add Memory regions
         for name, memory, mapaddr, mmap in self.csr_bankarray.srams:
             self.csr.add_region(name + "_" + memory.name_override, SoCCSRRegion(
-                origin   = (self.bus.regions["csr"].origin + self.csr.paging*mapaddr),
-                busworkd = self.csr.data_width,
-                obj      = memory))
+                origin  = (self.bus.regions["csr"].origin + self.csr.paging*mapaddr),
+                busword = self.csr.data_width,
+                obj     = memory))
 
         # Sort CSR regions by origin
         self.csr.regions = {k: v for k, v in sorted(self.csr.regions.items(), key=lambda item: item[1].origin)}
@@ -818,12 +882,26 @@ class SoC(Module):
 
         # SoC CPU Check ----------------------------------------------------------------------------
         if not isinstance(self.cpu, cpu.CPUNone):
-            for name in ["rom", "sram"]:
-                if name not in list(self.bus.regions.keys()) + list(self.bus.ld_regions.keys()):
-                    self.logger.error("CPU needs {} Region to be defined as Bus or Linker Region.".format(
-                        colorer(name, color="red")))
-                    self.logger.error(self.bus)
-                    raise
+            if "sram" not in self.bus.regions.keys():
+                self.logger.error("CPU needs {} Region to be {} as Bus or Linker Region.".format(
+                    colorer("sram"),
+                    colorer("defined", color="red")))
+                self.logger.error(self.bus)
+                raise
+            cpu_reset_address_valid = False
+            for name, container in self.bus.regions.items():
+                if self.bus.check_region_is_in(
+                    region    = SoCRegion(origin=self.cpu.reset_address, size=self.bus.data_width//8),
+                    container = container):
+                    cpu_reset_address_valid = True
+                    if name == "rom":
+                        self.cpu.use_rom = True
+            if not cpu_reset_address_valid:
+                self.logger.error("CPU needs {} to be in a {} Region.".format(
+                    colorer("reset address 0x{:08x}".format(self.cpu.reset_address)),
+                    colorer("defined", color="red")))
+                self.logger.error(self.bus)
+                raise
 
         # SoC IRQ Interconnect ---------------------------------------------------------------------
         if hasattr(self, "cpu"):
@@ -834,8 +912,10 @@ class SoC(Module):
                     if hasattr(self, name):
                         module = getattr(self, name)
                         if not hasattr(module, "ev"):
-                            self.logger.error("No EventManager found on {} SubModule".format(
-                                colorer(name, color="red")))
+                            self.logger.error("EventManager {} in {} SubModule.".format(
+                                colorer("not found", color="red"),
+                                colorer(name)))
+                            raise
                         self.comb += self.cpu.interrupt[loc].eq(module.ev.irq)
                     self.add_constant(name + "_INTERRUPT", loc)
 
@@ -851,40 +931,83 @@ class LiteXSoC(SoC):
         self.check_if_exists(name)
         if with_build_time:
             identifier += " " + build_time()
-        setattr(self.submodules, name, Identifier(ident))
+        setattr(self.submodules, name, Identifier(identifier))
         self.csr.add(name + "_mem", use_loc_if_exists=True)
 
     # Add UART -------------------------------------------------------------------------------------
-    def add_uart(self, name, baudrate=115200):
+    def add_uart(self, name, baudrate=115200, fifo_depth=16):
         from litex.soc.cores import uart
+
+        # Stub / Stream
         if name in ["stub", "stream"]:
-            self.submodules.uart = uart.UART()
+            self.submodules.uart = uart.UART(tx_fifo_depth=0, rx_fifo_depth=0)
             if name == "stub":
                 self.comb += self.uart.sink.ready.eq(1)
-        elif name == "bridge":
-            self.submodules.uart = uart.UARTWishboneBridge(
-                pads     = self.platform.request("serial"),
-                clk_freq = self.sys_clk_freq,
-                baudrate = baudrate)
-            self.bus.master(name="uart_bridge", master=self.uart.wishbone)
-        elif name == "crossover":
+
+        # UARTBone / Bridge
+        elif name in ["uartbone", "bridge"]:
+            self.add_uartbone(baudrate=baudrate)
+
+        # Crossover
+        elif name in ["crossover"]:
             self.submodules.uart = uart.UARTCrossover()
+
+        # Model/Sim
+        elif name in ["model", "sim"]:
+            self.submodules.uart_phy = uart.RS232PHYModel(self.platform.request("serial"))
+            self.submodules.uart = ResetInserter()(uart.UART(self.uart_phy,
+                tx_fifo_depth = fifo_depth,
+                rx_fifo_depth = fifo_depth))
+
+        # JTAG Atlantic
+        elif name in ["jtag_atlantic"]:
+            from litex.soc.cores.jtag import JTAGAtlantic
+            self.submodules.uart_phy = JTAGAtlantic()
+            self.submodules.uart = ResetInserter()(uart.UART(self.uart_phy,
+                tx_fifo_depth = fifo_depth,
+                rx_fifo_depth = fifo_depth))
+
+        # JTAG UART
+        elif name in ["jtag_uart"]:
+            from litex.soc.cores.jtag import JTAGPHY
+            self.submodules.uart_phy = JTAGPHY(device=self.platform.device)
+            self.submodules.uart = ResetInserter()(uart.UART(self.uart_phy,
+                tx_fifo_depth = fifo_depth,
+                rx_fifo_depth = fifo_depth))
+
+        # USB ACM (with ValentyUSB core)
+        elif name in ["usb_acm"]:
+            import valentyusb.usbcore.io as usbio
+            import valentyusb.usbcore.cpu.cdc_eptri as cdc_eptri
+            usb_pads = self.platform.request("usb")
+            usb_iobuf = usbio.IoBuf(usb_pads.d_p, usb_pads.d_n, usb_pads.pullup)
+            self.submodules.uart = cdc_eptri.CDCUsb(usb_iobuf)
+
+        # Classic UART
         else:
-            if name == "jtag_atlantic":
-                from litex.soc.cores.jtag import JTAGAtlantic
-                self.submodules.uart_phy = JTAGAtlantic()
-            elif name == "jtag_uart":
-                from litex.soc.cores.jtag import JTAGPHY
-                self.submodules.uart_phy = JTAGPHY(device=self.platform.device)
-            else:
-                self.submodules.uart_phy = uart.UARTPHY(
-                    pads     = self.platform.request(name),
-                    clk_freq = self.sys_clk_freq,
-                    baudrate = baudrate)
-            self.submodules.uart = ResetInserter()(uart.UART(self.uart_phy))
+            self.submodules.uart_phy = uart.UARTPHY(
+                pads     = self.platform.request(name),
+                clk_freq = self.sys_clk_freq,
+                baudrate = baudrate)
+            self.submodules.uart = ResetInserter()(uart.UART(self.uart_phy,
+                tx_fifo_depth = fifo_depth,
+                rx_fifo_depth = fifo_depth))
+
         self.csr.add("uart_phy", use_loc_if_exists=True)
         self.csr.add("uart", use_loc_if_exists=True)
-        self.irq.add("uart", use_loc_if_exists=True)
+        if hasattr(self.cpu, "interrupt"):
+            self.irq.add("uart", use_loc_if_exists=True)
+        else:
+            self.add_constant("UART_POLLING")
+
+    # Add UARTbone ---------------------------------------------------------------------------------
+    def add_uartbone(self, name="serial", baudrate=115200):
+        from litex.soc.cores import uart
+        self.submodules.uartbone = uart.UARTBone(
+            pads     = self.platform.request(name),
+            clk_freq = self.sys_clk_freq,
+            baudrate = baudrate)
+        self.bus.add_master(name="uartbone", master=self.uartbone.wishbone)
 
     # Add SDRAM ------------------------------------------------------------------------------------
     def add_sdram(self, name, phy, module, origin, size=None,
@@ -894,7 +1017,13 @@ class LiteXSoC(SoC):
         l2_cache_full_memory_we = True,
         **kwargs):
 
-        # LiteDRAM core ----------------------------------------------------------------------------
+        # Imports
+        from litedram.common import LiteDRAMNativePort
+        from litedram.core import LiteDRAMCore
+        from litedram.frontend.wishbone import LiteDRAMWishbone2Native
+        from litedram.frontend.axi import LiteDRAMAXI2Native
+
+        # LiteDRAM core
         self.submodules.sdram = LiteDRAMCore(
             phy             = phy,
             geom_settings   = module.geom_settings,
@@ -903,54 +1032,78 @@ class LiteXSoC(SoC):
             **kwargs)
         self.csr.add("sdram")
 
-        # LiteDRAM port ----------------------------------------------------------------------------
-        port = self.sdram.crossbar.get_port()
-        port.data_width = 2**int(log2(port.data_width)) # Round to nearest power of 2
-
-        # SDRAM size -------------------------------------------------------------------------------
+        # Compute/Check SDRAM size
         sdram_size = 2**(module.geom_settings.bankbits +
                          module.geom_settings.rowbits +
                          module.geom_settings.colbits)*phy.settings.databits//8
         if size is not None:
             sdram_size = min(sdram_size, size)
-        self.bus.add_region("main_ram", SoCRegion(origin=origin, size=sdram_size))
+
+        # Add SDRAM region
+        if self.cpu_type is not None:
+            self.bus.add_region("main_ram", SoCRegion(origin=origin, size=sdram_size))
 
         # SoC [<--> L2 Cache] <--> LiteDRAM --------------------------------------------------------
-        if self.cpu.name == "rocket":
-            # Rocket has its own I/D L1 cache: connect directly to LiteDRAM when possible.
-            if port.data_width == self.cpu.mem_axi.data_width:
-                self.logger.info("Matching AXI MEM data width ({})\n".format(port.data_width))
-                self.submodules += LiteDRAMAXI2Native(
-                    axi          = self.cpu.mem_axi,
-                    port         = port,
-                    base_address = self.bus.regions["main_ram"].origin)
-            else:
-                self.logger.info("Converting MEM data width: {} to {} via Wishbone".format(
-                    port.data_width,
-                    self.cpu.mem_axi.data_width))
-                # FIXME: replace WB data-width converter with native AXI converter!!!
-                mem_wb  = wishbone.Interface(
-                    data_width = self.cpu.mem_axi.data_width,
-                    adr_width  = 32-log2_int(self.cpu.mem_axi.data_width//8))
-                # NOTE: AXI2Wishbone FSMs must be reset with the CPU!
-                mem_a2w = ResetInserter()(axi.AXI2Wishbone(
-                    axi          = self.cpu.mem_axi,
-                    wishbone     = mem_wb,
-                    base_address = 0))
-                self.comb += mem_a2w.reset.eq(ResetSignal() | self.cpu.reset)
-                self.submodules += mem_a2w
-                litedram_wb = wishbone.Interface(port.data_width)
-                self.submodules += LiteDRAMWishbone2Native(
-                    wishbone     = litedram_wb,
-                    port         = port,
-                    base_address = origin)
-                self.submodules += wishbone.Converter(mem_wb, litedram_wb)
-        elif self.with_wishbone:
-            # Wishbone Slave SDRAM interface -------------------------------------------------------
+        if len(self.cpu.memory_buses):
+            # When CPU has at least a direct memory bus, connect them directly to LiteDRAM.
+            for mem_bus in self.cpu.memory_buses:
+                # Request a LiteDRAM native port.
+                port = self.sdram.crossbar.get_port()
+                port.data_width = 2**int(log2(port.data_width)) # Round to nearest power of 2.
+
+                # Check if bus is an AXI bus and connect it.
+                if isinstance(mem_bus, axi.AXIInterface):
+                    # If same data_width, connect it directly.
+                    if port.data_width == mem_bus.data_width:
+                        self.logger.info("Matching AXI MEM data width ({})\n".format(port.data_width))
+                        self.submodules += LiteDRAMAXI2Native(
+                            axi          = self.cpu.mem_axi,
+                            port         = port,
+                            base_address = self.bus.regions["main_ram"].origin)
+                    # If different data_width, do the adaptation and connect it via Wishbone.
+                    else:
+                        self.logger.info("Converting MEM data width: {} to {} via Wishbone".format(
+                            port.data_width,
+                            self.cpu.mem_axi.data_width))
+                        # FIXME: replace WB data-width converter with native AXI converter!!!
+                        mem_wb  = wishbone.Interface(
+                            data_width = self.cpu.mem_axi.data_width,
+                            adr_width  = 32-log2_int(self.cpu.mem_axi.data_width//8))
+                        # NOTE: AXI2Wishbone FSMs must be reset with the CPU!
+                        mem_a2w = ResetInserter()(axi.AXI2Wishbone(
+                            axi          = self.cpu.mem_axi,
+                            wishbone     = mem_wb,
+                            base_address = 0))
+                        self.comb += mem_a2w.reset.eq(ResetSignal() | self.cpu.reset)
+                        self.submodules += mem_a2w
+                        litedram_wb = wishbone.Interface(port.data_width)
+                        self.submodules += LiteDRAMWishbone2Native(
+                            wishbone     = litedram_wb,
+                            port         = port,
+                            base_address = origin)
+                        self.submodules += wishbone.Converter(mem_wb, litedram_wb)
+                # Check if bus is a Native bus and connect it.
+                if isinstance(mem_bus, LiteDRAMNativePort):
+                    # If same data_width, connect it directly.
+                    if port.data_width == mem_bus.data_width:
+                        self.comb += mem_bus.cmd.connect(port.cmd)
+                        self.comb += mem_bus.wdata.connect(port.wdata)
+                        self.comb += port.rdata.connect(mem_bus.rdata)
+                    # Else raise Error.
+                    else:
+                        raise NotImplementedError
+        elif self.cpu_type is not None:
+            # When CPU has no direct memory interface, create a Wishbone Slave interface to LiteDRAM.
+
+            # Request a LiteDRAM native port.
+            port = self.sdram.crossbar.get_port()
+            port.data_width = 2**int(log2(port.data_width)) # Round to nearest power of 2.
+
+            # Create Wishbone Slave.
             wb_sdram = wishbone.Interface()
-            self.bus.add_slave("main_ram", wb_sdram, SoCRegion(origin=origin, size=sdram_size))
+            self.bus.add_slave("main_ram", wb_sdram)
 
-            # L2 Cache -----------------------------------------------------------------------------
+            # L2 Cache
             if l2_cache_size != 0:
                 # Insert L2 cache inbetween Wishbone bus and LiteDRAM
                 l2_cache_size = max(l2_cache_size, int(2*port.data_width/8)) # Use minimal size if lower
@@ -970,5 +1123,107 @@ class LiteXSoC(SoC):
                 self.submodules += wishbone.Converter(wb_sdram, litedram_wb)
             self.add_config("L2_SIZE", l2_cache_size)
 
-            # Wishbone Slave <--> LiteDRAM bridge --------------------------------------------------
-            self.submodules.wishbone_bridge = LiteDRAMWishbone2Native(litedram_wb, port)
+            # Wishbone Slave <--> LiteDRAM bridge
+            self.submodules.wishbone_bridge = LiteDRAMWishbone2Native(litedram_wb, port,
+                base_address = self.bus.regions["main_ram"].origin)
+
+    # Add Ethernet ---------------------------------------------------------------------------------
+    def add_ethernet(self, name="ethmac", phy=None):
+        # Imports
+        from liteeth.mac import LiteEthMAC
+        # MAC
+        ethmac = LiteEthMAC(
+            phy        = phy,
+            dw         = 32,
+            interface  = "wishbone",
+            endianness = self.cpu.endianness)
+        setattr(self.submodules, name, ethmac)
+        ethmac_region = SoCRegion(origin=self.mem_map.get(name, None), size=0x2000, cached=False)
+        self.bus.add_slave(name=name, slave=ethmac.bus, region=ethmac_region)
+        self.add_csr(name)
+        self.add_interrupt(name)
+        # Timing constraints
+        if hasattr(phy, "crg"):
+            eth_rx_clk = phy.crg.cd_eth_rx.clk
+            eth_tx_clk = phy.crg.cd_eth_tx.clk
+        else:
+            eth_rx_clk = phy.cd_eth_rx.clk
+            eth_tx_clk = phy.cd_eth_tx.clk
+        self.platform.add_period_constraint(eth_rx_clk, 1e9/phy.rx_clk_freq)
+        self.platform.add_period_constraint(eth_tx_clk, 1e9/phy.tx_clk_freq)
+        self.platform.add_false_path_constraints(
+            self.crg.cd_sys.clk,
+            eth_rx_clk,
+            eth_tx_clk)
+
+    # Add Etherbone --------------------------------------------------------------------------------
+    def add_etherbone(self, name="etherbone", phy=None, clock_domain=None,
+        mac_address = 0x10e2d5000000,
+        ip_address  = "192.168.1.50",
+        udp_port    = 1234):
+        # Imports
+        from liteeth.core import LiteEthUDPIPCore
+        from liteeth.frontend.etherbone import LiteEthEtherbone
+        # Core
+        ethcore = LiteEthUDPIPCore(
+            phy         = self.ethphy,
+            mac_address = mac_address,
+            ip_address  = ip_address,
+            clk_freq    = self.clk_freq)
+        if clock_domain is not None: # FIXME: Could probably be avoided.
+            ethcore = ClockDomainsRenamer("eth_tx")(ethcore)
+        self.submodules += ethcore
+
+        # Clock domain renaming
+        if clock_domain is not None: # FIXME: Could probably be avoided.
+            self.clock_domains.cd_etherbone = ClockDomain("etherbone")
+            self.comb += self.cd_etherbone.clk.eq(ClockSignal(clock_domain))
+            self.comb += self.cd_etherbone.rst.eq(ResetSignal(clock_domain))
+            clock_domain = "etherbone"
+        else:
+            clock_domain = "sys"
+
+        # Etherbone
+        etherbone = LiteEthEtherbone(ethcore.udp, udp_port, cd=clock_domain)
+        setattr(self.submodules, name, etherbone)
+        self.add_wb_master(etherbone.wishbone.bus)
+        # Timing constraints
+        if hasattr(phy, "crg"):
+            eth_rx_clk = phy.crg.cd_eth_rx.clk
+            eth_tx_clk = phy.crg.cd_eth_tx.clk
+        else:
+            eth_rx_clk = phy.cd_eth_rx.clk
+            eth_tx_clk = phy.cd_eth_tx.clk
+        self.platform.add_period_constraint(eth_rx_clk, 1e9/phy.rx_clk_freq)
+        self.platform.add_period_constraint(eth_tx_clk, 1e9/phy.tx_clk_freq)
+        self.platform.add_false_path_constraints(
+            self.crg.cd_sys.clk,
+            eth_rx_clk,
+            eth_tx_clk)
+
+    # Add SPI Flash --------------------------------------------------------------------------------
+    def add_spi_flash(self, name="spiflash", mode="4x", dummy_cycles=None, clk_freq=None):
+        assert dummy_cycles is not None                 # FIXME: Get dummy_cycles from SPI Flash
+        assert mode in ["1x", "4x"]
+        if clk_freq is None: clk_freq = self.clk_freq/2 # FIXME: Get max clk_freq from SPI Flash
+        spiflash = SpiFlash(
+            pads         = self.platform.request(name if mode == "1x" else name + mode),
+            dummy        = dummy_cycles,
+            div          = ceil(self.clk_freq/clk_freq),
+            with_bitbang = True,
+            endianness   = self.cpu.endianness)
+        spiflash.add_clk_primitive(self.platform.device)
+        setattr(self.submodules, name, spiflash)
+        self.add_memory_region(name, self.mem_map[name], 0x1000000) # FIXME: Get size from SPI Flash
+        self.add_wb_slave(self.mem_map[name], spiflash.bus)
+        self.add_csr(name)
+
+    # Add SPI SDCard -------------------------------------------------------------------------------
+    def add_spi_sdcard(self, name="spisdcard", clk_freq=400e3):
+        pads = self.platform.request(name)
+        if hasattr(pads, "rst"):
+            self.comb += pads.rst.eq(0)
+        spisdcard = SPIMaster(pads, 8, self.sys_clk_freq, 400e3)
+        spisdcard.add_clk_divider()
+        setattr(self.submodules, name, spisdcard)
+        self.add_csr(name)