From f66179b009cfd18ff8e33b134fdc2efc885f6f3f Mon Sep 17 00:00:00 2001 From: Andelf Date: Sat, 14 Dec 2024 18:55:28 +0800 Subject: [PATCH] Add support for LCKFB LJPI See-also: https://wiki.lckfb.com/zh-hans/fpga-ljpi/ --- litex_boards/platforms/lckfb_ljpi.py | 180 +++++++++++++++++++++++ litex_boards/targets/lckfb_ljpi.py | 204 +++++++++++++++++++++++++++ 2 files changed, 384 insertions(+) create mode 100644 litex_boards/platforms/lckfb_ljpi.py create mode 100755 litex_boards/targets/lckfb_ljpi.py diff --git a/litex_boards/platforms/lckfb_ljpi.py b/litex_boards/platforms/lckfb_ljpi.py new file mode 100644 index 0000000..4c6cd47 --- /dev/null +++ b/litex_boards/platforms/lckfb_ljpi.py @@ -0,0 +1,180 @@ +# +# This file is part of LiteX-Boards. +# +# Copyright (c) 2024 Andelf +# SPDX-License-Identifier: BSD-2-Clause +# +# LCKFB LJPI FPGA board: https://wiki.lckfb.com/zh-hans/fpga-ljpi/ + +from migen import * + +from litex.build.generic_platform import * +from litex.build.gowin.platform import GowinPlatform +from litex.build.gowin.programmer import GowinProgrammer, GOWIN_CABLE_FT2CH +from litex.build.openfpgaloader import OpenFPGALoader + + +# IOs ---------------------------------------------------------------------------------------------- + +_io = [ + # Clk / Rst. + ("clk50", 0, Pins("T7"), IOStandard("LVCMOS33")), + + # Serial. + ("serial", 0, + Subsignal("tx", Pins("F12")), + Subsignal("rx", Pins("F13")), + IOStandard("LVCMOS33") + ), + + # Leds. LED2 and LED3 are RGB LEDs. + ("led", 0, Pins( "R9"), IOStandard("LVCMOS33")), + ("led", 1, Pins("C10"), IOStandard("LVCMOS33")), + ("led", 3, Pins( "R7"), IOStandard("LVCMOS33")), + ("led", 2, Pins( "N6"), IOStandard("LVCMOS33")), + ("led", 4, Pins("T10"), IOStandard("LVCMOS33")), + ("led", 5, Pins( "P7"), IOStandard("LVCMOS33")), + + # Buttons. SW2 + ("btn_n", 0, Pins("D11"), IOStandard("LVCMOS33")), + # ("btn_n", 1, Pins("F10"), IOStandard("LVCMOS33")), + + # Reset. Use SW3 as reset button. + ("rst_n", 0, Pins("F10"), IOStandard("LVCMOS33")), + + # SPIFlash. + # W25Q64JVSSIQ + ("spiflash", 0, + Subsignal("cs_n", Pins("M9"), IOStandard("LVCMOS33")), + Subsignal("clk", Pins("L10"), IOStandard("LVCMOS33")), + Subsignal("miso", Pins("P10"), IOStandard("LVCMOS33")), + Subsignal("mosi", Pins("R10"), IOStandard("LVCMOS33")), + ), + + # HDMI(LVDS). + ("hdmi", 0, + Subsignal("clk_p", Pins("M10")), + Subsignal("clk_n", Pins("N11")), + Subsignal("data0_p", Pins("R13")), + Subsignal("data0_n", Pins("T14")), + Subsignal("data1_p", Pins("R11")), + Subsignal("data1_n", Pins("T12")), + Subsignal("data2_p", Pins("R12")), + Subsignal("data2_n", Pins("P13")), + IOStandard("LVCMOS33D DRIVE=8"), + Misc("PULL_MODE=NONE"), + ), + + # 8-segment LED display, Common Anode. + ("seg8", 0, Pins("G13"), IOStandard("LVCMOS33")), + ("seg8", 1, Pins("H16"), IOStandard("LVCMOS33")), + ("seg8", 2, Pins("H12"), IOStandard("LVCMOS33")), + ("seg8", 3, Pins("H13"), IOStandard("LVCMOS33")), + ("seg8", 4, Pins("H14"), IOStandard("LVCMOS33")), + ("seg8", 5, Pins("G12"), IOStandard("LVCMOS33")), + ("seg8", 6, Pins("G11"), IOStandard("LVCMOS33")), + # ("seg8", 7, Pins("L14"), IOStandard("LVCMOS33")), + + # DDR3 SDRAM + # MT41J128M16JT-125K. + ("ddram", 0, + Subsignal("a", Pins("F7 A4 D6 F8 C4 E6 B1 D8 A5 F9 K3 B7 A3 C8"), + IOStandard("SSTL15")), + Subsignal("ba", Pins("H4 D3 H5"), IOStandard("SSTL15")), + Subsignal("ras_n", Pins("R4"), IOStandard("SSTL15")), + Subsignal("cas_n", Pins("R6"), IOStandard("SSTL15")), + Subsignal("we_n", Pins("L2"), IOStandard("SSTL15")), + Subsignal("cs_n", Pins("P5"), IOStandard("SSTL15")), + Subsignal("dm", Pins("G1 K5"), IOStandard("SSTL15")), + Subsignal("dq", Pins( + "G5 F5 F4 F3 E2 C1 E1 B3", + "M3 K4 N2 L1 P4 H3 R1 M2"), + IOStandard("SSTL15"), + Misc("VREF=INTERNAL")), + Subsignal("dqs_p", Pins("G2 J5"), IOStandard("SSTL15D")), + Subsignal("dqs_n", Pins("G3 K6"), IOStandard("SSTL15D")), + Subsignal("clk_p", Pins("J1"), IOStandard("SSTL15D")), + Subsignal("clk_n", Pins("J3"), IOStandard("SSTL15D")), + Subsignal("cke", Pins("J2"), IOStandard("SSTL15")), + Subsignal("odt", Pins("R3"), IOStandard("SSTL15")), + Subsignal("reset_n", Pins("B9"), IOStandard("SSTL15")), + ), + + # USB Type-C. + ("usb", 0, + Subsignal("d_p", Pins("M14")), + Subsignal("d_n", Pins("M15")), + Subsignal("pullup", Pins("L15")), + IOStandard("LVCMOS33") + ), + + # Wired to onboard GD32 MCU. + ("mcu_bus", 0, + Subsignal("data", Pins("PA0 PA1 PA2 PA3 PA4 PA5 PA6 PA7"), IOStandard("LVCMOS33"))), +] + +# Connector IOs ------------------------------------------------------------------------------------ + +_connectors = [ + ("h5", { + 1: "K16", 2: "J15", + 3: "J14", 4: "J16", + 5: "F14", 6: "F16", + 7: "J13", 8: "H11", + 9: "E16", 10: "F15", + 11: "C16", 12: "D15", + 13: "D16", 14: "E14", + 15: "B13", 16: "A14", + 17: "B14", 18: "A15", + 19: "A12", 20: "B11", + 21: "B12", 22: "C12", + 23: "C11", 24: "A11", + 25: "C9", 26: "A9", + 27: "E10", 28: "D10", + 29: "N10", 30: "M11", + 31: "P12", 32: "P11", + 33: "T13", 34: "T11", + 35: "R14", 36: "T15", + }), + ("h6", { + 7: "G15", 8: "G14", + 9: "G16", 10: "H15", + 11: "L9", + 13: "L8", 14: "N9", + 15: "M6", 16: "M8", + 17: "M7", 18: "T6", + 19: "N7", 20: "N8", + 21: "P6", 22: "R8", + 23: "T9", 24: "P9", + 25: "T8", 26: "P8", + 27: "K14", 28: "K15", + 29: "K13", 30: "K12", + 31: "N15", 32: "P16", + 33: "P15", 34: "R16", + 35: "N16", 36: "N14", + }), +] + +# Platform ----------------------------------------------------------------------------------------- + +class Platform(GowinPlatform): + default_clk_name = "clk50" + default_clk_period = 1e9/50e6 + + def __init__(self, toolchain="gowin"): + + GowinPlatform.__init__(self, "GW2A-LV18PG256C8/I7", _io, _connectors, toolchain=toolchain, devicename="GW2A-18C") + + self.toolchain.options["use_mspi_as_gpio"] = 1 + self.toolchain.options["use_sspi_as_gpio"] = 1 + + def create_programmer(self, kit="openfpgaloader"): + if kit == "gowin": + # The board provides an external programmer with an emulated FT2232 + return GowinProgrammer(self.devicename, cable=GOWIN_CABLE_FT2CH) + else: + return OpenFPGALoader(cable="ft2232") + + def do_finalize(self, fragment): + GowinPlatform.do_finalize(self, fragment) + self.add_period_constraint(self.lookup_request("clk50", loose=True), 1e9/50e6) diff --git a/litex_boards/targets/lckfb_ljpi.py b/litex_boards/targets/lckfb_ljpi.py new file mode 100755 index 0000000..bb4fbe4 --- /dev/null +++ b/litex_boards/targets/lckfb_ljpi.py @@ -0,0 +1,204 @@ +#!/usr/bin/env python3 + +# +# This file is part of LiteX-Boards. +# +# Copyright (c) 2024 Andelf +# SPDX-License-Identifier: BSD-2-Clause + +from migen import * +from migen.genlib.resetsync import AsyncResetSynchronizer + +from litex.gen import * + +from litex.soc.cores.clock.gowin_gw2a import GW2APLL +from litex.soc.integration.soc_core import * +from litex.soc.integration.builder import * +from litex.soc.cores.led import LedChaser +from litex.soc.cores.gpio import GPIOIn +from litex.soc.cores.video import * + +from litex_boards.platforms import lckfb_ljpi + +from litedram.modules import MT41J128M16 +from litedram.phy import GW2DDRPHY + +# CRG ---------------------------------------------------------------------------------------------- + +class _CRG(LiteXModule): + def __init__(self, platform, sys_clk_freq, with_hdmi=False, with_dram=False): + self.rst = Signal() + self.cd_sys = ClockDomain() + self.cd_por = ClockDomain() + if with_dram: + self.cd_init = ClockDomain() + self.cd_sys2x = ClockDomain() + self.cd_sys2x_i = ClockDomain() + if with_hdmi: + self.cd_hdmi = ClockDomain() + self.cd_hdmi5x = ClockDomain() + + # # # + + self.stop = Signal() + self.reset = Signal() + + # Clk / Rst + clk50 = platform.request("clk50") + rst_n = platform.request("rst_n") + + # Power on reset (the onboard POR is not aware of reprogramming) + por_count = Signal(16, reset=2**16-1) + por_done = Signal() + self.comb += self.cd_por.clk.eq(clk50) + self.comb += por_done.eq(por_count == 0) + self.sync.por += If(~por_done, por_count.eq(por_count - 1)) + + # PLL + self.pll = pll = GW2APLL(devicename=platform.devicename, device=platform.device) + self.comb += pll.reset.eq(~por_done | ~rst_n) + pll.register_clkin(clk50, 50e6) + if with_dram: + # 2:1 clock needed for DDR + pll.create_clkout(self.cd_sys2x_i, 2*sys_clk_freq) + self.specials += [ + Instance("DHCEN", + i_CLKIN = self.cd_sys2x_i.clk, + i_CE = self.stop, + o_CLKOUT = self.cd_sys2x.clk), + Instance("CLKDIV", + p_DIV_MODE = "2", + i_CALIB = 0, + i_HCLKIN = self.cd_sys2x.clk, + i_RESETN = ~self.reset, + o_CLKOUT = self.cd_sys.clk), + ] + + # Init clock domain + self.comb += self.cd_init.clk.eq(clk50) + self.comb += self.cd_init.rst.eq(pll.reset) + else: + pll.create_clkout(self.cd_sys, sys_clk_freq) + + self.specials += AsyncResetSynchronizer(self.cd_sys, ~pll.locked | self.rst | self.reset) + + # Video PLL + if with_hdmi: + self.video_pll = video_pll = GW2APLL(devicename=platform.devicename, device=platform.device) + video_pll.register_clkin(clk50, 50e6) + video_pll.create_clkout(self.cd_hdmi5x, 125e6, margin=1e-3) + self.specials += Instance("CLKDIV", + p_DIV_MODE = "5", + i_RESETN = 1, # Disable reset signal. + i_CALIB = 0, # No calibration. + i_HCLKIN = self.cd_hdmi5x.clk, + o_CLKOUT = self.cd_hdmi.clk + ) + +# BaseSoC ------------------------------------------------------------------------------------------ + +class BaseSoC(SoCCore): + def __init__(self, toolchain="gowin", sys_clk_freq=50e6, + with_spi_flash = False, + with_led_chaser = True, + with_buttons = True, + with_video_terminal = False, + with_video_colorbars = False, + **kwargs): + + platform = lckfb_ljpi.Platform(toolchain=toolchain) + + with_hdmi = with_video_terminal or with_video_colorbars + + # CRG -------------------------------------------------------------------------------------- + with_dram = (kwargs.get("integrated_main_ram_size", 0) == 0) + assert not (toolchain == "apicula" and with_dram) + self.crg = _CRG(platform, sys_clk_freq, with_hdmi=with_hdmi, with_dram=with_dram) + + # SoCCore ---------------------------------------------------------------------------------- + SoCCore.__init__(self, platform, sys_clk_freq, ident="LiteX SoC on LCKFB LJPI", **kwargs) + + # DDR3 SDRAM ------------------------------------------------------------------------------- + # if not self.integrated_main_ram_size: + if with_dram: + self.ddrphy = GW2DDRPHY( + pads = platform.request("ddram"), + sys_clk_freq = sys_clk_freq + ) + self.ddrphy.settings.rtt_nom = "disabled" + self.comb += self.crg.stop.eq(self.ddrphy.init.stop) + self.comb += self.crg.reset.eq(self.ddrphy.init.reset) + self.add_sdram("sdram", + phy = self.ddrphy, + module = MT41J128M16(sys_clk_freq, "1:4"), + l2_cache_size = kwargs.get("l2_size", 8192) + ) + + # SPI Flash -------------------------------------------------------------------------------- + if with_spi_flash: + from litespi.modules import W25Q64JV as SpiFlashModule + from litespi.opcodes import SpiNorFlashOpCodes as Codes + self.add_spi_flash(mode="1x", module=SpiFlashModule(Codes.READ_1_1_1)) + + # Video ------------------------------------------------------------------------------------ + if with_hdmi: + hdmi_pads = platform.request("hdmi") + self.videophy = VideoHDMIPHY(hdmi_pads, clock_domain="hdmi") + if with_video_terminal: + # self.add_video_terminal(phy=self.videophy, timings="640x480@75Hz", clock_domain="hdmi") + self.add_video_terminal(phy=self.videophy, timings="800x600@75Hz", clock_domain="hdmi") + if with_video_colorbars: + # self.add_video_colorbars(phy=self.videophy, timings="640x480@60Hz", clock_domain="hdmi") + # self.add_video_colorbars(phy=self.videophy, timings="800x600@75Hz", clock_domain="hdmi") + self.add_video_colorbars(phy=self.videophy, timings="1024x768@60Hz", clock_domain="hdmi") + + # Leds ------------------------------------------------------------------------------------- + if with_led_chaser: + self.leds = LedChaser( + pads = platform.request_all("led"), + sys_clk_freq = sys_clk_freq + ) + + # Buttons ---------------------------------------------------------------------------------- + if with_buttons: + self.buttons = GPIOIn(pads=~platform.request_all("btn_n")) + + +# Build -------------------------------------------------------------------------------------------- + +def main(): + from litex.build.parser import LiteXArgumentParser + parser = LiteXArgumentParser(platform=lckfb_ljpi.Platform, description="LiteX SoC on LCKFB LJPI.") + parser.add_target_argument("--flash", action="store_true", help="Flash Bitstream.") + parser.add_target_argument("--sys-clk-freq", default=50e6, type=float, help="System clock frequency.") + parser.add_target_argument("--with-spi-flash", action="store_true", help="Enable SPI Flash (MMAPed).") + viopts = parser.target_group.add_mutually_exclusive_group() + viopts.add_argument("--with-video-terminal", action="store_true", help="Enable Video Terminal (HDMI).") + viopts.add_argument("--with-video-colorbars", action="store_true", help="Enable Video Colorbars (HDMI).") + parser.add_target_argument("--prog-kit", default="gpwin", help="Programmer select from Gowin/openFPGALoader.") + + args = parser.parse_args() + + soc = BaseSoC( + toolchain = args.toolchain, + sys_clk_freq = args.sys_clk_freq, + with_spi_flash = args.with_spi_flash, + with_video_terminal = args.with_video_terminal, + with_video_colorbars = args.with_video_colorbars, + **parser.soc_argdict + ) + + builder = Builder(soc, **parser.builder_argdict) + if args.build: + builder.build(**parser.toolchain_argdict) + + if args.load: + prog = soc.platform.create_programmer(kit=args.prog_kit) + prog.load_bitstream(builder.get_bitstream_filename(mode="sram")) + + if args.flash: + prog = soc.platform.create_programmer(kit=args.prog_kit) + prog.flash(0, builder.get_bitstream_filename(mode="flash", ext=".fs")) + +if __name__ == "__main__": + main()