From: Jean-François Nguyen Date: Fri, 29 Oct 2021 18:21:24 +0000 (+0200) Subject: Add LiteEth support. X-Git-Url: https://git.libre-soc.org/?a=commitdiff_plain;h=2ab12c7d1821e6a89e668bb09c25bcd86bcf702b;p=lambdasoc.git Add LiteEth support. --- diff --git a/examples/minerva_soc.py b/examples/minerva_soc.py index 02675f2..99b0564 100644 --- a/examples/minerva_soc.py +++ b/examples/minerva_soc.py @@ -19,12 +19,13 @@ 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.periph.eth import EthernetMACPeripheral 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 import litedram, liteeth from lambdasoc.cores.utils import request_bare from lambdasoc.sim.blackboxes.serial import AsyncSerial_Blackbox @@ -35,13 +36,14 @@ __all__ = ["MinervaSoC"] class _ClockResetGenerator(Elaboratable): - def __init__(self, *, sync_clk_freq, with_sdram): + def __init__(self, *, sync_clk_freq, with_sdram, with_ethernet): 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) + self.with_ethernet = bool(with_ethernet) def elaborate(self, platform): m = Module() @@ -55,6 +57,15 @@ class _ClockResetGenerator(Elaboratable): if platform.default_rst is not None: m.d.comb += ResetSignal("_ref").eq(platform.request(platform.default_rst, 0).i) + # On the Arty A7, the DP83848 Ethernet PHY uses a 25 MHz reference clock. + if isinstance(platform, ArtyA7_35Platform) and self.with_ethernet: + m.domains += ClockDomain("_eth_ref", local=True) + m.submodules += Instance("BUFGCE", + i_I = ClockSignal("_eth_ref"), + i_CE = ~ResetSignal("_eth_ref"), + o_O = platform.request("eth_clk25", 0).o, + ) + # 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. @@ -70,6 +81,24 @@ class _ClockResetGenerator(Elaboratable): ResetSignal("sync").eq(ResetSignal("litedram_user")), ] + # On the Arty A7, we still use our own PLL to drive the Ethernet PHY reference clock. + if isinstance(platform, ArtyA7_35Platform) and self.with_ethernet: + eth_ref_pll_params = PLL_Xilinx7Series.Parameters( + i_domain = "_ref", + i_freq = platform.default_clk_frequency, + i_reset_less = platform.default_rst is None, + o_domain = "_eth_ref", + o_freq = 25e6, + ) + m.submodules.eth_ref_pll = eth_ref_pll = PLL_Xilinx7Series(eth_ref_pll_params) + + if platform.default_rst is not None: + eth_ref_pll_arst = ~eth_ref_pll.locked | ResetSignal("_ref") + else: + eth_ref_pll_arst = ~eth_ref_pll.locked + + m.submodules += ResetSynchronizer(eth_ref_pll_arst, domain="_eth_ref") + # 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 @@ -87,6 +116,8 @@ class _ClockResetGenerator(Elaboratable): o_domain = "sync", o_freq = self.sync_clk_freq, ) + if self.with_ethernet: + sync_pll_params.add_secondary_output(domain="_eth_ref", freq=25e6) m.submodules.sync_pll = sync_pll = PLL_Xilinx7Series(sync_pll_params) elif isinstance(platform, (ECPIX545Platform, ECPIX585Platform)): sync_pll_params = PLL_LatticeECP5.Parameters( @@ -106,6 +137,8 @@ class _ClockResetGenerator(Elaboratable): sync_pll_arst = ~sync_pll.locked m.submodules += ResetSynchronizer(sync_pll_arst, domain="sync") + if isinstance(platform, ArtyA7_35Platform) and self.with_ethernet: + m.submodules += ResetSynchronizer(sync_pll_arst, domain="_eth_ref") return m @@ -161,6 +194,7 @@ class MinervaSoC(CPUSoC, Elaboratable): self._sdram = None self._sram = None + self._ethmac = None @property def memory_map(self): @@ -170,8 +204,10 @@ class MinervaSoC(CPUSoC, Elaboratable): def constants(self): return super().constants.union( SDRAM = self.sdram .constant_map if self.sdram is not None else None, + ETHMAC = self.ethmac.constant_map if self.ethmac is not None else None, SOC = ConstantMap( WITH_SDRAM = self.sdram is not None, + WITH_ETHMAC = self.ethmac is not None, MEMTEST_ADDR_SIZE = 8192, MEMTEST_DATA_SIZE = 8192, ), @@ -206,12 +242,25 @@ class MinervaSoC(CPUSoC, Elaboratable): self._sram = SRAMPeripheral(size=size) self._decoder.add(self._sram.bus, addr=addr) + @property + def ethmac(self): + return self._ethmac + + def add_ethmac(self, core, *, addr, irqno, local_ip, remote_ip): + if self._ethmac is not None: + raise AttributeError("Ethernet MAC has already been set to {!r}" + .format(self._ethmac)) + self._ethmac = EthernetMACPeripheral(core=core, local_ip=local_ip, remote_ip=remote_ip) + self._decoder.add(self._ethmac.bus, addr=addr) + self.intc.add_irq(self._ethmac.irq, index=irqno) + def elaborate(self, platform): m = Module() m.submodules.crg = _ClockResetGenerator( sync_clk_freq = self.sync_clk_freq, with_sdram = self.sdram is not None, + with_ethernet = self.ethmac is not None, ) m.submodules.cpu = self.cpu @@ -227,6 +276,8 @@ class MinervaSoC(CPUSoC, Elaboratable): m.submodules.sdram = self.sdram if self.sram is not None: m.submodules.sram = self.sram + if self.ethmac is not None: + m.submodules.ethmac = self.ethmac m.d.comb += [ self._arbiter.bus.connect(self._decoder.bus), @@ -257,6 +308,14 @@ if __name__ == "__main__": parser.add_argument("--baudrate", type=int, default=9600, help="UART baudrate (default: 9600)") + parser.add_argument("--with-ethernet", action="store_true", + help="enable Ethernet") + parser.add_argument("--local-ip", type=str, + default="192.168.1.50", + help="Local IPv4 address (default: 192.168.1.50)") + parser.add_argument("--remote-ip", type=str, + default="192.168.1.100", + help="Remote IPv4 address (default: 192.168.1.100)") args = parser.parse_args() # Platform selection @@ -326,6 +385,30 @@ if __name__ == "__main__": litedram_core = None mainram_size = args.internal_sram_size + # LiteEth + + if args.with_ethernet: + if isinstance(platform, CXXRTLPlatform): + raise NotImplementedError("Ethernet is currently unsupported in simulation.") + elif isinstance(platform, ArtyA7_35Platform): + liteeth_config = liteeth.Artix7Config( + phy_iface = "mii", + clk_freq = int(25e6), + ) + elif isinstance(platform, (ECPIX545Platform, ECPIX585Platform)): + liteeth_config = liteeth.ECP5Config( + phy_iface = "rgmii", + clk_freq = int(125e6), + ) + else: + assert False + + liteeth_pins = request_bare(platform, f"eth_{liteeth_config.phy_iface}", 0) + liteeth_core = liteeth.Core(liteeth_config, pins=liteeth_pins) + liteeth_core.build(liteeth.Builder(), platform, args.build_dir) + else: + liteeth_core = None + # UART if isinstance(platform, CXXRTLPlatform): @@ -385,6 +468,10 @@ if __name__ == "__main__": else: soc.add_internal_sram(addr=0x40000000, size=args.internal_sram_size) + if args.with_ethernet: + soc.add_ethmac(liteeth_core, addr=0x90000000, irqno=2, + local_ip=args.local_ip, remote_ip=args.remote_ip) + soc.build(build_dir=args.build_dir, do_init=True) if isinstance(platform, CXXRTLPlatform): diff --git a/lambdasoc/cores/liteeth.py b/lambdasoc/cores/liteeth.py new file mode 100644 index 0000000..1583750 --- /dev/null +++ b/lambdasoc/cores/liteeth.py @@ -0,0 +1,352 @@ +from abc import ABCMeta, abstractmethod +import csv +import jinja2 +import textwrap +import re + +from nmigen import * +from nmigen import tracer +from nmigen.utils import log2_int +from nmigen.hdl.rec import Layout +from nmigen.build.plat import Platform +from nmigen.build.run import BuildPlan, BuildProducts + +from nmigen_soc import wishbone +from nmigen_soc.memory import MemoryMap + +from .. import __version__ +from ..periph import IRQLine + + +__all__ = [ + "Config", "ECP5Config", "Artix7Config", + "Core", + "Builder", +] + +class Config(metaclass=ABCMeta): + def __init__(self, *, + phy_iface, + clk_freq, + rx_slots = 2, + tx_slots = 2, + endianess="little"): + + if phy_iface not in {"mii", "rmii", "rgmii"}: + raise ValueError("LiteEth PHY interface must be one of \"mii\", \"rmii\" or " + "\"rgmii\", not {!r}".format(phy_iface)) + if not isinstance(clk_freq, int) or clk_freq <= 0: + raise ValueError("LiteEth clock frequency must be a positive integer, not {!r}" + .format(clk_freq)) + if not isinstance(rx_slots, int) or rx_slots < 0: + raise ValueError("LiteEth Rx FIFO slots must be a non-negative integer, not {!r}" + .format(rx_slots)) + if not isinstance(tx_slots, int) or tx_slots < 0: + raise ValueError("LiteEth Tx FIFO slots must be a non-negative integer, not {!r}" + .format(tx_slots)) + if endianess not in {"big", "little"}: + raise ValueError("LitEth endianess must be one of \"big\" or \"little\", not {!r}" + .format(endianess)) + + self.phy_iface = phy_iface + self.clk_freq = clk_freq + self.rx_slots = rx_slots + self.tx_slots = tx_slots + self.endianess = endianess + # FIXME: hardcoded + self.ctrl_addr = 0x0 + self.data_addr = 0x10000 + + @property + @abstractmethod + def phy_name(self): + raise NotImplementedError + + @property + @abstractmethod + def vendor(self): + raise NotImplementedError + + +class ECP5Config(Config): + vendor = "lattice" + + @property + def phy_name(self): + if self.phy_iface in {"mii", "rmii", "gmii"}: + return "LiteEthPHY{}".format(self.phy_iface.upper()) + elif self.phy_iface == "rgmii": + return "LiteEthECP5PHYRGMII" + else: + assert False + + +class Artix7Config(Config): + vendor = "xilinx" + + @property + def phy_name(self): + if self.phy_iface in {"mii", "rmii", "gmii"}: + return "LiteEthPHY{}".format(self.phy_iface.upper()) + elif self.phy_iface == "rgmii": + return "LiteEthS7PHYRGMII" + else: + assert False + + +class Core(Elaboratable): + def __init__(self, config, *, pins=None, name=None, src_loc_at=0): + if not isinstance(config, Config): + raise TypeError("Config must be an instance liteeth.Config, " + "not {!r}" + .format(config)) + self.config = config + + if name is not None and not isinstance(name, str): + raise TypeError("Name must be a string, not {!r}".format(name)) + self.name = name or tracer.get_var_name(depth=2 + src_loc_at) + + self.irq = IRQLine(name=f"{self.name}_irq") + + self._bus = None + self._pins = pins + + @property + def bus(self): + if self._bus is None: + raise AttributeError("Bus memory map has not been populated. " + "Core.build(do_build=True) must be called before accessing " + "Core.bus") + return self._bus + + def _populate_map(self, build_products): + if not isinstance(build_products, BuildProducts): + raise TypeError("Build products must be an instance of BuildProducts, not {!r}" + .format(build_products)) + + # LiteEth's Wishbone bus has a granularity of 8 bits. + ctrl_map = MemoryMap(addr_width=1, data_width=8) + data_map = MemoryMap(addr_width=1, data_width=8) + + csr_csv = build_products.get(f"{self.name}_csr.csv", mode="t") + for row in csv.reader(csr_csv.split("\n"), delimiter=","): + if not row or row[0][0] == "#": continue + res_type, res_name, addr, size, attrs = row + if res_type == "csr_register": + ctrl_map.add_resource( + object(), + name = res_name, + addr = int(addr, 16), + size = int(size, 10) * 32 // ctrl_map.data_width, + extend = True, + ) + + # TODO: rephrase + # The LiteEth MAC core uses a memory region capable of holding an IEEE 802.3 Ethernet frame + # (rounded to the nearest power-of-two) for each Rx/Tx slot. + data_map.add_resource( + object(), + name = "ethmac_slots", + size = (self.config.rx_slots + self.config.tx_slots) * 2048, + extend = True, + ) + + bus_map = MemoryMap(addr_width=1, data_width=8) + bus_map.add_window(ctrl_map, addr=self.config.ctrl_addr, extend=True) + bus_map.add_window(data_map, addr=self.config.data_addr, extend=True) + + self._bus = wishbone.Interface( + addr_width = bus_map.addr_width + - log2_int(32 // bus_map.data_width), + data_width = 32, + granularity = bus_map.data_width, + features = {"cti", "bte", "err"}, + ) + self._bus.memory_map = bus_map + + def build(self, builder, platform, build_dir, *, do_build=True, name_force=False): + if not isinstance(builder, Builder): + raise TypeError("Builder must be an instance of liteeth.Builder, not {!r}" + .format(builder)) + + plan = builder.prepare(self, name_force=name_force) + if not do_build: + return plan + + products = plan.execute_local(f"{build_dir}/lambdasoc.cores.liteeth") # TODO __package__ + self._populate_map(products) + + core_src = f"liteeth_core/liteeth_core.v" + platform.add_file(core_src, products.get(core_src, mode="t")) + + return products + + def elaborate(self, platform): + core_kwargs = { + "i_sys_clock" : ClockSignal("sync"), + "i_sys_reset" : ResetSignal("sync"), + + "i_wishbone_adr" : self.bus.adr, + "i_wishbone_dat_w" : self.bus.dat_w, + "o_wishbone_dat_r" : self.bus.dat_r, + "i_wishbone_sel" : self.bus.sel, + "i_wishbone_cyc" : self.bus.cyc, + "i_wishbone_stb" : self.bus.stb, + "o_wishbone_ack" : self.bus.ack, + "i_wishbone_we" : self.bus.we, + "i_wishbone_cti" : self.bus.cti, + "i_wishbone_bte" : self.bus.bte, + "o_wishbone_err" : self.bus.err, + + "o_interrupt" : self.irq, + } + + if self._pins is not None: + if self.config.phy_iface == "mii": + core_kwargs.update({ + "i_mii_eth_clocks_tx" : self._pins.tx_clk, + "i_mii_eth_clocks_rx" : self._pins.rx_clk, + "o_mii_eth_rst_n" : self._pins.rst, + "io_mii_eth_mdio" : self._pins.mdio, + "o_mii_eth_mdc" : self._pins.mdc, + "i_mii_eth_rx_dv" : self._pins.rx_dv, + "i_mii_eth_rx_er" : self._pins.rx_er, + "i_mii_eth_rx_data" : self._pins.rx_data, + "o_mii_eth_tx_en" : self._pins.tx_en, + "o_mii_eth_tx_data" : self._pins.tx_data, + "i_mii_eth_col" : self._pins.col, + "i_mii_eth_crs" : self._pins.crs, + }) + elif self.config.phy_iface == "rmii": + core_kwargs.update({ + "o_rmii_eth_clocks_ref_clk" : self._pins.clk, + "o_rmii_eth_rst_n" : self._pins.rst, + "io_rmii_eth_mdio" : self._pins.mdio, + "o_rmii_eth_mdc" : self._pins.mdc, + "i_rmii_eth_crs_dv" : self._pins.crs_dv, + "i_rmii_eth_rx_data" : self._pins.rx_data, + "o_rmii_eth_tx_en" : self._pins.tx_en, + "o_rmii_eth_tx_data" : self._pins.tx_data, + }) + elif self.config.phy_iface == "gmii": + core_kwargs.update({ + "o_gmii_eth_clocks_tx" : self._pins.tx_clk, + "i_gmii_eth_clocks_rx" : self._pins.rx_clk, + "o_gmii_eth_rst_n" : self._pins.rst, + "i_gmii_eth_int_n" : Const(1), + "io_gmii_eth_mdio" : self._pins.mdio, + "o_gmii_eth_mdc" : self._pins.mdc, + "i_gmii_eth_rx_dv" : self._pins.rx_dv, + "i_gmii_eth_rx_er" : self._pins.rx_er, + "i_gmii_eth_rx_data" : self._pins.rx_data, + "o_gmii_eth_tx_en" : self._pins.tx_en, + "o_gmii_eth_tx_er" : self._pins.tx_er, + "o_gmii_eth_tx_data" : self._pins.tx_data, + "i_gmii_eth_col" : self._pins.col, + "i_gmii_eth_crs" : self._pins.crs, + }) + elif self.config.phy_iface == "rgmii": + core_kwargs.update({ + "o_rgmii_eth_clocks_tx" : self._pins.tx_clk, + "i_rgmii_eth_clocks_rx" : self._pins.rx_clk, + "o_rgmii_eth_rst_n" : self._pins.rst, + "i_rgmii_eth_int_n" : Const(1), + "io_rgmii_eth_mdio" : self._pins.mdio, + "o_rgmii_eth_mdc" : self._pins.mdc, + "i_rgmii_eth_rx_ctl" : self._pins.rx_ctrl, + "i_rgmii_eth_rx_data" : self._pins.rx_data, + "o_rgmii_eth_tx_ctl" : self._pins.tx_ctrl, + "o_rgmii_eth_tx_data" : self._pins.tx_data, + }) + else: + assert False + + return Instance(f"{self.name}", **core_kwargs) + + +class Builder: + file_templates = { + "build_{{top.name}}.sh": r""" + # {{autogenerated}} + set -e + {{emit_commands()}} + """, + "{{top.name}}_config.yml": r""" + # {{autogenerated}} + { + # PHY ---------------------------------------------------------------------- + "phy": {{top.config.phy_name}}, + "vendor": {{top.config.vendor}}, + + # Core --------------------------------------------------------------------- + "clk_freq": {{top.config.clk_freq}}, + "core": wishbone, + "nrxslots": {{top.config.rx_slots}}, + "ntxslots": {{top.config.tx_slots}}, + "endianness": {{top.config.endianess}}, + + "soc": { + "mem_map": { + "csr": {{hex(top.config.ctrl_addr)}}, + "ethmac": {{hex(top.config.data_addr)}}, + }, + }, + } + """, + } + command_templates = [ + # FIXME: add --name upstream + r""" + python -m liteeth.gen + --output-dir {{top.name}} + --gateware-dir {{top.name}} + --csr-csv {{top.name}}_csr.csv + {{top.name}}_config.yml + """, + ] + + def __init__(self): + self.namespace = set() + + def prepare(self, core, *, name_force=False): + if not isinstance(core, Core): + raise TypeError("LiteEth core must be an instance of liteeth.Core, not {!r}" + .format(core)) + + if core.name in self.namespace and not name_force: + raise ValueError( + "LiteEth core name '{}' has already been used for a previous build. Building " + "this instance may overwrite previous build products. Passing `name_force=True` " + "will disable this check".format(core.name) + ) + self.namespace.add(core.name) + + autogenerated = f"Automatically generated by LambdaSoC {__version__}. Do not edit." + + def emit_commands(): + commands = [] + for index, command_tpl in enumerate(self.command_templates): + command = render(command_tpl, origin="".format(index + 1)) + command = re.sub(r"\s+", " ", command) + commands.append(command) + return "\n".join(commands) + + def render(source, origin): + try: + source = textwrap.dedent(source).strip() + compiled = jinja2.Template(source, trim_blocks=True, lstrip_blocks=True) + except jinja2.TemplateSyntaxError as e: + e.args = ("{} (at {}:{})".format(e.message, origin, e.lineno),) + raise + return compiled.render({ + "autogenerated": autogenerated, + "emit_commands": emit_commands, + "hex": hex, + "top": core, + }) + + plan = BuildPlan(script=f"build_{core.name}") + for filename_tpl, content_tpl in self.file_templates.items(): + plan.add_file(render(filename_tpl, origin=filename_tpl), + render(content_tpl, origin=content_tpl)) + return plan diff --git a/lambdasoc/periph/eth.py b/lambdasoc/periph/eth.py new file mode 100644 index 0000000..13bef12 --- /dev/null +++ b/lambdasoc/periph/eth.py @@ -0,0 +1,79 @@ +from ipaddress import IPv4Address + +from nmigen import * + +from nmigen_soc import wishbone +from nmigen_soc.memory import MemoryMap +from nmigen_soc.periph import ConstantMap + +from . import Peripheral +from .event import IRQLine + +from ..cores import liteeth +from ..soc.base import ConstantAddr + + +__all__ = ["EthernetMACPeripheral"] + + +class EthernetMACPeripheral(Peripheral, Elaboratable): + """Ethernet MAC peripheral. + + Parameters + ---------- + core : :class:`liteeth.Core` + LiteEth core. + local_ip : :class:`ipaddress.IPv4Address` + Local IP address. Defaults to 192.168.1.50. + remote_ip : :class:`ipaddress.IPv4Address` + Remote IP address. Defaults to 192.168.1.100. + """ + def __init__(self, *, core, local_ip="192.168.1.50", remote_ip="192.168.1.100"): + super().__init__() + + if not isinstance(core, liteeth.Core): + raise TypeError("LiteEth core must be an instance of lambdasoc.cores.liteeth.Core, " + "not {!r}" + .format(core)) + + self.core = core + self.local_ip = IPv4Address(local_ip) + self.remote_ip = IPv4Address(remote_ip) + + bus_map = MemoryMap( + name = self.name, + addr_width = core.bus.memory_map.addr_width, + data_width = core.bus.memory_map.data_width, + ) + bus_map.add_window(core.bus.memory_map) + + self.bus = wishbone.Interface( + addr_width = core.bus.addr_width, + data_width = core.bus.data_width, + granularity = core.bus.granularity, + features = {"cti", "bte", "err"}, + ) + self.bus.memory_map = bus_map + + self.irq = IRQLine(name=f"{self.name}_irq") + + @property + def constant_map(self): + return ConstantMap( + CTRL_OFFSET = ConstantAddr(self.core.config.ctrl_addr), + DATA_OFFSET = ConstantAddr(self.core.config.data_addr), + RX_SLOTS = self.core.config.rx_slots, + TX_SLOTS = self.core.config.tx_slots, + SLOT_SIZE = 2048, + LOCAL_IP = int(self.local_ip), + REMOTE_IP = int(self.remote_ip), + ) + + def elaborate(self, platform): + m = Module() + m.submodules.core = self.core + m.d.comb += [ + self.bus.connect(self.core.bus), + self.irq.eq(self.core.irq), + ] + return m diff --git a/lambdasoc/soc/cpu.py b/lambdasoc/soc/cpu.py index 24149f4..9d338d4 100644 --- a/lambdasoc/soc/cpu.py +++ b/lambdasoc/soc/cpu.py @@ -4,7 +4,7 @@ from nmigen_soc.periph import ConstantMap, ConstantBool, ConstantInt from .base import * from ..cpu import CPU -from ..cores import litedram +from ..cores import litedram, liteeth from ..periph.intc import InterruptController from ..periph.sram import SRAMPeripheral from ..periph.serial import AsyncSerialPeripheral @@ -142,6 +142,9 @@ class BIOSBuilder(ConfigBuilder): {% if soc.sdram is not none %} litedram_dir={{build_dir}}/{{litedram_pkg}}/{{soc.sdram.core.name}} {% endif %} + {% if soc.ethmac is not none %} + liteeth_dir={{build_dir}}/{{liteeth_pkg}}/{{soc.ethmac.core.name}} + {% endif %} build={{bios_dir}} KCONFIG_CONFIG={{bios_dir}}/{{name}}.config make -C {{software_dir}}/bios 1>&2 @@ -158,6 +161,7 @@ class BIOSBuilder(ConfigBuilder): "cpp_format": cpp_format, "bios_dir": os.path.abspath(f"{build_dir}/{__name__}"), "litedram_pkg": litedram.__name__, + "liteeth_pkg": liteeth.__name__, }) return super().prepare(soc, build_dir, name, **render_params) diff --git a/lambdasoc/software/bios b/lambdasoc/software/bios index ba0aa96..32d6022 160000 --- a/lambdasoc/software/bios +++ b/lambdasoc/software/bios @@ -1 +1 @@ -Subproject commit ba0aa9698d60f2e6b6c398a01614352f3bd827fa +Subproject commit 32d60226f6728a183a6677cbd2451c9f1a52ec74