targets/fomu: base it on iCEBreaker target + USB-ACM.
This uniformizes Fomu target with others, provide a simple example of LiteX SoC on Fomu and will ease maintenance.
This commit is contained in:
parent
79ef091a06
commit
fff20f7532
|
@ -5,295 +5,160 @@
|
||||||
#
|
#
|
||||||
# Copyright (c) 2019 Sean Cross <sean@xobs.io>
|
# Copyright (c) 2019 Sean Cross <sean@xobs.io>
|
||||||
# Copyright (c) 2018 David Shah <dave@ds0.me>
|
# Copyright (c) 2018 David Shah <dave@ds0.me>
|
||||||
|
# Copyright (c) 2020 Florent Kermarrec <florent@enjoy-digital.fr>
|
||||||
# SPDX-License-Identifier: BSD-2-Clause
|
# SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import sys
|
||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
from migen import *
|
from migen import *
|
||||||
from migen.genlib.resetsync import AsyncResetSynchronizer
|
from migen.genlib.resetsync import AsyncResetSynchronizer
|
||||||
|
|
||||||
from litex.soc.cores import up5kspram
|
from litex_boards.platforms import fomu_pvt
|
||||||
from litex.soc.integration.soc_core import SoCCore
|
|
||||||
from litex.soc.integration.builder import Builder, builder_argdict, builder_args
|
|
||||||
from litex.soc.integration.soc_core import soc_core_argdict, soc_core_args
|
|
||||||
from litex.soc.integration.doc import AutoDoc
|
|
||||||
|
|
||||||
from valentyusb.usbcore import io as usbio
|
from litex.soc.cores.up5kspram import Up5kSPRAM
|
||||||
from valentyusb.usbcore.cpu import dummyusb, epfifo, eptri
|
from litex.soc.cores.spi_flash import SpiFlash
|
||||||
|
from litex.soc.cores.clock import iCE40PLL
|
||||||
|
from litex.soc.integration.soc_core import *
|
||||||
|
from litex.soc.integration.soc import SoCRegion
|
||||||
|
from litex.soc.integration.builder import *
|
||||||
|
from litex.soc.cores.led import LedChaser
|
||||||
|
|
||||||
import os, shutil, subprocess
|
kB = 1024
|
||||||
|
mB = 1024*kB
|
||||||
|
|
||||||
# CRG ----------------------------------------------------------------------------------------------
|
# CRG ----------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
class _CRG(Module, AutoDoc):
|
class _CRG(Module):
|
||||||
"""Fomu Clock Resource Generator
|
def __init__(self, platform, sys_clk_freq):
|
||||||
|
assert sys_clk_freq == 12e6
|
||||||
Fomu is a USB device, which means it must have a 12 MHz clock. Valentyusb
|
|
||||||
oversamples the clock by 4x, which drives the requirement for a 48 MHz clock.
|
|
||||||
The ICE40UP5k is a relatively low speed grade of FPGA that is incapable of
|
|
||||||
running the entire design at 48 MHz, so the majority of the logic is placed
|
|
||||||
in the 12 MHz domain while only critical USB logic is placed in the fast
|
|
||||||
48 MHz domain.
|
|
||||||
|
|
||||||
Fomu has a 48 MHz crystal on it, which provides the raw clock input. This
|
|
||||||
signal is fed through the ICE40 PLL in order to divide it down into a 12 MHz
|
|
||||||
signal and keep the clocks within 1ns of phase. Earlier designs used a simple
|
|
||||||
flop, however this proved unreliable when the FPGA became very full.
|
|
||||||
|
|
||||||
The following clock domains are available on this design:
|
|
||||||
|
|
||||||
+---------+------------+---------------------------------+
|
|
||||||
| Name | Frequency | Description |
|
|
||||||
+=========+============+=================================+
|
|
||||||
| usb_48 | 48 MHz | Raw USB signals and pulse logic |
|
|
||||||
+---------+------------+---------------------------------+
|
|
||||||
| usb_12 | 12 MHz | USB control logic |
|
|
||||||
+---------+------------+---------------------------------+
|
|
||||||
| sys | 12 MHz | System CPU and wishbone bus |
|
|
||||||
+---------+------------+---------------------------------+
|
|
||||||
"""
|
|
||||||
def __init__(self, platform):
|
|
||||||
clk48_raw = platform.request("clk48")
|
|
||||||
clk12 = Signal()
|
|
||||||
|
|
||||||
reset_delay = Signal(12, reset=4095)
|
|
||||||
self.clock_domains.cd_por = ClockDomain()
|
|
||||||
self.reset = Signal()
|
|
||||||
|
|
||||||
self.clock_domains.cd_sys = ClockDomain()
|
self.clock_domains.cd_sys = ClockDomain()
|
||||||
|
self.clock_domains.cd_por = ClockDomain(reset_less=True)
|
||||||
self.clock_domains.cd_usb_12 = ClockDomain()
|
self.clock_domains.cd_usb_12 = ClockDomain()
|
||||||
self.clock_domains.cd_usb_48 = ClockDomain()
|
self.clock_domains.cd_usb_48 = ClockDomain()
|
||||||
|
|
||||||
platform.add_period_constraint(self.cd_usb_48.clk, 1e9/48e6)
|
# # #
|
||||||
platform.add_period_constraint(self.cd_sys.clk, 1e9/12e6)
|
|
||||||
platform.add_period_constraint(self.cd_usb_12.clk, 1e9/12e6)
|
|
||||||
platform.add_period_constraint(clk48_raw, 1e9/48e6)
|
|
||||||
|
|
||||||
# POR reset logic- POR generated from sys clk, POR logic feeds sys clk
|
# Clk/Rst
|
||||||
# reset.
|
clk48 = platform.request("clk48")
|
||||||
self.comb += [
|
platform.add_period_constraint(clk48, 1e9/48e6)
|
||||||
self.cd_por.clk.eq(self.cd_sys.clk),
|
|
||||||
self.cd_sys.rst.eq(reset_delay != 0),
|
|
||||||
self.cd_usb_12.rst.eq(reset_delay != 0),
|
|
||||||
]
|
|
||||||
|
|
||||||
# POR reset logic- POR generated from sys clk, POR logic feeds sys clk
|
# Power On Reset
|
||||||
# reset.
|
por_count = Signal(16, reset=2**16-1)
|
||||||
self.comb += [
|
por_done = Signal()
|
||||||
self.cd_usb_48.rst.eq(reset_delay != 0),
|
self.comb += self.cd_por.clk.eq(ClockSignal())
|
||||||
]
|
self.comb += por_done.eq(por_count == 0)
|
||||||
|
self.sync.por += If(~por_done, por_count.eq(por_count - 1))
|
||||||
|
|
||||||
self.comb += self.cd_usb_48.clk.eq(clk48_raw)
|
# USB PLL
|
||||||
|
self.submodules.pll = pll = iCE40PLL()
|
||||||
self.specials += Instance(
|
pll.clko_freq_range = ( 12e6, 275e9) # FIXME: improve iCE40PLL to avoid lowering clko_freq_min.
|
||||||
"SB_PLL40_CORE",
|
pll.register_clkin(clk48, 48e6)
|
||||||
# Parameters
|
pll.create_clkout(self.cd_usb_12, 12e6, with_reset=False)
|
||||||
p_DIVR = 0,
|
self.comb += self.cd_usb_48.clk.eq(clk48)
|
||||||
p_DIVF = 15,
|
self.specials += AsyncResetSynchronizer(self.cd_usb_12, ~por_done | ~pll.locked)
|
||||||
p_DIVQ = 5,
|
self.specials += AsyncResetSynchronizer(self.cd_usb_48, ~por_done | ~pll.locked)
|
||||||
p_FILTER_RANGE = 1,
|
|
||||||
p_FEEDBACK_PATH = "SIMPLE",
|
|
||||||
p_DELAY_ADJUSTMENT_MODE_FEEDBACK = "FIXED",
|
|
||||||
p_FDA_FEEDBACK = 15,
|
|
||||||
p_DELAY_ADJUSTMENT_MODE_RELATIVE = "FIXED",
|
|
||||||
p_FDA_RELATIVE = 0,
|
|
||||||
p_SHIFTREG_DIV_MODE = 1,
|
|
||||||
p_PLLOUT_SELECT = "GENCLK_HALF",
|
|
||||||
p_ENABLE_ICEGATE = 0,
|
|
||||||
# IO
|
|
||||||
i_REFERENCECLK = clk48_raw,
|
|
||||||
o_PLLOUTCORE = clk12,
|
|
||||||
# o_PLLOUTGLOBAL = clk12,
|
|
||||||
#i_EXTFEEDBACK,
|
|
||||||
#i_DYNAMICDELAY,
|
|
||||||
#o_LOCK,
|
|
||||||
i_BYPASS = 0,
|
|
||||||
i_RESETB = 1,
|
|
||||||
#i_LATCHINPUTVALUE,
|
|
||||||
#o_SDO,
|
|
||||||
#i_SDI,
|
|
||||||
)
|
|
||||||
|
|
||||||
self.comb += self.cd_sys.clk.eq(clk12)
|
|
||||||
self.comb += self.cd_usb_12.clk.eq(clk12)
|
|
||||||
|
|
||||||
self.sync.por += \
|
|
||||||
If(reset_delay != 0,
|
|
||||||
reset_delay.eq(reset_delay - 1)
|
|
||||||
)
|
|
||||||
self.specials += AsyncResetSynchronizer(self.cd_por, self.reset)
|
|
||||||
|
|
||||||
|
# Sys Clk
|
||||||
|
self.comb += self.cd_sys.clk.eq(self.cd_usb_12.clk)
|
||||||
|
self.specials += AsyncResetSynchronizer(self.cd_sys, ~por_done | ~pll.locked)
|
||||||
|
|
||||||
# BaseSoC ------------------------------------------------------------------------------------------
|
# BaseSoC ------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
class BaseSoC(SoCCore):
|
class BaseSoC(SoCCore):
|
||||||
"""A SoC on Fomu, optionally with a softcore CPU"""
|
mem_map = {**SoCCore.mem_map, **{"spiflash": 0x80000000}}
|
||||||
|
def __init__(self, bios_flash_offset, **kwargs):
|
||||||
# Create a default CSR map to prevent values from getting reassigned.
|
kwargs["uart_name"] = "usb_acm" # Enforce UART to USB-ACM
|
||||||
# This increases consistency across litex versions.
|
sys_clk_freq = int(12e6)
|
||||||
SoCCore.csr_map = {
|
platform = fomu_pvt.Platform()
|
||||||
"ctrl": 0, # provided by default (optional)
|
|
||||||
"crg": 1, # user
|
|
||||||
"uart_phy": 2, # provided by default (optional)
|
|
||||||
"uart": 3, # provided by default (optional)
|
|
||||||
"identifier_mem": 4, # provided by default (optional)
|
|
||||||
"timer0": 5, # provided by default (optional)
|
|
||||||
"cpu_or_bridge": 8,
|
|
||||||
"usb": 9,
|
|
||||||
"picorvspi": 10,
|
|
||||||
"touch": 11,
|
|
||||||
"reboot": 12,
|
|
||||||
"rgb": 13,
|
|
||||||
"version": 14,
|
|
||||||
}
|
|
||||||
|
|
||||||
# Statically-define the memory map, to prevent it from shifting across
|
|
||||||
# various litex versions.
|
|
||||||
SoCCore.mem_map = {
|
|
||||||
"rom": 0x00000000, # (default shadow @0x80000000)
|
|
||||||
"sram": 0x10000000, # (default shadow @0xa0000000)
|
|
||||||
"spiflash": 0x20000000, # (default shadow @0xa0000000)
|
|
||||||
"main_ram": 0x40000000, # (default shadow @0xc0000000)
|
|
||||||
"csr": 0xe0000000, # (default shadow @0x60000000)
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self, board,
|
|
||||||
pnr_placer="heap", pnr_seed=0, usb_core="dummyusb", usb_bridge=False,
|
|
||||||
use_dsp=True, **kwargs):
|
|
||||||
"""Create a basic SoC for Fomu.
|
|
||||||
|
|
||||||
Create a basic SoC for Fomu, including a 48 MHz and 12 MHz clock
|
|
||||||
domain called `usb_48` and `usb_12`. The `sys` frequency will
|
|
||||||
run at 12 MHz.
|
|
||||||
|
|
||||||
The USB core will optionally have a bridge to the Wishbone bus.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
board (str): Which Fomu board to build for: pvt, evt, or hacker
|
|
||||||
pnr_placer (str): Which placer to use in nextpnr
|
|
||||||
pnr_seed (int): Which seed to use in nextpnr
|
|
||||||
usb_core (str): The name of the USB core to use, if any: dummyusb, epfifo, eptri
|
|
||||||
usb_bridge (bool): Whether to include a USB-to-Wishbone bridge
|
|
||||||
Raises:
|
|
||||||
ValueError: If either the `usb_core` or `board` are unrecognized
|
|
||||||
Returns:
|
|
||||||
Newly-constructed SoC
|
|
||||||
"""
|
|
||||||
if board == "pvt":
|
|
||||||
from litex_boards.platforms.fomu_pvt import Platform
|
|
||||||
elif board == "hacker":
|
|
||||||
from litex_boards.platforms.fomu_hacker import Platform
|
|
||||||
elif board == "evt":
|
|
||||||
from litex_boards.platforms.fomu_evt import Platform
|
|
||||||
else:
|
|
||||||
raise ValueError("unrecognized fomu board: {}".format(board))
|
|
||||||
platform = Platform()
|
|
||||||
|
|
||||||
if "cpu_type" not in kwargs:
|
|
||||||
kwargs["cpu_type"] = None
|
|
||||||
kwargs["cpu_variant"] = None
|
|
||||||
|
|
||||||
clk_freq = int(12e6)
|
|
||||||
|
|
||||||
if "with_uart" not in kwargs:
|
|
||||||
kwargs["with_uart"] = False
|
|
||||||
|
|
||||||
if "with_ctrl" not in kwargs:
|
|
||||||
kwargs["with_ctrl"] = False
|
|
||||||
|
|
||||||
|
# Disable Integrated ROM/SRAM since too large for iCE40 and UP5K has specific SPRAM.
|
||||||
kwargs["integrated_sram_size"] = 0
|
kwargs["integrated_sram_size"] = 0
|
||||||
SoCCore.__init__(self, platform, clk_freq, **kwargs)
|
kwargs["integrated_rom_size"] = 0
|
||||||
|
|
||||||
self.submodules.crg = _CRG(platform)
|
# Set CPU variant / reset address
|
||||||
|
kwargs["cpu_reset_address"] = self.mem_map["spiflash"] + bios_flash_offset
|
||||||
|
|
||||||
# UP5K has single port RAM, which is a dedicated 128 kilobyte block.
|
# Serial -----------------------------------------------------------------------------------
|
||||||
# Use this as CPU RAM.
|
# FIXME: do proper install of ValentyUSB.
|
||||||
spram_size = 128*1024
|
# FIXME: replace IoBuf with https://github.com/im-tomu/valentyusb/blob/master/valentyusb/usbcore/io.py#L13-L61.
|
||||||
self.submodules.spram = up5kspram.Up5kSPRAM(size=spram_size)
|
os.system("git clone https://github.com/gregdavill/valentyusb -b hw_cdc_eptri")
|
||||||
self.register_mem("sram", self.mem_map["sram"], self.spram.bus, spram_size)
|
sys.path.append("valentyusb")
|
||||||
|
|
||||||
if usb_core is not None:
|
# SoCCore ----------------------------------------------------------------------------------
|
||||||
# Add USB pads. We use DummyUsb, which simply enumerates as a USB
|
SoCCore.__init__(self, platform, sys_clk_freq,
|
||||||
# device. Then all interaction is done via the wishbone bridge.
|
ident = "LiteX SoC on Fomu",
|
||||||
usb_pads = platform.request("usb")
|
ident_version = True,
|
||||||
usb_iobuf = usbio.IoBuf(usb_pads.d_p, usb_pads.d_n, usb_pads.pullup)
|
**kwargs)
|
||||||
if usb_core == "dummyusb":
|
|
||||||
self.submodules.usb = dummyusb.DummyUsb(usb_iobuf, debug=usb_bridge)
|
# CRG --------------------------------------------------------------------------------------
|
||||||
elif usb_core == "epfifo":
|
self.submodules.crg = _CRG(platform, sys_clk_freq)
|
||||||
self.submodules.usb = epfifo.PerEndpointFifo(usb_iobuf, debug=usb_bridge)
|
|
||||||
elif usb_core == "eptri":
|
# 128KB SPRAM (used as SRAM) ---------------------------------------------------------------
|
||||||
self.submodules.usb = eptri.TriEndpointInterface(usb_iobuf, debug=usb_bridge)
|
self.submodules.spram = Up5kSPRAM(size=128*kB)
|
||||||
|
self.bus.add_slave("sram", self.spram.bus, SoCRegion(size=128*kB))
|
||||||
|
|
||||||
|
# SPI Flash --------------------------------------------------------------------------------
|
||||||
|
self.add_spi_flash(mode="1x", dummy_cycles=8)
|
||||||
|
|
||||||
|
# Add ROM linker region --------------------------------------------------------------------
|
||||||
|
self.bus.add_region("rom", SoCRegion(
|
||||||
|
origin = self.mem_map["spiflash"] + bios_flash_offset,
|
||||||
|
size = 32*kB,
|
||||||
|
linker = True)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Leds -------------------------------------------------------------------------------------
|
||||||
|
self.submodules.leds = LedChaser(
|
||||||
|
pads = platform.request_all("user_led_n"),
|
||||||
|
sys_clk_freq = sys_clk_freq)
|
||||||
|
self.add_csr("leds")
|
||||||
|
|
||||||
|
# Flash --------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def flash(bios_flash_offset):
|
||||||
|
from litex.build.dfu import DFUProg
|
||||||
|
prog = DFUProg(vid="1209", pid="5bf0")
|
||||||
|
bitstream = open("build/fomu_pvt/gateware/fomu_pvt.bin", "rb")
|
||||||
|
bios = open("build/fomu_pvt/software/bios/bios.bin", "rb")
|
||||||
|
image = open("build/fomu_pvt/image.bin", "wb")
|
||||||
|
# Copy bitstream at 0x00000000
|
||||||
|
for i in range(0x00000000, 0x0020000):
|
||||||
|
b = bitstream.read(1)
|
||||||
|
if not b:
|
||||||
|
image.write(0xff.to_bytes(1, "big"))
|
||||||
else:
|
else:
|
||||||
raise ValueError("unrecognized usb_core: {}".format(usb_core))
|
image.write(b)
|
||||||
if usb_bridge:
|
# Copy bios at 0x00020000
|
||||||
self.add_wb_master(self.usb.debug_bridge.wishbone)
|
for i in range(0x00000000, 0x00010000):
|
||||||
|
b = bios.read(1)
|
||||||
# Override default LiteX's yosys/build templates
|
if not b:
|
||||||
assert hasattr(platform.toolchain, "yosys_template")
|
image.write(0xff.to_bytes(1, "big"))
|
||||||
assert hasattr(platform.toolchain, "build_template")
|
else:
|
||||||
platform.toolchain.yosys_template = [
|
image.write(b)
|
||||||
"{read_files}",
|
bitstream.close()
|
||||||
"attrmap -tocase keep -imap keep=\"true\" keep=1 -imap keep=\"false\" keep=0 -remove keep=0",
|
bios.close()
|
||||||
"synth_ice40 -json {build_name}.json -top {build_name}",
|
image.close()
|
||||||
]
|
prog.load_bitstream("build/fomu_pvt/image.bin")
|
||||||
platform.toolchain.build_template = [
|
|
||||||
"yosys -q -l {build_name}.rpt {build_name}.ys",
|
|
||||||
"nextpnr-ice40 --json {build_name}.json --pcf {build_name}.pcf --asc {build_name}.txt \
|
|
||||||
--pre-pack {build_name}_pre_pack.py --{architecture} --package {package}",
|
|
||||||
"icepack {build_name}.txt {build_name}.bin"
|
|
||||||
]
|
|
||||||
|
|
||||||
# Add "-relut -dffe_min_ce_use 4" to the synth_ice40 command.
|
|
||||||
# The "-reult" adds an additional LUT pass to pack more stuff in,
|
|
||||||
# and the "-dffe_min_ce_use 4" flag prevents Yosys from generating a
|
|
||||||
# Clock Enable signal for a LUT that has fewer than 4 flip-flops.
|
|
||||||
# This increases density, and lets us use the FPGA more efficiently.
|
|
||||||
platform.toolchain.yosys_template[2] += " -relut -abc2 -dffe_min_ce_use 4 -relut"
|
|
||||||
if use_dsp:
|
|
||||||
platform.toolchain.yosys_template[2] += " -dsp"
|
|
||||||
|
|
||||||
# Disable final deep-sleep power down so firmware words are loaded
|
|
||||||
# onto softcore's address bus.
|
|
||||||
platform.toolchain.build_template[2] = "icepack -s {build_name}.txt {build_name}.bin"
|
|
||||||
|
|
||||||
# Allow us to set the nextpnr seed
|
|
||||||
platform.toolchain.build_template[1] += " --seed " + str(pnr_seed)
|
|
||||||
|
|
||||||
if pnr_placer is not None:
|
|
||||||
platform.toolchain.build_template[1] += " --placer {}".format(pnr_placer)
|
|
||||||
|
|
||||||
|
|
||||||
class USBSoC(BaseSoC):
|
|
||||||
"""A SoC for Fomu with interrupts for a softcore CPU"""
|
|
||||||
|
|
||||||
interrupt_map = {
|
|
||||||
"usb": 3,
|
|
||||||
}
|
|
||||||
interrupt_map.update(SoCCore.interrupt_map)
|
|
||||||
|
|
||||||
|
|
||||||
# Build --------------------------------------------------------------------------------------------
|
# Build --------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
def add_dfu_suffix(fn):
|
|
||||||
fn_base, _ext = os.path.splitext(fn)
|
|
||||||
fn_dfu = fn_base + '.dfu'
|
|
||||||
shutil.copyfile(fn, fn_dfu)
|
|
||||||
subprocess.check_call(['dfu-suffix', '--pid', '1209', '--vid', '5bf0', '--add', fn_dfu])
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
parser = argparse.ArgumentParser(description="LiteX SoC on Fomu")
|
parser = argparse.ArgumentParser(description="LiteX SoC on Fomu")
|
||||||
parser.add_argument("--build", action="store_true", help="Build bitstream")
|
parser.add_argument("--build", action="store_true", help="Build bitstream")
|
||||||
parser.add_argument("--board", choices=["evt", "pvt", "hacker"], required=True, help="Build for a particular hardware board")
|
parser.add_argument("--bios-flash-offset", default=0x60000, help="BIOS offset in SPI Flash")
|
||||||
parser.add_argument("--seed", default=0, help="Seed to use in Nextpnr")
|
parser.add_argument("--flash", action="store_true", help="Flash Bitstream")
|
||||||
parser.add_argument("--placer", default="heap", choices=["sa", "heap"], help="Which placer to use in Nextpnr")
|
|
||||||
builder_args(parser)
|
builder_args(parser)
|
||||||
soc_core_args(parser)
|
soc_core_args(parser)
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
soc = BaseSoC(board=args.board, pnr_placer=args.placer, pnr_seed=args.seed, debug=True, **soc_core_argdict(args))
|
soc = BaseSoC(args.bios_flash_offset, **soc_core_argdict(args))
|
||||||
builder = Builder(soc, **builder_argdict(args))
|
builder = Builder(soc, **builder_argdict(args))
|
||||||
builder.build(run=args.build)
|
builder.build(run=args.build)
|
||||||
|
|
||||||
|
if args.flash:
|
||||||
|
flash(args.bios_flash_offset)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|
Loading…
Reference in New Issue