mirror of
https://github.com/enjoy-digital/litex.git
synced 2025-01-04 09:52:26 -05:00
build/lattice: add initial Radiant support for NX FPGA family (Crosslink-NX/Certus-NX).
This commit is contained in:
parent
8a44464a45
commit
e441bd60fa
3 changed files with 367 additions and 2 deletions
|
@ -4,6 +4,7 @@
|
||||||
# Copyright (c) 2015-2020 Florent Kermarrec <florent@enjoy-digital.fr>
|
# Copyright (c) 2015-2020 Florent Kermarrec <florent@enjoy-digital.fr>
|
||||||
# Copyright (c) 2017 William D. Jones <thor0505@comcast.net>
|
# Copyright (c) 2017 William D. Jones <thor0505@comcast.net>
|
||||||
# Copyright (c) 2019 David Shah <dave@ds0.me>
|
# Copyright (c) 2019 David Shah <dave@ds0.me>
|
||||||
|
# Copyright (c) 2020 Piense <piense@gmail.com>
|
||||||
# SPDX-License-Identifier: BSD-2-Clause
|
# SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
|
||||||
from migen.fhdl.module import Module
|
from migen.fhdl.module import Module
|
||||||
|
@ -144,7 +145,111 @@ lattice_ecp5_trellis_special_overrides = {
|
||||||
DDROutput: LatticeECP5DDROutput
|
DDROutput: LatticeECP5DDROutput
|
||||||
}
|
}
|
||||||
|
|
||||||
# iCE40 AsyncResetSynchronizer ----------------------------------------------------------------------
|
|
||||||
|
# NX AsyncResetSynchronizer ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
class LatticeNXsyncResetSynchronizerImpl(Module):
|
||||||
|
def __init__(self, cd, async_reset):
|
||||||
|
rst1 = Signal()
|
||||||
|
self.specials += [
|
||||||
|
Instance("FD1P3BX",
|
||||||
|
i_D = 0,
|
||||||
|
i_PD = async_reset,
|
||||||
|
i_CK = cd.clk,
|
||||||
|
i_SP = 1,
|
||||||
|
o_Q = rst1),
|
||||||
|
Instance("FD1P3BX",
|
||||||
|
i_D = rst1,
|
||||||
|
i_PD = async_reset,
|
||||||
|
i_CK = cd.clk,
|
||||||
|
i_SP = 1,
|
||||||
|
o_Q = cd.rst)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class LatticeNXAsyncResetSynchronizer:
|
||||||
|
@staticmethod
|
||||||
|
def lower(dr):
|
||||||
|
return LatticeNXsyncResetSynchronizerImpl(dr.cd, dr.async_reset)
|
||||||
|
|
||||||
|
|
||||||
|
# NX SDR Input -------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
class LatticeNXSDRInputImpl(Module):
|
||||||
|
def __init__(self, i, o, clk):
|
||||||
|
self.specials += Instance("IFD1P3BX",
|
||||||
|
i_SCLK = clk,
|
||||||
|
i_PD = 0,
|
||||||
|
i_SP = 1,
|
||||||
|
i_D = i,
|
||||||
|
o_Q = o,
|
||||||
|
)
|
||||||
|
|
||||||
|
class LatticeNXSDRInput:
|
||||||
|
@staticmethod
|
||||||
|
def lower(dr):
|
||||||
|
return LatticeNXSDRInputImpl(dr.i, dr.o, dr.clk)
|
||||||
|
|
||||||
|
# NX SDR Output ------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
class LatticeNXSDROutputImpl(Module):
|
||||||
|
def __init__(self, i, o, clk):
|
||||||
|
self.specials += Instance("OFD1P3BX",
|
||||||
|
i_SCLK = clk,
|
||||||
|
i_PD = 0,
|
||||||
|
i_SP = 1,
|
||||||
|
i_D = i,
|
||||||
|
o_Q = o,
|
||||||
|
)
|
||||||
|
|
||||||
|
class LatticeNXSDROutput:
|
||||||
|
@staticmethod
|
||||||
|
def lower(dr):
|
||||||
|
return LatticeNXSDROutputImpl(dr.i, dr.o, dr.clk)
|
||||||
|
|
||||||
|
# NX DDR Input -------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
class LatticeNXDDRInputImpl(Module):
|
||||||
|
def __init__(self, i, o1, o2, clk):
|
||||||
|
self.specials += Instance("IDDRX1",
|
||||||
|
i_SCLK = clk,
|
||||||
|
i_D = i,
|
||||||
|
o_Q0 = o1,
|
||||||
|
o_Q1 = o2,
|
||||||
|
)
|
||||||
|
|
||||||
|
class LatticeNXDDRInput:
|
||||||
|
@staticmethod
|
||||||
|
def lower(dr):
|
||||||
|
return LatticeNXDDRInputImpl(dr.i, dr.o1, dr.o2, dr.clk)
|
||||||
|
|
||||||
|
# NX DDR Output ------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
class LatticeNXDDROutputImpl(Module):
|
||||||
|
def __init__(self, i1, i2, o, clk):
|
||||||
|
self.specials += Instance("ODDRX1",
|
||||||
|
i_SCLK = clk,
|
||||||
|
i_D0 = i1,
|
||||||
|
i_D1 = i2,
|
||||||
|
o_Q = o,
|
||||||
|
)
|
||||||
|
|
||||||
|
class LatticeNXDDROutput:
|
||||||
|
@staticmethod
|
||||||
|
def lower(dr):
|
||||||
|
return LatticeNXDDROutputImpl(dr.i1, dr.i2, dr.o, dr.clk)
|
||||||
|
|
||||||
|
# NX Special Overrides -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
lattice_NX_special_overrides = {
|
||||||
|
AsyncResetSynchronizer: LatticeNXAsyncResetSynchronizer,
|
||||||
|
SDRInput: LatticeNXSDRInput,
|
||||||
|
SDROutput: LatticeNXSDROutput,
|
||||||
|
DDRInput: LatticeNXDDRInput,
|
||||||
|
DDROutput: LatticeNXDDROutput,
|
||||||
|
}
|
||||||
|
|
||||||
|
# iCE40 AsyncResetSynchronizer ---------------------------------------------------------------------
|
||||||
|
|
||||||
class LatticeiCE40AsyncResetSynchronizerImpl(Module):
|
class LatticeiCE40AsyncResetSynchronizerImpl(Module):
|
||||||
def __init__(self, cd, async_reset):
|
def __init__(self, cd, async_reset):
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
# SPDX-License-Identifier: BSD-2-Clause
|
# SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
|
||||||
from litex.build.generic_platform import GenericPlatform
|
from litex.build.generic_platform import GenericPlatform
|
||||||
from litex.build.lattice import common, diamond, icestorm, trellis
|
from litex.build.lattice import common, diamond, icestorm, trellis, radiant
|
||||||
|
|
||||||
# LatticePlatform ----------------------------------------------------------------------------------
|
# LatticePlatform ----------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@ -22,6 +22,8 @@ class LatticePlatform(GenericPlatform):
|
||||||
elif toolchain == "icestorm":
|
elif toolchain == "icestorm":
|
||||||
self.bitstream_ext = ".bin"
|
self.bitstream_ext = ".bin"
|
||||||
self.toolchain = icestorm.LatticeIceStormToolchain()
|
self.toolchain = icestorm.LatticeIceStormToolchain()
|
||||||
|
elif toolchain == "radiant":
|
||||||
|
self.toolchain = radiant.LatticeRadiantToolchain()
|
||||||
else:
|
else:
|
||||||
raise ValueError("Unknown toolchain")
|
raise ValueError("Unknown toolchain")
|
||||||
|
|
||||||
|
|
258
litex/build/lattice/radiant.py
Normal file
258
litex/build/lattice/radiant.py
Normal file
|
@ -0,0 +1,258 @@
|
||||||
|
#
|
||||||
|
# This file is part of LiteX.
|
||||||
|
#
|
||||||
|
# Copyright (c) 2020 Piense <piense@gmail.com>
|
||||||
|
# Copyright (c) 2015-2019 Florent Kermarrec <florent@enjoy-digital.fr>
|
||||||
|
# Copyright (c) 2017-2018 Sergiusz Bazanski <q3k@q3k.org>
|
||||||
|
# Copyright (c) 2017 William D. Jones <thor0505@comcast.net>
|
||||||
|
# SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
import math
|
||||||
|
import subprocess
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
from migen.fhdl.structure import _Fragment
|
||||||
|
|
||||||
|
from litex.gen.fhdl.verilog import DummyAttrTranslate
|
||||||
|
|
||||||
|
from litex.build.generic_platform import *
|
||||||
|
from litex.build import tools
|
||||||
|
from litex.build.lattice import common
|
||||||
|
|
||||||
|
|
||||||
|
# Constraints (.ldc) -------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def _format_constraint(c):
|
||||||
|
if isinstance(c, Pins):
|
||||||
|
return ("ldc_set_location -site {" + c.identifiers[0] + "} [get_ports ","]")
|
||||||
|
elif isinstance(c, IOStandard):
|
||||||
|
return ("ldc_set_port -iobuf {IO_TYPE="+c.name+"} [get_ports ", "]")
|
||||||
|
elif isinstance(c, Misc):
|
||||||
|
return ("ldc_set_port -iobuf {"+c.misc+"} [get_ports ", "]" )
|
||||||
|
|
||||||
|
|
||||||
|
def _format_ldc(signame, pin, others, resname):
|
||||||
|
fmt_c = [_format_constraint(c) for c in ([Pins(pin)] + others)]
|
||||||
|
ldc = []
|
||||||
|
for pre, suf in fmt_c:
|
||||||
|
ldc.append(pre + signame + suf)
|
||||||
|
return "\n".join(ldc)
|
||||||
|
|
||||||
|
|
||||||
|
def _build_pdc(named_sc, named_pc, clocks, vns, build_name):
|
||||||
|
pdc = []
|
||||||
|
|
||||||
|
for sig, pins, others, resname in named_sc:
|
||||||
|
if len(pins) > 1:
|
||||||
|
for i, p in enumerate(pins):
|
||||||
|
pdc.append(_format_ldc(sig + "[" + str(i) + "]", p, others, resname))
|
||||||
|
else:
|
||||||
|
pdc.append(_format_ldc(sig, pins[0], others, resname))
|
||||||
|
if named_pc:
|
||||||
|
pdc.append("\n".join(named_pc))
|
||||||
|
|
||||||
|
# Note: .pdc is only used post-synthesis, Synplify constraints clocks by default to 200MHz.
|
||||||
|
for clk, period in clocks.items():
|
||||||
|
clk_name = vns.get_name(clk)
|
||||||
|
pdc.append("create_clock -period {} -name {} [{} {}];".format(
|
||||||
|
str(period),
|
||||||
|
clk_name,
|
||||||
|
"get_ports" if clk_name in [name for name, _, _, _ in named_sc] else "get_nets",
|
||||||
|
clk_name
|
||||||
|
))
|
||||||
|
|
||||||
|
tools.write_to_file(build_name + ".pdc", "\n".join(pdc))
|
||||||
|
|
||||||
|
# Project (.tcl) -----------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def _build_tcl(device, sources, vincpaths, build_name, pdc_file):
|
||||||
|
tcl = []
|
||||||
|
# Create project
|
||||||
|
tcl.append(" ".join([
|
||||||
|
"prj_create",
|
||||||
|
"-name \"{}\"".format(build_name),
|
||||||
|
"-impl \"impl\"",
|
||||||
|
"-dev {}".format(device),
|
||||||
|
"-synthesis \"synplify\""
|
||||||
|
]))
|
||||||
|
|
||||||
|
def tcl_path(path): return path.replace("\\", "/")
|
||||||
|
|
||||||
|
# Add include paths
|
||||||
|
vincpath = ";".join(map(lambda x: tcl_path(x), vincpaths))
|
||||||
|
tcl.append("prj_set_impl_opt {include path} {\"" + vincpath + "\"}")
|
||||||
|
|
||||||
|
# Add sources
|
||||||
|
for filename, language, library in sources:
|
||||||
|
tcl.append("prj_add_source \"{}\" -work {}".format(tcl_path(filename), library))
|
||||||
|
|
||||||
|
tcl.append("prj_add_source \"{}\" -work {}".format(tcl_path(pdc_file), library))
|
||||||
|
|
||||||
|
# Set top level
|
||||||
|
tcl.append("prj_set_impl_opt top \"{}\"".format(build_name))
|
||||||
|
|
||||||
|
# Save project
|
||||||
|
tcl.append("prj_save")
|
||||||
|
|
||||||
|
# Build flow
|
||||||
|
tcl.append("prj_run Synthesis -impl impl -forceOne")
|
||||||
|
tcl.append("prj_run Map -impl impl")
|
||||||
|
tcl.append("prj_run PAR -impl impl")
|
||||||
|
tcl.append("prj_run Export -impl impl -task Bitgen")
|
||||||
|
|
||||||
|
# Close project
|
||||||
|
tcl.append("prj_close")
|
||||||
|
|
||||||
|
tools.write_to_file(build_name + ".tcl", "\n".join(tcl))
|
||||||
|
|
||||||
|
# Script -------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def _build_script(build_name, device):
|
||||||
|
if sys.platform in ("win32", "cygwin"):
|
||||||
|
tool = "pnmainc"
|
||||||
|
script_ext = ".bat"
|
||||||
|
script_contents = "@echo off\nrem Autogenerated by LiteX / git: " + tools.get_litex_git_revision() + "\n\n"
|
||||||
|
copy_stmt = "copy"
|
||||||
|
fail_stmt = " || exit /b"
|
||||||
|
else:
|
||||||
|
tool = "radiantc"
|
||||||
|
script_ext = ".sh"
|
||||||
|
script_contents = "# Autogenerated by LiteX / git: " + tools.get_litex_git_revision() + "\nset -e\n"
|
||||||
|
copy_stmt = "cp"
|
||||||
|
fail_stmt = ""
|
||||||
|
|
||||||
|
script_contents += "{tool} {tcl_script}{fail_stmt}\n".format(
|
||||||
|
tool = tool,
|
||||||
|
tcl_script = build_name + ".tcl",
|
||||||
|
fail_stmt = fail_stmt)
|
||||||
|
|
||||||
|
script_contents += "{copy_stmt} {radiant_product} {migen_product} {fail_stmt}\n".format(
|
||||||
|
copy_stmt = copy_stmt,
|
||||||
|
fail_stmt = fail_stmt,
|
||||||
|
radiant_product = os.path.join("impl", build_name + "_impl.bit"),
|
||||||
|
migen_product = build_name + ".bit")
|
||||||
|
|
||||||
|
build_script_file = "build_" + build_name + script_ext
|
||||||
|
tools.write_to_file(build_script_file, script_contents, force_unix=False)
|
||||||
|
return build_script_file
|
||||||
|
|
||||||
|
def _run_script(script):
|
||||||
|
if sys.platform in ("win32", "cygwin"):
|
||||||
|
shell = ["cmd", "/c"]
|
||||||
|
else:
|
||||||
|
shell = ["bash"]
|
||||||
|
|
||||||
|
if subprocess.call(shell + [script]) != 0:
|
||||||
|
raise OSError("Subprocess failed")
|
||||||
|
|
||||||
|
def _check_timing(build_name):
|
||||||
|
lines = open("impl/{}_impl.par".format(build_name), "r").readlines()
|
||||||
|
runs = [None, None]
|
||||||
|
for i in range(len(lines)-1):
|
||||||
|
if lines[i].startswith("Level/") and lines[i+1].startswith("Cost "):
|
||||||
|
runs[0] = i + 2
|
||||||
|
if lines[i].startswith("* : Design saved.") and runs[0] is not None:
|
||||||
|
runs[1] = i
|
||||||
|
break
|
||||||
|
assert all(map(lambda x: x is not None, runs))
|
||||||
|
|
||||||
|
p = re.compile(r"(^\s*\S+\s+\*?\s+[0-9]+\s+)(\S+)(\s+\S+\s+)(\S+)(\s+.*)")
|
||||||
|
for l in lines[runs[0]:runs[1]]:
|
||||||
|
m = p.match(l)
|
||||||
|
if m is None: continue
|
||||||
|
limit = 1e-8
|
||||||
|
setup = m.group(2)
|
||||||
|
hold = m.group(4)
|
||||||
|
# If there were no freq constraints in ldc, ratings will be dashed.
|
||||||
|
# results will likely be terribly unreliable, so bail
|
||||||
|
assert not setup == hold == "-", "No timing constraints were provided"
|
||||||
|
setup, hold = map(float, (setup, hold))
|
||||||
|
if setup > limit and hold > limit:
|
||||||
|
# At least one run met timing
|
||||||
|
# XXX is this necessarily the run from which outputs will be used?
|
||||||
|
return
|
||||||
|
raise Exception("Failed to meet timing")
|
||||||
|
|
||||||
|
# LatticeRadiantToolchain --------------------------------------------------------------------------
|
||||||
|
|
||||||
|
class LatticeRadiantToolchain:
|
||||||
|
attr_translate = {
|
||||||
|
# FIXME: document
|
||||||
|
"keep": ("syn_keep", "true"),
|
||||||
|
"no_retiming": ("syn_no_retiming", "true"),
|
||||||
|
"async_reg": None,
|
||||||
|
"mr_ff": None,
|
||||||
|
"mr_false_path": None,
|
||||||
|
"ars_ff1": None,
|
||||||
|
"ars_ff2": None,
|
||||||
|
"ars_false_path": None,
|
||||||
|
"no_shreg_extract": None
|
||||||
|
}
|
||||||
|
|
||||||
|
special_overrides = common.lattice_NX_special_overrides
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.clocks = {}
|
||||||
|
self.false_paths = set() # FIXME: use it
|
||||||
|
|
||||||
|
def build(self, platform, fragment,
|
||||||
|
build_dir = "build",
|
||||||
|
build_name = "top",
|
||||||
|
run = True,
|
||||||
|
timingstrict = True,
|
||||||
|
**kwargs):
|
||||||
|
|
||||||
|
# Create build directory
|
||||||
|
os.makedirs(build_dir, exist_ok=True)
|
||||||
|
cwd = os.getcwd()
|
||||||
|
os.chdir(build_dir)
|
||||||
|
|
||||||
|
# Finalize design
|
||||||
|
if not isinstance(fragment, _Fragment):
|
||||||
|
fragment = fragment.get_fragment()
|
||||||
|
platform.finalize(fragment)
|
||||||
|
|
||||||
|
# Generate verilog
|
||||||
|
v_output = platform.get_verilog(fragment, name=build_name, **kwargs)
|
||||||
|
named_sc, named_pc = platform.resolve_signals(v_output.ns)
|
||||||
|
v_file = build_name + ".v"
|
||||||
|
v_output.write(v_file)
|
||||||
|
platform.add_source(v_file)
|
||||||
|
|
||||||
|
# Generate design constraints file (.pdc)
|
||||||
|
_build_pdc(named_sc, named_pc, self.clocks, v_output.ns, build_name)
|
||||||
|
pdc_file = build_dir + "\\" + build_name + ".pdc"
|
||||||
|
|
||||||
|
# Generate design script file (.tcl)
|
||||||
|
_build_tcl(platform.device, platform.sources, platform.verilog_include_paths, build_name, pdc_file)
|
||||||
|
|
||||||
|
# Generate build script
|
||||||
|
script = _build_script(build_name, platform.device)
|
||||||
|
|
||||||
|
# Run
|
||||||
|
if run:
|
||||||
|
_run_script(script)
|
||||||
|
if timingstrict:
|
||||||
|
_check_timing(build_name)
|
||||||
|
|
||||||
|
os.chdir(cwd)
|
||||||
|
|
||||||
|
return v_output.ns
|
||||||
|
|
||||||
|
def add_period_constraint(self, platform, clk, period):
|
||||||
|
clk.attr.add("keep")
|
||||||
|
period = math.floor(period*1e3)/1e3 # round to lowest picosecond
|
||||||
|
if clk in self.clocks:
|
||||||
|
if period != self.clocks[clk]:
|
||||||
|
raise ValueError("Clock already constrained to {:.2f}ns, new constraint to {:.2f}ns"
|
||||||
|
.format(self.clocks[clk], period))
|
||||||
|
self.clocks[clk] = period
|
||||||
|
|
||||||
|
def add_false_path_constraint(self, platform, from_, to):
|
||||||
|
from_.attr.add("keep")
|
||||||
|
to.attr.add("keep")
|
||||||
|
if (to, from_) not in self.false_paths:
|
||||||
|
self.false_paths.add((from_, to))
|
Loading…
Reference in a new issue