Import Icestorm backend improvements from Migen.

This commit is contained in:
William D. Jones 2017-12-18 20:36:21 -05:00 committed by Florent Kermarrec
parent e7015e4191
commit f096030fc8
5 changed files with 168 additions and 67 deletions

View File

@ -50,8 +50,8 @@ class Platform(LatticePlatform):
default_clk_period = 83.333 default_clk_period = 83.333
def __init__(self): def __init__(self):
LatticePlatform.__init__(self, "ice40-1k-tq144", _io, _connectors, LatticePlatform.__init__(self, "ice40-hx1k-tq144", _io, _connectors,
toolchain="icestorm") toolchain="icestorm")
def create_programmer(self): def create_programmer(self):
return IceStormProgrammer() return IceStormProgrammer()

View File

@ -4,38 +4,80 @@ from litex.gen.genlib.io import *
from litex.gen.genlib.resetsync import AsyncResetSynchronizer from litex.gen.genlib.resetsync import AsyncResetSynchronizer
class LatticeAsyncResetSynchronizerImpl(Module): class DiamondAsyncResetSynchronizerImpl(Module):
def __init__(self, cd, async_reset): def __init__(self, cd, async_reset):
rst1 = Signal() rst1 = Signal()
self.specials += [ self.specials += [
Instance("FD1S3BX", i_D=0, i_PD=async_reset, Instance("FD1S3BX", i_D=0, i_PD=async_reset,
i_CK=cd.clk, o_Q=rst1), i_CK=cd.clk, o_Q=rst1),
Instance("FD1S3BX", i_D=rst1, i_PD=async_reset, Instance("FD1S3BX", i_D=rst1, i_PD=async_reset,
i_CK=cd.clk, o_Q=cd.rst) i_CK=cd.clk, o_Q=cd.rst)
] ]
class LatticeAsyncResetSynchronizer: class DiamondAsyncResetSynchronizer:
@staticmethod @staticmethod
def lower(dr): def lower(dr):
return LatticeAsyncResetSynchronizerImpl(dr.cd, dr.async_reset) return DiamondAsyncResetSynchronizerImpl(dr.cd, dr.async_reset)
class LatticeDDROutputImpl(Module): class DiamondDDROutputImpl(Module):
def __init__(self, i1, i2, o, clk): def __init__(self, i1, i2, o, clk):
self.specials += Instance("ODDRXD1", self.specials += Instance("ODDRXD1",
synthesis_directive="ODDRAPPS=\"SCLK_ALIGNED\"", synthesis_directive="ODDRAPPS=\"SCLK_ALIGNED\"",
i_SCLK=clk, i_SCLK=clk,
i_DA=i1, i_DB=i2, o_Q=o, i_DA=i1, i_DB=i2, o_Q=o)
)
class LatticeDDROutput: class DiamondDDROutput:
@staticmethod @staticmethod
def lower(dr): def lower(dr):
return LatticeDDROutputImpl(dr.i1, dr.i2, dr.o, dr.clk) return DiamondDDROutputImpl(dr.i1, dr.i2, dr.o, dr.clk)
lattice_special_overrides = { diamond_special_overrides = {
AsyncResetSynchronizer: LatticeAsyncResetSynchronizer, AsyncResetSynchronizer: DiamondAsyncResetSynchronizer,
DDROutput: LatticeDDROutput DDROutput: DiamondDDROutput
}
class IcestormAsyncResetSynchronizerImpl(Module):
def __init__(self, cd, async_reset):
rst1 = Signal()
self.specials += [
Instance("SB_DFFS", i_D=0, i_S=async_reset,
i_C=cd.clk, o_Q=rst1),
Instance("SB_DFFS", i_D=rst1, i_S=async_reset,
i_C=cd.clk, o_Q=cd.rst)
]
class IcestormAsyncResetSynchronizer:
@staticmethod
def lower(dr):
return IcestormAsyncResetSynchronizerImpl(dr.cd, dr.async_reset)
class IcestormDifferentialOutputImpl(Module):
def __init__(self, i, o_p, o_n):
self.specials += Instance("SB_IO",
p_PIN_TYPE=C(0b011000, 6),
p_IO_STANDARD="SB_LVCMOS",
io_PACKAGE_PIN=o_p,
i_D_OUT_0=i)
self.specials += Instance("SB_IO",
p_PIN_TYPE=C(0b011000, 6),
p_IO_STANDARD="SB_LVCMOS",
io_PACKAGE_PIN=o_n,
i_D_OUT_0=~i)
class IcestormDifferentialOutput:
@staticmethod
def lower(dr):
return IcestormDifferentialOutputImpl(dr.i, dr.o_p, dr.o_n)
icestorm_special_overrides = {
AsyncResetSynchronizer: IcestormAsyncResetSynchronizer,
DifferentialOutput: IcestormDifferentialOutput
} }

View File

@ -7,6 +7,7 @@ import subprocess
import shutil import shutil
from litex.gen.fhdl.structure import _Fragment from litex.gen.fhdl.structure import _Fragment
from litex.gen.fhdl.verilog import DummyAttrTranslate
from litex.build.generic_platform import * from litex.build.generic_platform import *
from litex.build import tools from litex.build import tools
@ -89,6 +90,10 @@ def _run_diamond(build_name, toolchain_path, ver=None):
class LatticeDiamondToolchain: class LatticeDiamondToolchain:
attr_translate = DummyAttrTranslate()
special_overrides = common.diamond_special_overrides
def build(self, platform, fragment, build_dir="build", build_name="top", def build(self, platform, fragment, build_dir="build", build_name="top",
toolchain_path="/opt/Diamond", run=True, **kwargs): toolchain_path="/opt/Diamond", run=True, **kwargs):
os.makedirs(build_dir, exist_ok=True) os.makedirs(build_dir, exist_ok=True)

View File

@ -9,6 +9,7 @@ from litex.gen.fhdl.structure import _Fragment
from litex.build.generic_platform import * from litex.build.generic_platform import *
from litex.build import tools from litex.build import tools
from litex.build.lattice import common
def _format_constraint(c): def _format_constraint(c):
@ -32,23 +33,8 @@ def _build_pcf(named_sc, named_pc):
return r return r
def _build_yosys(device, sources, vincpaths, build_name): def _run_icestorm(source, build_template, build_name, pnr_pkg_opts,
ys_contents = "" icetime_pkg_opts, icetime_constraint):
incflags = ""
for path in vincpaths:
incflags += " -I" + path
for filename, language, library in sources:
ys_contents += "read_{}{} {}\n".format(language, incflags, filename)
ys_contents += """synth_ice40 -top top -blif {build_name}.blif""".format(
build_name=build_name)
ys_name = build_name + ".ys"
tools.write_to_file(ys_name, ys_contents)
def _run_icestorm(build_name, source, yosys_opt, pnr_opt,
icetime_opt, icepack_opt):
if sys.platform == "win32" or sys.platform == "cygwin": if sys.platform == "win32" or sys.platform == "cygwin":
source_cmd = "call " source_cmd = "call "
script_ext = ".bat" script_ext = ".bat"
@ -62,16 +48,14 @@ def _run_icestorm(build_name, source, yosys_opt, pnr_opt,
build_script_contents = "# Autogenerated by LiteX\nset -e\n" build_script_contents = "# Autogenerated by LiteX\nset -e\n"
fail_stmt = "" fail_stmt = ""
build_script_contents += """ for s in build_template:
yosys {yosys_opt} -l {build_name}.rpt {build_name}.ys{fail_stmt} s_fail = s + "{fail_stmt}\n" # Required so Windows scripts fail early.
arachne-pnr {pnr_opt} -p {build_name}.pcf {build_name}.blif -o {build_name}.txt{fail_stmt} build_script_contents += s_fail.format(build_name=build_name,
icetime {icetime_opt} -t -p {build_name}.pcf -r {build_name}.tim {build_name}.txt{fail_stmt} pnr_pkg_opts=pnr_pkg_opts,
icepack {icepack_opt} {build_name}.txt {build_name}.bin{fail_stmt} icetime_pkg_opts=icetime_pkg_opts,
""" icetime_constraint=icetime_constraint,
build_script_contents = build_script_contents.format( fail_stmt=fail_stmt)
build_name=build_name,
yosys_opt=yosys_opt, pnr_opt=pnr_opt, icepack_opt=icepack_opt,
icetime_opt=icetime_opt, fail_stmt=fail_stmt)
build_script_file = "build_" + build_name + script_ext build_script_file = "build_" + build_name + script_ext
tools.write_to_file(build_script_file, build_script_contents, tools.write_to_file(build_script_file, build_script_contents,
force_unix=False) force_unix=False)
@ -82,14 +66,47 @@ icepack {icepack_opt} {build_name}.txt {build_name}.bin{fail_stmt}
class LatticeIceStormToolchain: class LatticeIceStormToolchain:
attr_translate = {
"keep": ("keep", "true"),
"no_retiming": None, # yosys can do retiming via the (non-default)
# "-retime" option to "synth_ice40", but
# yosys does not check for an equivalent
# constraint to prevent retiming on signals.
"async_reg": None, # yosys has no equivalent, and arachne-pnr
# wouldn't take advantage of it anyway.
# While custom attributes are supported in yosys, neither
# arachne-pnr nor icetime currently can take advantage of them
# to add fine-grained timing constraints.
"mr_ff": None, # user-defined attribute
"mr_false_path": None, # user-defined attribute
"ars_ff1": None, # user-defined attribute
"ars_ff2": None, # user-defined attribute
"ars_false_path": None, # user-defined attribute
# ice40 does not have a shift register primitive.
"no_shreg_extract": None
}
special_overrides = common.icestorm_special_overrides
def __init__(self): def __init__(self):
self.yosys_opt = "-q" self.yosys_template = [
self.pnr_opt = "-q" "{read_files}",
self.icetime_opt = "" "attrmap -tocase keep -imap keep=\"true\" keep=1 -imap keep=\"false\" keep=0 -remove keep=0",
self.icepack_opt = "" "synth_ice40 -top top -blif {build_name}.blif",
]
self.build_template = [
"yosys -q -l {build_name}.rpt {build_name}.ys",
"arachne-pnr -q {pnr_pkg_opts} -p {build_name}.pcf {build_name}.blif -o {build_name}.txt",
"icetime {icetime_pkg_opts} -c {icetime_constraint} -t -p {build_name}.pcf -r {build_name}.tim {build_name}.txt",
"icepack {build_name}.txt {build_name}.bin"
]
self.freq_constraints = dict() self.freq_constraints = dict()
# platform.device should be of the form "ice40-{1k,8k}-{tq144, etc}"" # platform.device should be of the form "ice40-{lp384, hx1k, etc}-{tq144, etc}"
def build(self, platform, fragment, build_dir="build", build_name="top", def build(self, platform, fragment, build_dir="build", build_name="top",
run=True): run=True):
os.makedirs(build_dir, exist_ok=True) os.makedirs(build_dir, exist_ok=True)
@ -104,35 +121,69 @@ class LatticeIceStormToolchain:
named_sc, named_pc = platform.resolve_signals(v_output.ns) named_sc, named_pc = platform.resolve_signals(v_output.ns)
v_file = build_name + ".v" v_file = build_name + ".v"
v_output.write(v_file) v_output.write(v_file)
sources = platform.sources | {(v_file, "verilog", "work")}
_build_yosys(platform.device, sources, platform.verilog_include_paths, ys_contents = "\n".join(_.format(build_name=build_name,
build_name) read_files=self.gen_read_files(platform, v_file))
for _ in self.yosys_template)
ys_name = build_name + ".ys"
tools.write_to_file(ys_name, ys_contents)
tools.write_to_file(build_name + ".pcf", tools.write_to_file(build_name + ".pcf",
_build_pcf(named_sc, named_pc)) _build_pcf(named_sc, named_pc))
if run: if run:
(family, size, package) = self.parse_device_string(platform.device) (family, series_size, package) = self.parse_device_string(platform.device)
pnr_opt = self.pnr_opt + " -d " + size + " -P " + package pnr_pkg_opts = "-d " + self.get_size_string(series_size) + \
# TODO: PNR will probably eventually support LP devices. " -P " + package
icetime_opt = self.icetime_opt + " -P " + package + \ icetime_pkg_opts = "-P " + package + " -d " + series_size
" -d " + "hx" + size + " -c " + \ icetime_constraint = str(max(self.freq_constraints.values(),
str(max(self.freq_constraints.values(), default=0.0)) default=0.0))
_run_icestorm(build_name, False, self.yosys_opt, pnr_opt,
icetime_opt, self.icepack_opt) _run_icestorm(False, self.build_template, build_name, pnr_pkg_opts,
icetime_pkg_opts, icetime_constraint)
os.chdir(cwd) os.chdir(cwd)
return v_output.ns return v_output.ns
def parse_device_string(self, device_str): def parse_device_string(self, device_str):
(family, size, package) = device_str.split("-") # Arachne only understands packages based on the device size, but
# LP for a given size supports packages that HX for the same size
# doesn't and vice versa; we need to know the device series due to
# icetime.
valid_packages = {
"lp384": ["qn32", "cm36", "cm49"],
"lp1k": ["swg16tr", "cm36", "cm49", "cm81", "cb81", "qn84",
"cm121", "cb121"],
"hx1k": ["vq100", "cb132", "tq144"],
"lp8k": ["cm81", "cm81:4k", "cm121", "cm121:4k", "cm225",
"cm225:4k"],
"hx8k": ["cb132", "cb132:4k", "tq144:4k", "cm225", "ct256"],
}
(family, series_size, package) = device_str.split("-")
if family not in ["ice40"]: if family not in ["ice40"]:
raise ValueError("Unknown device family") raise ValueError("Unknown device family")
if size not in ["1k", "8k"]: if series_size not in ["lp384", "lp1k", "hx1k", "lp8k", "hx8k"]:
raise ValueError("Invalid device size") raise ValueError("Invalid device series/size")
if package not in ["tq144", "ct256", "vq100"]: if package not in valid_packages[series_size]:
raise ValueError("Invalid device package") raise ValueError("Invalid device package")
return (family, size, package) return (family, series_size, package)
def get_size_string(self, series_size_str):
return series_size_str[2:]
def gen_read_files(self, platform, main):
sources = platform.sources | {(main, "verilog", "work")}
incflags = ""
read_files = list()
for path in platform.verilog_include_paths:
incflags += " -I" + path
for filename, language, library in sources:
read_files.append("read_{}{} {}".format(language,
incflags,
filename))
return "\n".join(read_files)
# icetime can only handle a single global constraint. Pending more # icetime can only handle a single global constraint. Pending more
# finely-tuned analysis features in arachne-pnr and IceStorm, save # finely-tuned analysis features in arachne-pnr and IceStorm, save

View File

@ -16,9 +16,12 @@ class LatticePlatform(GenericPlatform):
raise ValueError("Unknown toolchain") raise ValueError("Unknown toolchain")
def get_verilog(self, *args, special_overrides=dict(), **kwargs): def get_verilog(self, *args, special_overrides=dict(), **kwargs):
so = dict(common.lattice_special_overrides) so = dict() # No common overrides between ECP and ice40.
so.update(self.toolchain.special_overrides)
so.update(special_overrides) so.update(special_overrides)
return GenericPlatform.get_verilog(self, *args, special_overrides=so, **kwargs) return GenericPlatform.get_verilog(self, *args, special_overrides=so,
attr_translate=self.toolchain.attr_translate,
**kwargs)
def build(self, *args, **kwargs): def build(self, *args, **kwargs):
return self.toolchain.build(self, *args, **kwargs) return self.toolchain.build(self, *args, **kwargs)