From 50bbc2c5c342dc25c040eadbd505bc244c1853c2 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Jean-Fran=C3=A7ois=20Nguyen?= Date: Fri, 29 Oct 2021 19:31:23 +0200 Subject: [PATCH] soc.cpu: generate BIOS configuration from SoC constants. Also, merge examples/{sdram,sram}_soc as minerva_soc. --- examples/minerva_soc.py | 395 ++++++++++++++++++++++++++++++++++++ examples/sdram_soc.py | 170 ---------------- examples/sram_soc.py | 107 ---------- lambdasoc/cores/litedram.py | 45 ++-- lambdasoc/cores/utils.py | 25 +++ lambdasoc/soc/base.py | 2 + lambdasoc/soc/cpu.py | 163 +++++++++------ setup.py | 1 + 8 files changed, 541 insertions(+), 367 deletions(-) create mode 100644 examples/minerva_soc.py delete mode 100644 examples/sdram_soc.py delete mode 100644 examples/sram_soc.py create mode 100644 lambdasoc/cores/utils.py diff --git a/examples/minerva_soc.py b/examples/minerva_soc.py new file mode 100644 index 0000000..02675f2 --- /dev/null +++ b/examples/minerva_soc.py @@ -0,0 +1,395 @@ +import argparse +from collections import OrderedDict + +from nmigen import * +from nmigen.lib.cdc import ResetSynchronizer +from nmigen.build import * + +from nmigen_soc import wishbone +from nmigen_soc.periph import ConstantMap + +from nmigen_stdio.serial import AsyncSerial + +from nmigen_boards.arty_a7 import ArtyA7_35Platform +from nmigen_boards.ecpix5 import ECPIX545Platform, ECPIX585Platform + +from lambdasoc.cpu.minerva import MinervaCPU +from lambdasoc.periph.intc import GenericInterruptController +from lambdasoc.periph.serial import AsyncSerialPeripheral +from lambdasoc.periph.sram import SRAMPeripheral +from lambdasoc.periph.timer import TimerPeripheral +from lambdasoc.periph.sdram import SDRAMPeripheral + +from lambdasoc.soc.cpu import CPUSoC, BIOSBuilder + +from lambdasoc.cores.pll.lattice_ecp5 import PLL_LatticeECP5 +from lambdasoc.cores.pll.xilinx_7series import PLL_Xilinx7Series +from lambdasoc.cores import litedram +from lambdasoc.cores.utils import request_bare + +from lambdasoc.sim.blackboxes.serial import AsyncSerial_Blackbox +from lambdasoc.sim.platform import CXXRTLPlatform + + +__all__ = ["MinervaSoC"] + + +class _ClockResetGenerator(Elaboratable): + def __init__(self, *, sync_clk_freq, with_sdram): + if not isinstance(sync_clk_freq, (int, float)) or sync_clk_freq <= 0: + raise ValueError("Sync domain clock frequency must be a positive integer or float, " + "not {!r}" + .format(sync_clk_freq)) + self.sync_clk_freq = sync_clk_freq + self.with_sdram = bool(with_sdram) + + def elaborate(self, platform): + m = Module() + + m.domains += [ + ClockDomain("_ref", reset_less=platform.default_rst is None, local=True), + ClockDomain("sync"), + ] + + m.d.comb += ClockSignal("_ref").eq(platform.request(platform.default_clk, 0).i) + if platform.default_rst is not None: + m.d.comb += ResetSignal("_ref").eq(platform.request(platform.default_rst, 0).i) + + # The LiteDRAM core provides its own PLL, which drives the litedram_user clock domain. + # We reuse this clock domain as the sync domain, in order to avoid CDC between LiteDRAM + # and the SoC interconnect. + if self.with_sdram: + m.domains += ClockDomain("litedram_input") + m.d.comb += ClockSignal("litedram_input").eq(ClockSignal("_ref")) + if platform.default_rst is not None: + m.d.comb += ResetSignal("litedram_input").eq(ResetSignal("_ref")) + + m.domains += ClockDomain("litedram_user") + m.d.comb += [ + ClockSignal("sync").eq(ClockSignal("litedram_user")), + ResetSignal("sync").eq(ResetSignal("litedram_user")), + ] + + # In simulation mode, the sync clock domain is directly driven by the platform clock. + elif isinstance(platform, CXXRTLPlatform): + assert self.sync_clk_freq == platform.default_clk_frequency + m.d.comb += ClockSignal("sync").eq(ClockSignal("_ref")) + if platform.default_rst is not None: + m.d.comb += ResetSignal("sync").eq(ResetSignal("_ref")) + + # Otherwise, we use a PLL to drive the sync clock domain. + else: + if isinstance(platform, ArtyA7_35Platform): + sync_pll_params = PLL_Xilinx7Series.Parameters( + i_domain = "_ref", + i_freq = platform.default_clk_frequency, + i_reset_less = platform.default_rst is None, + o_domain = "sync", + o_freq = self.sync_clk_freq, + ) + m.submodules.sync_pll = sync_pll = PLL_Xilinx7Series(sync_pll_params) + elif isinstance(platform, (ECPIX545Platform, ECPIX585Platform)): + sync_pll_params = PLL_LatticeECP5.Parameters( + i_domain = "_ref", + i_freq = platform.default_clk_frequency, + i_reset_less = platform.default_rst is None, + o_domain = "sync", + o_freq = self.sync_clk_freq, + ) + m.submodules.sync_pll = sync_pll = PLL_LatticeECP5(sync_pll_params) + else: + assert False + + if self.platform.default_rst is not None: + sync_pll_arst = ~sync_pll.locked | ResetSignal("_ref") + else: + sync_pll_arst = ~sync_pll.locked + + m.submodules += ResetSynchronizer(sync_pll_arst, domain="sync") + + return m + + +class MinervaSoC(CPUSoC, Elaboratable): + def __init__(self, + sync_clk_freq, + cpu_core, + bootrom_addr, + bootrom_size, + scratchpad_addr, + scratchpad_size, + uart_core, + uart_addr, + uart_irqno, + timer_addr, + timer_width, + timer_irqno): + if not isinstance(sync_clk_freq, (int, float)) or sync_clk_freq <= 0: + raise ValueError("Sync domain clock frequency must be a positive integer or float, " + "not {!r}" + .format(sync_clk_freq)) + self.sync_clk_freq = int(sync_clk_freq) + + if not isinstance(cpu_core, MinervaCPU): + raise TypeError("CPU core must be an instance of MinervaCPU, not {!r}" + .format(cpu_core)) + self.cpu = cpu_core + + self._arbiter = wishbone.Arbiter(addr_width=30, data_width=32, granularity=8, + features={"cti", "bte", "err"}) + self._decoder = wishbone.Decoder(addr_width=30, data_width=32, granularity=8, + features={"cti", "bte", "err"}) + + self._arbiter.add(self.cpu.ibus) + self._arbiter.add(self.cpu.dbus) + + self.intc = GenericInterruptController(width=len(self.cpu.ip)) + + self.bootrom = SRAMPeripheral(size=bootrom_size, writable=False) + self._decoder.add(self.bootrom.bus, addr=bootrom_addr) + + self.scratchpad = SRAMPeripheral(size=scratchpad_size) + self._decoder.add(self.scratchpad.bus, addr=scratchpad_addr) + + self.uart = AsyncSerialPeripheral(core=uart_core) + self._decoder.add(self.uart.bus, addr=uart_addr) + self.intc.add_irq(self.uart.irq, index=uart_irqno) + + self.timer = TimerPeripheral(width=timer_width) + self._decoder.add(self.timer.bus, addr=timer_addr) + self.intc.add_irq(self.timer.irq, index=timer_irqno) + + self._sdram = None + self._sram = None + + @property + def memory_map(self): + return self._decoder.bus.memory_map + + @property + def constants(self): + return super().constants.union( + SDRAM = self.sdram .constant_map if self.sdram is not None else None, + SOC = ConstantMap( + WITH_SDRAM = self.sdram is not None, + MEMTEST_ADDR_SIZE = 8192, + MEMTEST_DATA_SIZE = 8192, + ), + ) + + @property + def mainram(self): + assert not (self._sdram and self.sram) + return self._sdram or self._sram + + @property + def sdram(self): + return self._sdram + + @property + def sram(self): + return self._sram + + def add_sdram(self, core, *, addr, cache_size): + if self.mainram is not None: + raise AttributeError("Main RAM has already been set to {!r}".format(self.mainram)) + if core.config.user_clk_freq != self.sync_clk_freq: + raise ValueError("LiteDRAM user domain clock frequency ({} MHz) must match sync " + "domain clock frequency ({} MHz)" + .format(core.config.user_clk_freq / 1e6, self.sync_clk_freq / 1e6)) + self._sdram = SDRAMPeripheral(core=core, cache_size=cache_size) + self._decoder.add(self._sdram.bus, addr=addr) + + def add_internal_sram(self, *, addr, size): + if self.mainram is not None: + raise AttributeError("Main RAM has already been set to {!r}".format(self.mainram)) + self._sram = SRAMPeripheral(size=size) + self._decoder.add(self._sram.bus, addr=addr) + + def elaborate(self, platform): + m = Module() + + m.submodules.crg = _ClockResetGenerator( + sync_clk_freq = self.sync_clk_freq, + with_sdram = self.sdram is not None, + ) + + m.submodules.cpu = self.cpu + m.submodules.arbiter = self._arbiter + m.submodules.decoder = self._decoder + m.submodules.uart = self.uart + m.submodules.timer = self.timer + m.submodules.intc = self.intc + m.submodules.bootrom = self.bootrom + m.submodules.scratchpad = self.scratchpad + + if self.sdram is not None: + m.submodules.sdram = self.sdram + if self.sram is not None: + m.submodules.sram = self.sram + + m.d.comb += [ + self._arbiter.bus.connect(self._decoder.bus), + self.cpu.ip.eq(self.intc.ip), + ] + + return m + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--build-dir", type=str, + default="build/minerva_soc", + help="local build directory (default: 'build/sdram_soc')") + parser.add_argument("--platform", type=str, + choices=("sim", "arty_a7", "ecpix5_45", "ecpix5_85"), + default="sim", + help="target platform") + parser.add_argument("--sync-clk-freq", type=int, + default=75, + help="SoC clock frequency, in MHz. (default: 75)") + parser.add_argument("--with-sdram", action="store_true", + help="enable SDRAM") + parser.add_argument("--internal-sram-size", type=int, + default=8192, + help="Internal RAM size, in bytes. Ignored if --with-sdram is provided. " + "(default: 8192)") + parser.add_argument("--baudrate", type=int, + default=9600, + help="UART baudrate (default: 9600)") + args = parser.parse_args() + + # Platform selection + + if args.platform == "sim": + platform = CXXRTLPlatform() + elif args.platform == "arty_a7": + platform = ArtyA7_35Platform() + elif args.platform == "ecpix5_45": + platform = ECPIX545Platform() + elif args.platform == "ecpix5_85": + platform = ECPIX585Platform() + else: + assert False + + # LiteDRAM + + if args.with_sdram: + if isinstance(platform, CXXRTLPlatform): + litedram_config = litedram.ECP5Config( + memtype = "DDR3", + module_name = "MT41K256M16", + module_bytes = 2, + module_ranks = 1, + input_clk_freq = int(platform.default_clk_frequency), + user_clk_freq = int(platform.default_clk_frequency), + init_clk_freq = int(1e6), + ) + elif isinstance(platform, ArtyA7_35Platform): + litedram_config = litedram.Artix7Config( + memtype = "DDR3", + speedgrade = "-1", + cmd_latency = 0, + module_name = "MT41K128M16", + module_bytes = 2, + module_ranks = 1, + rtt_nom = 60, + rtt_wr = 60, + ron = 34, + input_clk_freq = int(platform.default_clk_frequency), + user_clk_freq = int(args.sync_clk_freq * 1e6), + iodelay_clk_freq = int(200e6), + ) + elif isinstance(platform, (ECPIX545Platform, ECPIX585Platform)): + litedram_config = litedram.ECP5Config( + memtype = "DDR3", + module_name = "MT41K256M16", + module_bytes = 2, + module_ranks = 1, + input_clk_freq = int(platform.default_clk_frequency), + user_clk_freq = int(args.sync_clk_freq * 1e6), + init_clk_freq = int(25e6), + ) + else: + assert False + + if isinstance(platform, CXXRTLPlatform): + litedram_pins = None + else: + litedram_pins = request_bare(platform, "ddr3", 0) + + litedram_core = litedram.Core(litedram_config, pins=litedram_pins) + litedram_core.build(litedram.Builder(), platform, args.build_dir, + sim=isinstance(platform, CXXRTLPlatform)) + mainram_size = litedram_core.size + else: + litedram_core = None + mainram_size = args.internal_sram_size + + # UART + + if isinstance(platform, CXXRTLPlatform): + uart_core = AsyncSerial_Blackbox( + data_bits = 8, + divisor = 1, + ) + else: + uart_core = AsyncSerial( + data_bits = 8, + divisor = int(args.sync_clk_freq * 1e6 // args.baudrate), + pins = platform.request("uart", 0), + ) + + # SoC and BIOS + + if isinstance(platform, CXXRTLPlatform): + sync_clk_freq = platform.default_clk_frequency + else: + sync_clk_freq = int(args.sync_clk_freq * 1e6) + + soc = MinervaSoC( + sync_clk_freq = sync_clk_freq, + + cpu_core = MinervaCPU( + reset_address = 0x00000000, + with_icache = True, + icache_nlines = 16, + icache_nwords = 4, + icache_nways = 1, + icache_base = 0x40000000, + icache_limit = 0x40000000 + mainram_size, + with_dcache = True, + dcache_nlines = 16, + dcache_nwords = 4, + dcache_nways = 1, + dcache_base = 0x40000000, + dcache_limit = 0x40000000 + mainram_size, + with_muldiv = True, + ), + + bootrom_addr = 0x00000000, + bootrom_size = 0x8000, + scratchpad_addr = 0x00008000, + scratchpad_size = 0x1000, + + uart_addr = 0x80000000, + uart_core = uart_core, + uart_irqno = 1, + timer_addr = 0x80001000, + timer_width = 32, + timer_irqno = 0, + ) + + if args.with_sdram: + soc.add_sdram(litedram_core, addr=0x40000000, cache_size=4096) + else: + soc.add_internal_sram(addr=0x40000000, size=args.internal_sram_size) + + soc.build(build_dir=args.build_dir, do_init=True) + + if isinstance(platform, CXXRTLPlatform): + platform.build(soc, build_dir=args.build_dir, blackboxes={ + "lambdasoc.sim.blackboxes.serial": "serial_pty", + }) + else: + platform.build(soc, build_dir=args.build_dir, do_program=True) diff --git a/examples/sdram_soc.py b/examples/sdram_soc.py deleted file mode 100644 index d3f5dcb..0000000 --- a/examples/sdram_soc.py +++ /dev/null @@ -1,170 +0,0 @@ -import argparse - -from nmigen import * -from nmigen.build import * -from nmigen_soc import wishbone - -from lambdasoc.cpu.minerva import MinervaCPU -from lambdasoc.periph.intc import GenericInterruptController -from lambdasoc.periph.serial import AsyncSerialPeripheral -from lambdasoc.periph.sram import SRAMPeripheral -from lambdasoc.periph.timer import TimerPeripheral -from lambdasoc.periph.sdram import SDRAMPeripheral -from lambdasoc.soc.cpu import CPUSoC - -from lambdasoc.cores import litedram - - -__all__ = ["SDRAMSoC"] - - -class SDRAMSoC(CPUSoC, Elaboratable): - def __init__(self, *, reset_addr, clk_freq, - rom_addr, rom_size, - ram_addr, ram_size, - uart_addr, uart_divisor, uart_pins, - timer_addr, timer_width, - sdram_addr, sdram_core, sdram_cache_size): - self._arbiter = wishbone.Arbiter(addr_width=30, data_width=32, granularity=8, - features={"cti", "bte"}) - self._decoder = wishbone.Decoder(addr_width=30, data_width=32, granularity=8, - features={"cti", "bte"}) - - self.cpu = MinervaCPU( - reset_address=reset_addr, - with_icache=True, icache_nlines=64, icache_nwords=4, icache_nways=1, - icache_base=sdram_addr, icache_limit=sdram_addr + sdram_core.size, - with_dcache=True, dcache_nlines=64, dcache_nwords=4, dcache_nways=1, - dcache_base=sdram_addr, dcache_limit=sdram_addr + sdram_core.size, - with_muldiv=True, - ) - self._arbiter.add(self.cpu.ibus) - self._arbiter.add(self.cpu.dbus) - - self.rom = SRAMPeripheral(size=rom_size, writable=False) - self._decoder.add(self.rom.bus, addr=rom_addr) - - self.ram = SRAMPeripheral(size=ram_size) - self._decoder.add(self.ram.bus, addr=ram_addr) - - self.sdram = SDRAMPeripheral(core=sdram_core, cache_size=sdram_cache_size) - self._decoder.add(self.sdram.bus, addr=sdram_addr) - - self.uart = AsyncSerialPeripheral(divisor=uart_divisor, pins=uart_pins) - self._decoder.add(self.uart.bus, addr=uart_addr) - - self.timer = TimerPeripheral(width=timer_width) - self._decoder.add(self.timer.bus, addr=timer_addr) - - self.intc = GenericInterruptController(width=len(self.cpu.ip)) - self.intc.add_irq(self.timer.irq, 0) - self.intc.add_irq(self.uart .irq, 1) - - self.memory_map = self._decoder.bus.memory_map - - self.clk_freq = clk_freq - - def elaborate(self, platform): - m = Module() - - m.domains += [ - ClockDomain("litedram_input"), - ClockDomain("litedram_user"), - ClockDomain("sync"), - ] - - m.d.comb += [ - ClockSignal("litedram_input").eq(platform.request("clk100", 0).i), - - ClockSignal("sync").eq(ClockSignal("litedram_user")), - ResetSignal("sync").eq(ResetSignal("litedram_user")), - ] - - m.submodules.arbiter = self._arbiter - m.submodules.cpu = self.cpu - - m.submodules.decoder = self._decoder - m.submodules.rom = self.rom - m.submodules.ram = self.ram - m.submodules.sdram = self.sdram - m.submodules.uart = self.uart - m.submodules.timer = self.timer - m.submodules.intc = self.intc - - m.d.comb += [ - self._arbiter.bus.connect(self._decoder.bus), - self.cpu.ip.eq(self.intc.ip), - ] - - return m - - -if __name__ == "__main__": - parser = argparse.ArgumentParser() - parser.add_argument("--platform", type=str, - choices=("arty_a7", "ecpix5_85"), - help="target platform") - parser.add_argument("--baudrate", type=int, - default=9600, - help="UART baudrate (default: 9600)") - parser.add_argument("--build-dir", type=str, - default="build", - help="local build directory (default: 'build')") - args = parser.parse_args() - - if args.platform == "arty_a7": - from nmigen_boards.arty_a7 import ArtyA7_35Platform - platform = ArtyA7_35Platform() - litedram_cfg = litedram.Artix7Config( - memtype = "DDR3", - speedgrade = "-1", - cmd_latency = 0, - module_name = "MT41K128M16", - module_bytes = 2, - module_ranks = 1, - rtt_nom = 60, - rtt_wr = 60, - ron = 34, - input_clk_freq = int(100e6), - user_clk_freq = int(100e6), - iodelay_clk_freq = int(200e6), - ) - elif args.platform == "ecpix5_85": - from nmigen_boards.ecpix5 import ECPIX585Platform - platform = ECPIX585Platform() - litedram_cfg = litedram.ECP5Config( - memtype = "DDR3", - module_name = "MT41K256M16", - module_bytes = 2, - module_ranks = 1, - input_clk_freq = int(100e6), - user_clk_freq = int(70e6), - init_clk_freq = int(25e6), - ) - else: - assert False - - litedram_pins = litedram_cfg.request_pins(platform, "ddr3", 0) - litedram_core = litedram.Core(litedram_cfg, pins=litedram_pins) - - litedram_builder = litedram.Builder() - litedram_build_dir = f"{args.build_dir}/litedram" - litedram_products = litedram_core.build(litedram_builder, build_dir=litedram_build_dir) - - litedram_core_v = f"{litedram_core.name}/{litedram_core.name}.v" - platform.add_file(litedram_core_v, litedram_products.get(litedram_core_v, mode="t")) - - soc = SDRAMSoC( - reset_addr=0x30000000, clk_freq=litedram_cfg.user_clk_freq, - uart_addr=0x00005000, uart_divisor=int(litedram_cfg.user_clk_freq // args.baudrate), - uart_pins=platform.request("uart", 0), - timer_addr=0x00006000, timer_width=32, - - rom_addr=0x30000000, rom_size=0x8000, - ram_addr=0x30008000, ram_size=0x1000, - sdram_addr=0x40000000, sdram_core=litedram_core, sdram_cache_size=8192, - ) - - soc.build(build_dir=f"{args.build_dir}/soc", litedram_dir=litedram_build_dir, do_init=True) - - platform.build(soc, build_dir=args.build_dir, do_program=True) diff --git a/examples/sram_soc.py b/examples/sram_soc.py deleted file mode 100644 index a3ecbc3..0000000 --- a/examples/sram_soc.py +++ /dev/null @@ -1,107 +0,0 @@ -import argparse -import importlib - -from nmigen import * -from nmigen_soc import wishbone - -from lambdasoc.cpu.minerva import MinervaCPU -from lambdasoc.periph.intc import GenericInterruptController -from lambdasoc.periph.serial import AsyncSerialPeripheral -from lambdasoc.periph.sram import SRAMPeripheral -from lambdasoc.periph.timer import TimerPeripheral -from lambdasoc.soc.cpu import CPUSoC - - -__all__ = ["SRAMSoC"] - - -class SRAMSoC(CPUSoC, Elaboratable): - def __init__(self, *, reset_addr, clk_freq, - rom_addr, rom_size, - ram_addr, ram_size, - uart_addr, uart_divisor, uart_pins, - timer_addr, timer_width): - self._arbiter = wishbone.Arbiter(addr_width=30, data_width=32, granularity=8, - features={"cti", "bte"}) - self._decoder = wishbone.Decoder(addr_width=30, data_width=32, granularity=8, - features={"cti", "bte"}) - - self.cpu = MinervaCPU(reset_address=reset_addr) - self._arbiter.add(self.cpu.ibus) - self._arbiter.add(self.cpu.dbus) - - self.rom = SRAMPeripheral(size=rom_size, writable=False) - self._decoder.add(self.rom.bus, addr=rom_addr) - - self.ram = SRAMPeripheral(size=ram_size) - self._decoder.add(self.ram.bus, addr=ram_addr) - - self.uart = AsyncSerialPeripheral(divisor=uart_divisor, pins=uart_pins) - self._decoder.add(self.uart.bus, addr=uart_addr) - - self.timer = TimerPeripheral(width=timer_width) - self._decoder.add(self.timer.bus, addr=timer_addr) - - self.intc = GenericInterruptController(width=len(self.cpu.ip)) - self.intc.add_irq(self.timer.irq, 0) - self.intc.add_irq(self.uart .irq, 1) - - self.memory_map = self._decoder.bus.memory_map - - self.clk_freq = clk_freq - - def elaborate(self, platform): - m = Module() - - m.submodules.arbiter = self._arbiter - m.submodules.cpu = self.cpu - - m.submodules.decoder = self._decoder - m.submodules.rom = self.rom - m.submodules.ram = self.ram - m.submodules.uart = self.uart - m.submodules.timer = self.timer - m.submodules.intc = self.intc - - m.d.comb += [ - self._arbiter.bus.connect(self._decoder.bus), - self.cpu.ip.eq(self.intc.ip), - ] - - return m - - -if __name__ == "__main__": - parser = argparse.ArgumentParser() - parser.add_argument("platform", type=str, - help="target platform (e.g. 'nmigen_boards.arty_a7.ArtyA7_35Platform')") - parser.add_argument("--baudrate", type=int, - default=9600, - help="UART baudrate (default: 9600)") - parser.add_argument("--build-dir", type=str, - default="build", - help="local build directory (default: 'build')") - args = parser.parse_args() - - def get_platform(platform_name): - module_name, class_name = platform_name.rsplit(".", 1) - module = importlib.import_module(name=module_name) - platform_class = getattr(module, class_name) - return platform_class() - - platform = get_platform(args.platform) - - uart_divisor = int(platform.default_clk_frequency // args.baudrate) - uart_pins = platform.request("uart", 0) - - soc = SRAMSoC( - reset_addr=0x00000000, clk_freq=int(platform.default_clk_frequency), - rom_addr=0x00000000, rom_size=0x4000, - ram_addr=0x00004000, ram_size=0x1000, - uart_addr=0x00005000, uart_divisor=uart_divisor, uart_pins=uart_pins, - timer_addr=0x00006000, timer_width=32, - ) - - soc.build(build_dir=f"{args.build_dir}/soc", do_init=True) - - platform.build(soc, build_dir=args.build_dir, do_program=True) diff --git a/lambdasoc/cores/litedram.py b/lambdasoc/cores/litedram.py index 497112f..227cdda 100644 --- a/lambdasoc/cores/litedram.py +++ b/lambdasoc/cores/litedram.py @@ -7,6 +7,7 @@ import textwrap from nmigen import * from nmigen import tracer +from nmigen.build.plat import Platform from nmigen.build.run import BuildPlan, BuildProducts from nmigen.utils import log2_int @@ -152,32 +153,6 @@ class Config(metaclass=ABCMeta): assert module.memtype == self.memtype return module - def request_pins(self, platform, name, number): - """Request DRAM pins. - - This helper requests the DRAM pins with `dir="-"` and `xdr=0`, because LiteDRAM already - provides its own I/O buffers. - - Arguments - --------- - platform : :class:`nmigen.build.Platform` - Target platform. - name : str - DRAM resource name. - number : int - DRAM resource number. - - Return value - ------------ - A :class:`Record` providing raw access to DRAM pins. - """ - res = platform.lookup(name, number) - return platform.request( - name, number, - dir={io.name: "-" for io in res.ios}, - xdr={io.name: 0 for io in res.ios}, - ) - class ECP5Config(Config): phy_name = "ECP5DDRPHY" @@ -485,18 +460,19 @@ class Core(Elaboratable): ) self._ctrl_bus.memory_map = ctrl_map - def build(self, builder, *, do_build=True, build_dir="build/litedram", sim=False, - name_force=False): + def build(self, builder, platform, build_dir, *, do_build=True, sim=False, name_force=False): """Build the LiteDRAM core. Arguments --------- builder: :class:`litedram.Builder` Builder instance. + platform: :class:`nmigen.build.Platform` + Target platform. + build_dir : str + Root build directory. do_build : bool Execute the build locally. Defaults to ``True``. - build_dir : str - Root build directory. Defaults to ``"build/litedram"``. sim : bool Do the build in simulation mode (i.e. by replacing the PHY with a model). Defaults to ``False``. @@ -511,13 +487,20 @@ class Core(Elaboratable): if not isinstance(builder, Builder): raise TypeError("Builder must be an instance of litedram.Builder, not {!r}" .format(builder)) + if not isinstance(platform, Platform): + raise TypeError("Platform must be an instance of nmigen.build.Platform, not {!r}" + .format(platform)) plan = builder.prepare(self, sim=sim, name_force=name_force) if not do_build: return plan - products = plan.execute_local(build_dir) + products = plan.execute_local(f"{build_dir}/{__package__}") self._populate_ctrl_map(products) + + core_src = f"{self.name}/{self.name}.v" + platform.add_file(core_src, products.get(core_src, mode="t")) + return products def elaborate(self, platform): diff --git a/lambdasoc/cores/utils.py b/lambdasoc/cores/utils.py new file mode 100644 index 0000000..fa7acca --- /dev/null +++ b/lambdasoc/cores/utils.py @@ -0,0 +1,25 @@ +def request_bare(platform, name, number): + """Request bare pins. + + This helper requests pins with `dir="-"` and `xdr=0`, for use cases where implicit I/O + buffers are undesirable. + + Arguments + --------- + platform : :class:`nmigen.build.plat.Platform` + Target platform. + name : str + Resource name. + number : int + Resource number. + + Return value + ------------ + A :class:`Record` providing raw access to pins. + """ + res = platform.lookup(name, number) + return platform.request( + name, number, + dir={io.name: "-" for io in res.ios}, + xdr={io.name: 0 for io in res.ios}, + ) diff --git a/lambdasoc/soc/base.py b/lambdasoc/soc/base.py index 593d44e..401a9ec 100644 --- a/lambdasoc/soc/base.py +++ b/lambdasoc/soc/base.py @@ -179,6 +179,8 @@ class ConfigBuilder: "periph_size": periph_size, "soc": soc, "software_dir": os.path.dirname(software.__file__), + "constants": soc.constants, + "ConstantAddr": ConstantAddr, **render_params, }) diff --git a/lambdasoc/soc/cpu.py b/lambdasoc/soc/cpu.py index 5f26054..24149f4 100644 --- a/lambdasoc/soc/cpu.py +++ b/lambdasoc/soc/cpu.py @@ -1,10 +1,12 @@ import os +from nmigen_soc.periph import ConstantMap, ConstantBool, ConstantInt + from .base import * from ..cpu import CPU +from ..cores import litedram from ..periph.intc import InterruptController from ..periph.sram import SRAMPeripheral -from ..periph.sdram import SDRAMPeripheral from ..periph.serial import AsyncSerialPeripheral from ..periph.timer import TimerPeripheral @@ -13,25 +15,35 @@ __all__ = ["CPUSoC", "BIOSBuilder"] class CPUSoC(SoC): - cpu = socproperty(CPU) - intc = socproperty(InterruptController) - rom = socproperty(SRAMPeripheral) - ram = socproperty(SRAMPeripheral) - sdram = socproperty(SDRAMPeripheral, weak=True) - uart = socproperty(AsyncSerialPeripheral) - timer = socproperty(TimerPeripheral) + cpu = socproperty(CPU) + intc = socproperty(InterruptController) + bootrom = socproperty(SRAMPeripheral) + scratchpad = socproperty(SRAMPeripheral) + uart = socproperty(AsyncSerialPeripheral) + timer = socproperty(TimerPeripheral) # TODO: implement a CRG peripheral and expose clock frequencies through CSRs. - clk_freq = socproperty(int) - - def build(self, name=None, - litedram_dir="build/litedram", - build_dir="build/soc", do_build=True, - do_init=False): + sync_clk_freq = socproperty(int) + + @property + def constants(self): + return ConstantMapCollection( + CPU = self.cpu.constant_map, + INTC = self.intc.constant_map, + BOOTROM = self.bootrom.constant_map, + SCRATCHPAD = self.scratchpad.constant_map, + UART = self.uart.constant_map, + TIMER = self.timer.constant_map, + SOC = ConstantMap( + CLOCK_FREQ = self.sync_clk_freq, + CSR_DATA_WIDTH = 32, + ), + ) + + def build(self, build_dir, name=None, do_build=True, do_init=False): """TODO """ - plan = BIOSBuilder().prepare(self, build_dir, name, - litedram_dir=os.path.abspath(litedram_dir)) + plan = BIOSBuilder().prepare(self, build_dir, name) if not do_build: return plan @@ -39,61 +51,86 @@ class CPUSoC(SoC): if not do_init: return products - with products.extract("bios/bios.bin") as bios_filename: + with products.extract(f"{__name__}/bios/bios.bin") as bios_filename: with open(bios_filename, "rb") as f: words = iter(lambda: f.read(self.cpu.data_width // 8), b'') bios = [int.from_bytes(w, self.cpu.byteorder) for w in words] - self.rom.init = bios + self.bootrom.init = bios + + +def kconfig_format(key, const, prefix="CONFIG_"): + if not isinstance(key, str) or not key: + raise ValueError("Key must be a non-empty string, not {!r}".format(key)) + if isinstance(const, ConstantBool): + value = "y" if const.value else "n" + elif isinstance(const, ConstantAddr): + value = hex(const.value) + elif isinstance(const, ConstantInt): + value = const.value + else: + raise TypeError("Unsupported constant type, must be ConstantBool, ConstantAddr or " + "ConstantInt, not {!r}" + .format(const)) + return "{}{}={}".format(prefix, key.upper(), value) + + +def cpp_format(key, const, prefix=""): + if not isinstance(key, str) or not key: + raise ValueError("Key must be a non-empty string, not {!r}".format(key)) + if isinstance(const, ConstantBool): + value = 1 if const.value else 0 + elif isinstance(const, ConstantAddr): + value = "{:#x}UL".format(const.value) + elif isinstance(const, ConstantInt): + value = "{}{}".format(const.value, "U" if not const.signed else "") + else: + raise TypeError("Unsupported constant type, must be ConstantBool, ConstantAddr or " + "ConstantInt, not {!r}" + .format(const)) + return "#define {}{} {}".format(prefix, key.upper(), value) class BIOSBuilder(ConfigBuilder): file_templates = { **ConfigBuilder.file_templates, - "{{name}}.config": r""" + "/".join([__name__, "{{name}}.config"]): r""" # {{autogenerated}} - CONFIG_CPU_{{soc.cpu.name.upper()}}=y - CONFIG_CPU_RESET_ADDR={{hex(soc.cpu.reset_addr)}} - CONFIG_CPU_BYTEORDER="{{soc.cpu.byteorder}}" - CONFIG_ARCH_{{soc.cpu.arch.upper()}}=y - {% if soc.cpu.muldiv == "soft" %} - CONFIG_{{soc.cpu.arch.upper()}}_MULDIV_SOFT=y - {% else %} - CONFIG_{{soc.cpu.arch.upper()}}_MULDIV_SOFT=n - {% endif %} - CONFIG_ROM_START={{hex(periph_addr(soc.rom))}} - CONFIG_ROM_SIZE={{hex(soc.rom.size)}} - CONFIG_RAM_START={{hex(periph_addr(soc.ram))}} - CONFIG_RAM_SIZE={{hex(soc.ram.size)}} - CONFIG_UART_START={{hex(periph_addr(soc.uart))}} - CONFIG_UART_IRQNO={{soc.intc.find_index(soc.uart.irq)}} - CONFIG_UART_RX_RINGBUF_SIZE_LOG2=7 - CONFIG_UART_TX_RINGBUF_SIZE_LOG2=7 - CONFIG_TIMER_START={{hex(periph_addr(soc.timer))}} - CONFIG_TIMER_IRQNO={{soc.intc.find_index(soc.timer.irq)}} - CONFIG_TIMER_CTR_WIDTH={{soc.timer.width}} - CONFIG_CLOCK_FREQ={{soc.clk_freq}} - {% if soc.sdram is not none %} - CONFIG_WITH_SDRAM=y - CONFIG_SDRAM_START={{hex(periph_addr(soc.sdram))}} - CONFIG_SDRAM_SIZE={{hex(soc.sdram.core.size)}} - {% else %} - CONFIG_WITH_SDRAM=n - {% endif %} + # Configuration constants + {% for key, value in constants.flatten(separator="_") %} + {{kconfig_format(key, value)}} + {% endfor %} + + # Memory regions + {% for window, (start, stop, step) in soc.memory_map.windows() %} + {% set window_name = window.name.upper() %} + {{kconfig_format(window_name + "_BASE", ConstantAddr(start))}} + {{kconfig_format(window_name + "_LIMIT", ConstantAddr(stop))}} + {% endfor %} """, - "litex_config.h": r""" + "/".join([__name__, "litex_config.h"]): r""" // {{autogenerated}} #ifndef __LITEX_CONFIG_H_LAMBDASOC #define __LITEX_CONFIG_H_LAMBDASOC - #define LX_CONFIG_TIMER_START {{hex(periph_addr(soc.timer))}} + // Configuration constants + {% for key, value in constants.flatten(separator="_") %} + {{cpp_format(key, value, prefix="LX_CONFIG_")}} + {% endfor %} + + // Memory regions + {% for window, (start, stop, step) in soc.memory_map.windows() %} + {% set window_name = window.name.upper() %} + {{cpp_format(window_name + "_BASE", ConstantAddr(start), prefix="LX_CONFIG_")}} + {{cpp_format(window_name + "_LIMIT", ConstantAddr(stop), prefix="LX_CONFIG_")}} + {% endfor %} {% if soc.sdram is not none %} - #define LX_CONFIG_SDRAM_START {{hex(periph_addr(soc.sdram))}}UL - #define LX_CONFIG_SDRAM_SIZE {{hex(soc.sdram.core.size)}}UL - #define LX_CONFIG_SDRAM_CACHE_SIZE {{soc.sdram._cache.size}} - #define LX_CONFIG_MEMTEST_DATA_SIZE 2*1024*1024 - #define LX_CONFIG_MEMTEST_ADDR_SIZE 65536 + #define LX_CONFIG_MAIN_RAM_BASE LX_CONFIG_SDRAM_BASE + #define LX_CONFIG_MAIN_RAM_SIZE LX_CONFIG_SDRAM_SIZE + {% else %} + #define LX_CONFIG_MAIN_RAM_BASE LX_CONFIG_SRAM_BASE + #define LX_CONFIG_MAIN_RAM_SIZE LX_CONFIG_SRAM_SIZE {% endif %} #endif @@ -103,16 +140,24 @@ class BIOSBuilder(ConfigBuilder): *ConfigBuilder.command_templates, r""" {% if soc.sdram is not none %} - litedram_dir={{litedram_dir}}/{{soc.sdram.core.name}} + litedram_dir={{build_dir}}/{{litedram_pkg}}/{{soc.sdram.core.name}} {% endif %} - build={{build_dir}} - KCONFIG_CONFIG={{build_dir}}/{{name}}.config + build={{bios_dir}} + KCONFIG_CONFIG={{bios_dir}}/{{name}}.config make -C {{software_dir}}/bios 1>&2 """, ] - def prepare(self, soc, build_dir, name, litedram_dir): + def prepare(self, soc, build_dir, name, **render_params): if not isinstance(soc, CPUSoC): raise TypeError("SoC must be an instance of CPUSoC, not {!r}" .format(soc)) - return super().prepare(soc, build_dir, name, litedram_dir=litedram_dir) + + render_params.update({ + "kconfig_format": kconfig_format, + "cpp_format": cpp_format, + "bios_dir": os.path.abspath(f"{build_dir}/{__name__}"), + "litedram_pkg": litedram.__name__, + }) + + return super().prepare(soc, build_dir, name, **render_params) diff --git a/setup.py b/setup.py index 8c7a732..e897b9a 100644 --- a/setup.py +++ b/setup.py @@ -24,6 +24,7 @@ setup( license="BSD", setup_requires=["setuptools_scm"], install_requires=[ + "jinja2>=3.0", "nmigen>=0.1,<0.5", "nmigen-soc", "nmigen-stdio", -- 2.30.2