diff --git a/litex_boards/platforms/colorlight_i5.py b/litex_boards/platforms/colorlight_i5.py new file mode 100644 index 0000000..0f4bd17 --- /dev/null +++ b/litex_boards/platforms/colorlight_i5.py @@ -0,0 +1,153 @@ +# +# This file is part of LiteX-Boards. +# +# Copyright (c) 2021 Kazumoto Kojima +# SPDX-License-Identifier: BSD-2-Clause + +# The Colorlight i5 PCB and IOs have been documented by @wuxx +# https://github.com/wuxx/Colorlight-FPGA-Projects + +from litex.build.generic_platform import * +from litex.build.lattice import LatticePlatform +from litex.build.lattice.programmer import EcpDapProgrammer + +# IOs ---------------------------------------------------------------------------------------------- + +_io_v7_0 = [ # Documented by @smunaut + # Clk + ("clk25", 0, Pins("P3"), IOStandard("LVCMOS33")), + + # Led + ("user_led_n", 0, Pins("U16"), IOStandard("LVCMOS33")), + + # Reset button + ("cpu_reset_n", 0, Pins("K18"), IOStandard("LVCMOS33"), Misc("PULLMODE=UP")), + + # Serial + ("serial", 0, + Subsignal("tx", Pins("J17")), + Subsignal("rx", Pins("H18")), + IOStandard("LVCMOS33") + ), + + # SPIFlash (GD25Q16CSIG) + ("spiflash", 0, + Subsignal("cs_n", Pins("R2")), + # https://github.com/m-labs/nmigen-boards/pull/38 + #Subsignal("clk", Pins("")), driven through USRMCLK + Subsignal("mosi", Pins("W2")), + Subsignal("miso", Pins("V2")), + IOStandard("LVCMOS33"), + ), + + # SDRAM SDRAM (EM638325-6H) + ("sdram_clock", 0, Pins("B9"), IOStandard("LVCMOS33")), + ("sdram", 0, + Subsignal("a", Pins( + "B13 C14 A16 A17 B16 B15 A14 A13", + "A12 A11 B12")), + Subsignal("dq", Pins( + "D15 E14 E13 D12 E12 D11 C10 B17", + "B8 A8 C7 A7 A6 B6 A5 B5", + "D5 C5 D6 C6 E7 D7 E8 D8", + "E9 D9 E11 C11 C12 D13 D14 C15")), + Subsignal("we_n", Pins("A10")), + Subsignal("ras_n", Pins("B10")), + Subsignal("cas_n", Pins("A9")), + #Subsignal("cs_n", Pins("")), # gnd + #Subsignal("cke", Pins("")), # 3v3 + Subsignal("ba", Pins("B11 C8")), # sdram pin BA0 and BA1 + #Subsignal("dm", Pins("")), # gnd + IOStandard("LVCMOS33"), + Misc("SLEWRATE=FAST") + ), + + # RGMII Ethernet (B50612D) + # The order of the two PHYs is swapped with the naming of the connectors + # on the board so to match with the configuration of their PHYA[0] pins. + ("eth_clocks", 0, + Subsignal("tx", Pins("G1")), + Subsignal("rx", Pins("H2")), + IOStandard("LVCMOS33") + ), + ("eth", 0, + Subsignal("rst_n", Pins("P4")), + Subsignal("mdio", Pins("P5")), + Subsignal("mdc", Pins("N5")), + Subsignal("rx_ctl", Pins("P2")), + Subsignal("rx_data", Pins("K2 L1 N1 P1")), + Subsignal("tx_ctl", Pins("K1")), + Subsignal("tx_data", Pins("G2 H1 J1 J3")), + IOStandard("LVCMOS33") + ), + ("eth_clocks", 1, + Subsignal("tx", Pins("U19")), + Subsignal("rx", Pins("L19")), + IOStandard("LVCMOS33") + ), + ("eth", 1, + Subsignal("rst_n", Pins("P4")), + Subsignal("mdio", Pins("P5")), + Subsignal("mdc", Pins("N5")), + Subsignal("rx_ctl", Pins("M20")), + Subsignal("rx_data", Pins("P20 N19 N20 M19")), + Subsignal("tx_ctl", Pins("P19")), + Subsignal("tx_data", Pins("U20 T19 T20 R20")), + IOStandard("LVCMOS33") + ), +] + +# From https://github.com/wuxx/Colorlight-FPGA-Projects/blob/master/schematic/i5_v6.0-extboard.pdf and +# https://github.com/wuxx/Colorlight-FPGA-Projects/blob/master/doc/i5_extboard_v1.2_pinout.png +_connectors_v7_0 = [ + ("pmode", "C17 B18 B20 F20 A18 A19 B19 D20"), + ("pmodf", "D1 C1 C2 E3 E2 D2 B1 A3"), +] + +# PMODS -------------------------------------------------------------------------------------------- + +def sdcard_pmod_io(pmod): + return [ + # SDCard PMOD: + # - https://store.digilentinc.com/pmod-microsd-microsd-card-slot/ + ("spisdcard", 0, + Subsignal("clk", Pins(f"{pmod}:3")), + Subsignal("mosi", Pins(f"{pmod}:1"), Misc("PULLMODE=UP")), + Subsignal("cs_n", Pins(f"{pmod}:0"), Misc("PULLMODE=UP")), + Subsignal("miso", Pins(f"{pmod}:2"), Misc("PULLMODE=UP")), + Misc("SLEWRATE=FAST"), + IOStandard("LVCMOS33"), + ), + ("sdcard", 0, + Subsignal("data", Pins(f"{pmod}:2 {pmod}:4 {pmod}:5 {pmod}:0"), Misc("PULLMODE=UP")), + Subsignal("cmd", Pins(f"{pmod}:1"), Misc("PULLMODE=UP")), + Subsignal("clk", Pins(f"{pmod}:3")), + Subsignal("cd", Pins(f"{pmod}:6")), + #Misc("SLEWRATE=FAST"), + IOStandard("LVCMOS33"), + ), +] +_sdcard_pmod_io = sdcard_pmod_io("pmode") # SDCARD PMOD on P3. + +# Platform ----------------------------------------------------------------------------------------- + +class Platform(LatticePlatform): + default_clk_name = "clk25" + default_clk_period = 1e9/25e6 + + def __init__(self, revision="7.0", toolchain="trellis"): + assert revision in ["7.0"] + self.revision = revision + device = {"7.0": "LFE5U-25F-6BG381C"}[revision] + io = {"7.0": _io_v7_0}[revision] + connectors = {"7.0": _connectors_v7_0}[revision] + LatticePlatform.__init__(self, device, io, connectors=connectors, toolchain=toolchain) + + def create_programmer(self): + return EcpDapProgrammer() + + def do_finalize(self, fragment): + LatticePlatform.do_finalize(self, fragment) + self.add_period_constraint(self.lookup_request("clk25", loose=True), 1e9/25e6) + self.add_period_constraint(self.lookup_request("eth_clocks:rx", 0, loose=True), 1e9/125e6) + self.add_period_constraint(self.lookup_request("eth_clocks:rx", 1, loose=True), 1e9/125e6) diff --git a/litex_boards/targets/colorlight_i5.py b/litex_boards/targets/colorlight_i5.py new file mode 100755 index 0000000..450e370 --- /dev/null +++ b/litex_boards/targets/colorlight_i5.py @@ -0,0 +1,234 @@ +#!/usr/bin/env python3 + +# +# This file is part of LiteX-Boards. +# +# Copyright (c) 2021 Kazumoto Kojima +# SPDX-License-Identifier: BSD-2-Clause + +import os +import argparse +import sys + +from migen import * +from migen.genlib.resetsync import AsyncResetSynchronizer + +from litex.build.io import DDROutput + +from litex_boards.platforms import colorlight_i5 +from litex.build.tools import write_to_file + +from litex.build.lattice.trellis import trellis_args, trellis_argdict + +from litex.soc.cores.clock import * +from litex.soc.cores.spi_flash import SpiFlash +from litex.soc.cores.spi import SPIMaster +from litex.soc.integration.soc_core import * +from litex.soc.integration.builder import * + +from litex.soc.cores.led import LedChaser + +from litex.soc.interconnect.csr import * +from litex.soc.cores.prbs import * + +from litedram.modules import M12L64322A +from litedram.phy import GENSDRPHY, HalfRateGENSDRPHY + +from liteeth.phy.ecp5rgmii import LiteEthPHYRGMII + +# PRBS ------------------------------------------------------------------------------------------- + +class _PRBSSource(Module, AutoCSR): + def __init__(self): + self.submodules.prbs = prbs = PRBS31Generator(32) + self.data = CSRStatus(32) + self.comb += [ + self.data.status.eq(prbs.o) + ] + +# CRG ---------------------------------------------------------------------------------------------- + +class _CRG(Module): + def __init__(self, platform, sys_clk_freq, use_internal_osc=False, with_usb_pll=False, sdram_rate="1:1"): + self.rst = Signal() + self.clock_domains.cd_sys = ClockDomain() + if sdram_rate == "1:2": + self.clock_domains.cd_sys2x = ClockDomain() + self.clock_domains.cd_sys2x_ps = ClockDomain(reset_less=True) + else: + self.clock_domains.cd_sys_ps = ClockDomain(reset_less=True) + + # # # + + # Clk / Rst + if not use_internal_osc: + clk = platform.request("clk25") + clk_freq = 25e6 + else: + clk = Signal() + div = 5 + self.specials += Instance("OSCG", + p_DIV = div, + o_OSC = clk) + clk_freq = 310e6/div + + rst_n = platform.request("cpu_reset_n") + + # PLL + self.submodules.pll = pll = ECP5PLL() + self.comb += pll.reset.eq(~rst_n | self.rst) + pll.register_clkin(clk, clk_freq) + pll.create_clkout(self.cd_sys, sys_clk_freq) + if sdram_rate == "1:2": + pll.create_clkout(self.cd_sys2x, 2*sys_clk_freq) + pll.create_clkout(self.cd_sys2x_ps, 2*sys_clk_freq, phase=180) # Idealy 90° but needs to be increased. + else: + pll.create_clkout(self.cd_sys_ps, sys_clk_freq, phase=180) # Idealy 90° but needs to be increased. + + # USB PLL + if with_usb_pll: + self.submodules.usb_pll = usb_pll = ECP5PLL() + self.comb += usb_pll.reset.eq(~rst_n | self.rst) + usb_pll.register_clkin(clk, clk_freq) + self.clock_domains.cd_usb_12 = ClockDomain() + self.clock_domains.cd_usb_48 = ClockDomain() + usb_pll.create_clkout(self.cd_usb_12, 12e6, margin=0) + usb_pll.create_clkout(self.cd_usb_48, 48e6, margin=0) + + # SDRAM clock + sdram_clk = ClockSignal("sys2x_ps" if sdram_rate == "1:2" else "sys_ps") + self.specials += DDROutput(1, 0, platform.request("sdram_clock"), sdram_clk) + +# BaseSoC ------------------------------------------------------------------------------------------ + +class BaseSoC(SoCCore): + mem_map = {**SoCCore.mem_map, **{"spiflash": 0xd0000000}} + def __init__(self, board="i5", revision="7.0", sys_clk_freq=60e6, with_ethernet=False, with_etherbone=False, local_ip="", remote_ip="", eth_phy=0, use_internal_osc=False, sdram_rate="1:1", with_prbs=False, **kwargs): + board = board.lower() + assert board in ["i5"] + if board == "i5": + platform = colorlight_i5.Platform(revision=revision) + + # SoCCore ---------------------------------------------------------------------------------- + SoCCore.__init__(self, platform, int(sys_clk_freq), + ident = "LiteX SoC on Colorlight " + board.upper(), + ident_version = True, + **kwargs) + + # CRG -------------------------------------------------------------------------------------- + with_usb_pll = kwargs.get("uart_name", None) == "usb_acm" + self.submodules.crg = _CRG(platform, sys_clk_freq, use_internal_osc=use_internal_osc, with_usb_pll=with_usb_pll, sdram_rate=sdram_rate) + + # Leds ------------------------------------------------------------------------------------- + ledn = platform.request_all("user_led_n") + self.submodules.leds = LedChaser(pads=ledn, sys_clk_freq=sys_clk_freq) + self.add_csr("leds") + + # SPI Flash -------------------------------------------------------------------------------- + self.add_spi_flash(mode="1x", dummy_cycles=8) + self.add_constant("SPIFLASH_PAGE_SIZE", 256) + self.add_constant("SPIFLASH_SECTOR_SIZE", 4096) + + # SDR SDRAM -------------------------------------------------------------------------------- + if not self.integrated_main_ram_size: + sdrphy_cls = HalfRateGENSDRPHY if sdram_rate == "1:2" else GENSDRPHY + self.submodules.sdrphy = sdrphy_cls(platform.request("sdram")) + # if board == "i5" and revision == "7.0": + sdram_cls = M12L64322A # compat with EM638325-6H + self.add_sdram("sdram", + phy = self.sdrphy, + module = sdram_cls(sys_clk_freq, sdram_rate), + origin = self.mem_map["main_ram"], + size = kwargs.get("max_sdram_size", 0x40000000), + l2_cache_size = kwargs.get("l2_size", 8192), + l2_cache_min_data_width = kwargs.get("min_l2_data_width", 128), + l2_cache_reverse = True + ) + + # Ethernet / Etherbone --------------------------------------------------------------------- + if with_ethernet or with_etherbone: + self.submodules.ethphy = LiteEthPHYRGMII( + clock_pads = self.platform.request("eth_clocks", eth_phy), + pads = self.platform.request("eth", eth_phy), + tx_delay = 0) + self.add_csr("ethphy") + if with_ethernet: + self.add_ethernet(phy=self.ethphy) + if with_etherbone: + self.add_etherbone(phy=self.ethphy) + + if local_ip: + local_ip = local_ip.split(".") + self.add_constant("LOCALIP1", int(local_ip[0])) + self.add_constant("LOCALIP2", int(local_ip[1])) + self.add_constant("LOCALIP3", int(local_ip[2])) + self.add_constant("LOCALIP4", int(local_ip[3])) + + if remote_ip: + remote_ip = remote_ip.split(".") + self.add_constant("REMOTEIP1", int(remote_ip[0])) + self.add_constant("REMOTEIP2", int(remote_ip[1])) + self.add_constant("REMOTEIP3", int(remote_ip[2])) + self.add_constant("REMOTEIP4", int(remote_ip[3])) + + # PRBS ------------------------------------------------------------------------------------- + if with_prbs: + self.submodules.prbs = _PRBSSource() + self.add_csr("prbs") + + # Build -------------------------------------------------------------------------------------------- + +def main(): + parser = argparse.ArgumentParser(description="LiteX SoC on Colorlight i5") + parser.add_argument("--build", action="store_true", help="Build bitstream") + parser.add_argument("--load", action="store_true", help="Load bitstream") + parser.add_argument("--board", default="i5", help="Board type: i5 (default)") + parser.add_argument("--revision", default="7.0", type=str, help="Board revision: 7.0 (default)") + parser.add_argument("--sys-clk-freq", default=60e6, help="System clock frequency (default: 60MHz)") + parser.add_argument("--with-ethernet", action="store_true", help="Enable Ethernet support") + parser.add_argument("--with-etherbone", action="store_true", help="Enable Etherbone support") + parser.add_argument("--remote-ip", default="192.168.1.100", help="Remote IP address of TFTP server") + parser.add_argument("--local-ip", default="192.168.1.50", help="Local IP address") + parser.add_argument("--with-spi-sdcard", action="store_true", help="Enable SPI-mode SDCard support") + parser.add_argument("--with-sdcard", action="store_true", help="Enable SDCard support") + parser.add_argument("--eth-phy", default=0, type=int, help="Ethernet PHY: 0 (default) or 1") + parser.add_argument("--use-internal-osc", action="store_true", help="Use internal oscillator") + parser.add_argument("--sdram-rate", default="1:1", help="SDRAM Rate: 1:1 Full Rate (default), 1:2 Half Rate") + parser.add_argument("--l2-size", default=8192, type=int, help="L2 cache size") + parser.add_argument("--with-prbs", action="store_true", help="Enable PRBS support") + builder_args(parser) + soc_core_args(parser) + trellis_args(parser) + args = parser.parse_args() + + assert not (args.with_ethernet and args.with_etherbone) + soc = BaseSoC(board=args.board, revision=args.revision, + sys_clk_freq = int(float(args.sys_clk_freq)), + with_ethernet = args.with_ethernet, + with_etherbone = args.with_etherbone, + local_ip = args.local_ip, + remote_ip = args.remote_ip, + eth_phy = args.eth_phy, + use_internal_osc = args.use_internal_osc, + sdram_rate = args.sdram_rate, + l2_size = args.l2_size, + with_prbs = args.with_prbs, + **soc_core_argdict(args) + ) + assert not (args.with_spi_sdcard and args.with_sdcard) + soc.platform.add_extension(colorlight_i5._sdcard_pmod_io) + if args.with_spi_sdcard: + soc.add_spi_sdcard() + if args.with_sdcard: + soc.add_sdcard() + + builder = Builder(soc, **builder_argdict(args)) + + builder.build(**trellis_argdict(args), run=args.build) + + if args.load: + prog = soc.platform.create_programmer() + prog.load_bitstream(os.path.join(builder.gateware_dir, soc.build_name + ".svf")) + +if __name__ == "__main__": + main()