diff --git a/gateware/litex_main.py b/gateware/litex_main.py new file mode 100755 index 0000000..5daf74f --- /dev/null +++ b/gateware/litex_main.py @@ -0,0 +1,199 @@ +#!/usr/bin/env python3 + +# +# This file is part of LiteX-Boards. +# +# Copyright (c) 2021 Kazumoto Kojima +# SPDX-License-Identifier: BSD-2-Clause + +from migen import * + +from litex.build.io import DDROutput + +#from litex_boards.platforms import colorlight_i5 +from platforms import sonar as colorlight_i5 + +from litex.build.lattice.trellis import trellis_args, trellis_argdict + +from litex.soc.cores.clock import * +from litex.soc.integration.soc_core import * +from litex.soc.integration.builder import * +from litex.soc.cores.video import VideoHDMIPHY +from litex.soc.cores.led import LedChaser + +from litex.soc.interconnect.csr import * + +from litedram.modules import M12L64322A # Compatible with EM638325-6H. +from litedram.phy import GENSDRPHY, HalfRateGENSDRPHY + +from liteeth.phy.ecp5rgmii import LiteEthPHYRGMII + +from sampler import Sampler +from litex.soc.integration.soc import SoCRegion + +# CRG ---------------------------------------------------------------------------------------------- + +class _CRG(Module): + def __init__(self, platform, sys_clk_freq, use_internal_osc=False, with_usb_pll=False, with_video_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() + else: + self.clock_domains.cd_sys_ps = ClockDomain() + + # # # + + # 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(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. + + # 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 ------------------------------------------------------------------------------------------ + +# TODO make my own platform for this based on the colorlight one, so I can export I2C and other pins +class BaseSoC(SoCCore): + def __init__(self, sys_clk_freq=60e6, eth_phy=0, with_led_chaser=True, use_internal_osc=False, + sdram_rate="1:1", with_video_terminal=False, with_video_framebuffer=False, + **kwargs): + + # TODO change SRAM size + kwargs["integrated_sram_size"] = 64 * 1024 + kwargs["integrated_rom_init"] = "../firmware/fw.bin" + + platform = colorlight_i5.Platform(board="i9", revision="7.2", toolchain="trellis") + + # CRG -------------------------------------------------------------------------------------- + with_usb_pll = kwargs.get("uart_name", None) == "usb_acm" + with_video_pll = with_video_terminal or with_video_framebuffer + self.submodules.crg = _CRG(platform, sys_clk_freq, + use_internal_osc = use_internal_osc, + with_usb_pll = with_usb_pll, + with_video_pll = with_video_pll, + sdram_rate = sdram_rate + ) + + # SoCCore ---------------------------------------------------------------------------------- + SoCCore.__init__(self, platform, int(sys_clk_freq), ident = "LiteX SoC on Sonar FPGA", **kwargs) + + # Leds ------------------------------------------------------------------------------------- + if with_led_chaser: + ledn = platform.request_all("user_led_n") + self.submodules.leds = LedChaser(pads=ledn, sys_clk_freq=sys_clk_freq) + + # SPI Flash -------------------------------------------------------------------------------- + from litespi.modules import W25Q64 as SpiFlashModule + + from litespi.opcodes import SpiNorFlashOpCodes as Codes + self.add_spi_flash(mode="1x", module=SpiFlashModule(Codes.READ_1_1_1)) + + # 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")) + # self.add_sdram("sdram", + # phy = self.sdrphy, + # module = M12L64322A(sys_clk_freq, sdram_rate), + # l2_cache_size = kwargs.get("l2_size", 8192) + # ) + + # Ethernet / 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_ethernet(phy=self.ethphy) + + # Video ------------------------------------------------------------------------------------ + if with_video_terminal or with_video_framebuffer: + self.submodules.videophy = VideoHDMIPHY(platform.request("gpdi"), clock_domain="hdmi") + if with_video_terminal: + self.add_video_terminal(phy=self.videophy, timings="800x600@60Hz", clock_domain="hdmi") + if with_video_framebuffer: + self.add_video_framebuffer(phy=self.videophy, timings="800x600@60Hz", clock_domain="hdmi") + + self.submodules.sampler = Sampler(platform.request("adc")) + sampler_region = SoCRegion(origin=None, size=0x1000, cached=False) + #self.add_wb_slave(0x9000_0000, self.sampler.bus, 0x1000) + # TODO better way to do this? + self.bus.add_slave(name="sampler", slave=self.sampler.bus, region=sampler_region) + +# Build -------------------------------------------------------------------------------------------- + +def main(): + from litex.soc.integration.soc import LiteXSoCArgumentParser + parser = LiteXSoCArgumentParser(description="LiteX SoC on Colorlight I5") + target_group = parser.add_argument_group(title="Target options") + target_group.add_argument("--build", action="store_true", help="Build design.") + target_group.add_argument("--load", action="store_true", help="Load bitstream.") + target_group.add_argument("--sys-clk-freq", default=60e6, help="System clock frequency.") + sdopts = target_group.add_mutually_exclusive_group() + sdopts.add_argument("--with-spi-sdcard", action="store_true", help="Enable SPI-mode SDCard support.") + sdopts.add_argument("--with-sdcard", action="store_true", help="Enable SDCard support.") + target_group.add_argument("--eth-phy", default=0, type=int, help="Ethernet PHY (0 or 1).") + target_group.add_argument("--use-internal-osc", action="store_true", help="Use internal oscillator.") + target_group.add_argument("--sdram-rate", default="1:1", help="SDRAM Rate (1:1 Full Rate or 1:2 Half Rate).") + viopts = 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-framebuffer", action="store_true", help="Enable Video Framebuffer (HDMI).") + builder_args(parser) + soc_core_args(parser) + trellis_args(parser) + args = parser.parse_args() + + # Build firmware + import subprocess as sp + sp.run(["./build_and_strip.sh"], cwd="../firmware").check_returncode() + + soc = BaseSoC( + sys_clk_freq = int(float(args.sys_clk_freq)), + eth_phy = args.eth_phy, + use_internal_osc = args.use_internal_osc, + sdram_rate = args.sdram_rate, + with_video_terminal = args.with_video_terminal, + with_video_framebuffer = args.with_video_framebuffer, + **soc_core_argdict(args) + ) + 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_kargs = trellis_argdict(args) + if args.build: + builder.build(**builder_kargs) + + if args.load: + prog = soc.platform.create_programmer() + prog.load_bitstream(builder.get_bitstream_filename(mode="sram")) + +if __name__ == "__main__": + main() diff --git a/gateware/platforms/sonar.py b/gateware/platforms/sonar.py new file mode 100644 index 0000000..fd84aa8 --- /dev/null +++ b/gateware/platforms/sonar.py @@ -0,0 +1,233 @@ +""" +LiteX Platform for sonar board using Colorlight i9 module +""" + +# This file used to belong to LiteX Boards +# +# Copyright (c) 2021 Kazumoto Kojima +# Copyright (c) 2023 David Lenfesty +# SPDX-License-Identifier: BSD-2-Clause + +# The Colorlight i5 PCB and IOs have been documented by @wuxx +# https://github.com/wuxx/Colorlight-FPGA-Projects + +import copy + +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")), + + # Serial + ("serial", 0, + Subsignal("tx", Pins("P16")), + Subsignal("rx", Pins("L5")), + IOStandard("LVCMOS33") + ), + + # TODO the other serial ports + + # TODO I2C + + # 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") + ), + + # GPDI + ("gpdi", 0, + Subsignal("clk_p", Pins("J19"), IOStandard("LVCMOS33D"), Misc("DRIVE=4")), + #Subsignal("clk_n", Pins("K19"), IOStandard("LVCMOS33D"), Misc("DRIVE=4")), + Subsignal("data0_p", Pins("G19"), IOStandard("LVCMOS33D"), Misc("DRIVE=4")), + #Subsignal("data0_n", Pins("H20"), IOStandard("LVCMOS33D"), Misc("DRIVE=4")), + Subsignal("data1_p", Pins("E20"), IOStandard("LVCMOS33D"), Misc("DRIVE=4")), + #Subsignal("data1_n", Pins("F19"), IOStandard("LVCMOS33D"), Misc("DRIVE=4")), + Subsignal("data2_p", Pins("C20"), IOStandard("LVCMOS33D"), Misc("DRIVE=4")), + #Subsignal("data2_n", Pins("D19"), IOStandard("LVCMOS33D"), Misc("DRIVE=4")), + ), + + # High speed parallel ADCs + ("adc", 0, + Subsignal("data", Pins("M18 N18 N17 P18 U17 U18 T17 M17 P17 R17")), + # TODO ???? what other pins are changed in 7.2 + Subsignal("refclk", Pins("L2")), + Subsignal("oen_b", Pins("K18")), + Subsignal("standby", Pins("C18")), + Subsignal("dfs", Pins("T18")), + Subsignal("otr", Pins("R18")), + 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"), +] + +# ColorLight i9 V 7.2 hardware +# See https://github.com/wuxx/Colorlight-FPGA-Projects/blob/master/colorlight_i9_v7.2.md + +# SPIFlash (W25Q64JVSIQ) + +_io_v7_2 = copy.deepcopy(_io_v7_0) + +# Change the LED pin to "L2" + +for i, x in enumerate(_io_v7_2): + if x[:2] == ("user_led_n", 0): + # TODO fix in HW + #_io_v7_2[i] = ("user_led_n", 0, Pins("L2"), IOStandard("LVCMOS33")) + _io_v7_2[i] = ("user_led_n", 0, Pins("J19"), IOStandard("LVCMOS33")) + break + +# optional, alternative uart location +# requires "--uart-name serialx" +_io_v7_2 += [ + ("serialx", 0, Subsignal("tx", Pins("E5")), Subsignal("rx", Pins("F4")), IOStandard("LVCMOS33")) +] + +_connectors_v7_2 = copy.deepcopy(_connectors_v7_0) + +# Append the rest of the pmod interfaces + +_connectors_v7_2 += [ + # P2 + ("pmodc", "P17 R18 C18 L2 M17 R17 T18 K18"), + ("pmodd", "J20 L18 M18 N17 G20 K20 L20 N18"), + # P4 + ("pmodg", "H4 G3 F1 F2 H3 F3 E4 E1"), + ("pmodh", "- E19 B3 K5 - B2 K4 A2"), + # P5 + ("pmodi", "D18 G5 F5 E5 D17 D16 E6 F4"), + ("pmodj", "J17 H17 H16 G16 H18 G18 F18 E18"), + # P6 + ("pmodk", "R3 M4 L5 J16 N4 L4 P16 J18"), + ("pmodl", "R1 U1 W1 M1 T1 Y2 V1 N2"), +] + +# 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, board="i5", revision="7.0", toolchain="trellis"): + if board == "i5": + 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] + if board == "i9": + assert revision in ["7.2"] + self.revision = revision + device = {"7.2": "LFE5U-45F-6BG381C"}[revision] + io = {"7.2": _io_v7_2}[revision] + connectors = {"7.2": _connectors_v7_2}[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) \ No newline at end of file diff --git a/gateware/sampler.py b/gateware/sampler.py new file mode 100644 index 0000000..25c8f30 --- /dev/null +++ b/gateware/sampler.py @@ -0,0 +1,28 @@ +from migen import * + +from litex.soc.interconnect.wishbone import * +from litex.soc.integration.soc import SoCRegion + +class Sampler(Module): + def __init__(self, adc_pins): + # TODO correct addr width + self.bus = Interface(data_width=32, adr_width=11) + + # self.clock_domains.foo = ClockDomain() is how to add a new clock domain, accessible at self.foo + + # Provide a slow clock to the ADC, 60MHz / 600 = 100kHz + self._counter = Signal(32) + self.sync += self._counter.eq(self._counter + 1) + self.sync += If(self._counter >= 600, self._counter.eq(0), adc_pins.refclk.eq(~adc_pins.refclk)) + + # Set config pins to constant values + self.comb += adc_pins.oen_b.eq(0) # Data pins enable + self.comb += adc_pins.standby.eq(0) # Sampling standby + self.comb += adc_pins.dfs.eq(0) # DFS (raw or two's complement) + # The only remaining pin, OTR, is an out of range status indicator + + # Read directly from the data pins into the wishbone bus for now, just for bringup + self.comb += self.bus.dat_r.eq(adc_pins.data) + self.sync += self.bus.ack.eq(0) + self.sync += If(self.bus.cyc & self.bus.stb, self.bus.ack.eq(1)) +