Skip to content

Commit

Permalink
Add Project Trellis Backend (m-labs#156)
Browse files Browse the repository at this point in the history
  • Loading branch information
cr1901 authored and sbourdeauducq committed Nov 8, 2018
1 parent 37deff1 commit 0c5d42c
Show file tree
Hide file tree
Showing 5 changed files with 249 additions and 25 deletions.
69 changes: 47 additions & 22 deletions migen/build/lattice/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from migen.genlib.resetsync import AsyncResetSynchronizer


class DiamondAsyncResetSynchronizerImpl(Module):
class LatticeECPXAsyncResetSynchronizerImpl(Module):
def __init__(self, cd, async_reset):
rst1 = Signal()
self.specials += [
Expand All @@ -16,32 +16,57 @@ def __init__(self, cd, async_reset):
]


class DiamondAsyncResetSynchronizer:
class LatticeECPXAsyncResetSynchronizer:
@staticmethod
def lower(dr):
return DiamondAsyncResetSynchronizerImpl(dr.cd, dr.async_reset)
return LatticeECPXAsyncResetSynchronizerImpl(dr.cd, dr.async_reset)


class DiamondDDROutputImpl(Module):
class LatticeECPXDDROutputImpl(Module):
def __init__(self, i1, i2, o, clk):
self.specials += Instance("ODDRXD1",
synthesis_directive="ODDRAPPS=\"SCLK_ALIGNED\"",
i_SCLK=clk,
i_DA=i1, i_DB=i2, o_Q=o)


class DiamondDDROutput:
class LatticeECPXDDROutput:
@staticmethod
def lower(dr):
return DiamondDDROutputImpl(dr.i1, dr.i2, dr.o, dr.clk)
return LatticeECPXDDROutputImpl(dr.i1, dr.i2, dr.o, dr.clk)

diamond_special_overrides = {
AsyncResetSynchronizer: DiamondAsyncResetSynchronizer,
DDROutput: DiamondDDROutput
lattice_ecpx_special_overrides = {
AsyncResetSynchronizer: LatticeECPXAsyncResetSynchronizer,
DDROutput: LatticeECPXDDROutput
}


class IcestormAsyncResetSynchronizerImpl(Module):
class LatticeECPXTrellisTristateImpl(Module):
def __init__(self, io, o, oe, i):
nbits, sign = value_bits_sign(io)
for bit in range(nbits):
self.specials += \
Instance("TRELLIS_IO",
p_DIR="BIDIR",
i_B=io[bit],
i_I=o[bit],
o_O=i[bit],
i_T=~oe,
)

class LatticeECPXTrellisTristate(Module):
@staticmethod
def lower(dr):
return LatticeECPXTrellisTristateImpl(dr.target, dr.o, dr.oe, dr.i)

lattice_ecpx_trellis_special_overrides = {
AsyncResetSynchronizer: LatticeECPXAsyncResetSynchronizer,
Tristate: LatticeECPXTrellisTristate,
DDROutput: LatticeECPXDDROutput
}


class LatticeiCE40AsyncResetSynchronizerImpl(Module):
def __init__(self, cd, async_reset):
rst1 = Signal()
self.specials += [
Expand All @@ -52,13 +77,13 @@ def __init__(self, cd, async_reset):
]


class IcestormAsyncResetSynchronizer:
class LatticeiCE40AsyncResetSynchronizer:
@staticmethod
def lower(dr):
return IcestormAsyncResetSynchronizerImpl(dr.cd, dr.async_reset)
return LatticeiCE40AsyncResetSynchronizerImpl(dr.cd, dr.async_reset)


class IcestormTristateImpl(Module):
class LatticeiCE40TristateImpl(Module):
def __init__(self, io, o, oe, i):
nbits, sign = value_bits_sign(io)
if nbits == 1:
Expand All @@ -82,13 +107,13 @@ def __init__(self, io, o, oe, i):
)


class IcestormTristate(Module):
class LatticeiCE40Tristate(Module):
@staticmethod
def lower(dr):
return IcestormTristateImpl(dr.target, dr.o, dr.oe, dr.i)
return LatticeiCE40TristateImpl(dr.target, dr.o, dr.oe, dr.i)


class IcestormDifferentialOutputImpl(Module):
class LatticeiCE40DifferentialOutputImpl(Module):
def __init__(self, i, o_p, o_n):
self.specials += Instance("SB_IO",
p_PIN_TYPE=C(0b011000, 6),
Expand All @@ -103,13 +128,13 @@ def __init__(self, i, o_p, o_n):
i_D_OUT_0=~i)


class IcestormDifferentialOutput:
class LatticeiCE40DifferentialOutput:
@staticmethod
def lower(dr):
return IcestormDifferentialOutputImpl(dr.i, dr.o_p, dr.o_n)
return LatticeiCE40DifferentialOutputImpl(dr.i, dr.o_p, dr.o_n)

icestorm_special_overrides = {
AsyncResetSynchronizer: IcestormAsyncResetSynchronizer,
Tristate: IcestormTristate,
DifferentialOutput: IcestormDifferentialOutput
lattice_ice40_special_overrides = {
AsyncResetSynchronizer: LatticeiCE40AsyncResetSynchronizer,
Tristate: LatticeiCE40Tristate,
DifferentialOutput: LatticeiCE40DifferentialOutput
}
2 changes: 1 addition & 1 deletion migen/build/lattice/diamond.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ def _run_script(script):
class LatticeDiamondToolchain:
attr_translate = DummyAttrTranslate()

special_overrides = common.diamond_special_overrides
special_overrides = common.lattice_ecpx_special_overrides

def build(self, platform, fragment, build_dir="build", build_name="top",
toolchain_path="/opt/Diamond", run=True):
Expand Down
2 changes: 1 addition & 1 deletion migen/build/lattice/icestorm.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ class LatticeIceStormToolchain:
"no_shreg_extract": None
}

special_overrides = common.icestorm_special_overrides
special_overrides = common.lattice_ice40_special_overrides

def __init__(self):
# Variables within replacement fields should be backend-aware and
Expand Down
4 changes: 3 additions & 1 deletion migen/build/lattice/platform.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from migen.build.generic_platform import GenericPlatform
from migen.build.lattice import common, diamond, icestorm
from migen.build.lattice import common, diamond, icestorm, trellis


class LatticePlatform(GenericPlatform):
Expand All @@ -9,6 +9,8 @@ def __init__(self, *args, toolchain="diamond", **kwargs):
GenericPlatform.__init__(self, *args, **kwargs)
if toolchain == "diamond":
self.toolchain = diamond.LatticeDiamondToolchain()
elif toolchain == "trellis":
self.toolchain = trellis.LatticeTrellisToolchain()
elif toolchain == "icestorm":
self.bitstream_ext = ".bin"
self.toolchain = icestorm.LatticeIceStormToolchain()
Expand Down
197 changes: 197 additions & 0 deletions migen/build/lattice/trellis.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
# This file is Copyright (c) 2018 Florent Kermarrec <florent@enjoy-digital.fr>
# This file is Copyright (c) 2018 William D. Jones <thor0505@comcast.net>
# License: BSD

import os
import subprocess
import sys

from migen.fhdl.structure import _Fragment

from migen.build.generic_platform import *
from migen.build import tools
from migen.build.lattice import common

# TODO:
# - check/document attr_translate.

nextpnr_ecp5_architectures = {
"lfe5u-25f": "25k",
"lfe5u-45f": "45k",
"lfe5u-85f": "85k",
"lfe5um-25f": "um-25k",
"lfe5um-45f": "um-45k",
"lfe5um-85f": "um-85k",
"lfe5um5g-25f": "um5g-25k",
"lfe5um5g-45f": "um5g-45k",
"lfe5um5g-85f": "um5g-85k",
}


def _format_constraint(c):
if isinstance(c, Pins):
return ("LOCATE COMP ", " SITE " + "\"" + c.identifiers[0] + "\"")
elif isinstance(c, IOStandard):
return ("IOBUF PORT ", " IO_TYPE=" + c.name)
elif isinstance(c, Misc):
return c.misc


def _format_lpf(signame, pin, others, resname):
fmt_c = [_format_constraint(c) for c in ([Pins(pin)] + others)]
r = ""
for pre, suf in fmt_c:
r += pre + "\"" + signame + "\"" + suf + ";\n"
return r


def _build_lpf(named_sc, named_pc):
r = "BLOCK RESETPATHS;\n"
r += "BLOCK ASYNCPATHS;\n"
for sig, pins, others, resname in named_sc:
if len(pins) > 1:
for i, p in enumerate(pins):
r += _format_lpf(sig + "[" + str(i) + "]", p, others, resname)
else:
r += _format_lpf(sig, pins[0], others, resname)
if named_pc:
r += "\n" + "\n\n".join(named_pc)
return r


def _build_script(source, build_template, build_name, architecture,
basecfg, freq_constraint):
if sys.platform in ("win32", "cygwin"):
script_ext = ".bat"
build_script_contents = "@echo off\nrem Autogenerated by Migen\n\n"
fail_stmt = " || exit /b"
else:
script_ext = ".sh"
build_script_contents = "# Autogenerated by Migen\nset -e\n\n"
fail_stmt = ""

for s in build_template:
s_fail = s + "{fail_stmt}\n" # Required so Windows scripts fail early.
build_script_contents += s_fail.format(build_name=build_name,
architecture=architecture,
basecfg=basecfg,
freq_constraint=freq_constraint,
fail_stmt=fail_stmt)

build_script_file = "build_" + build_name + script_ext
tools.write_to_file(build_script_file, build_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 yosys_import_sources(platform):
includes = ""
reads = []
for path in platform.verilog_include_paths:
includes += " -I" + path
for filename, language, library in platform.sources:
reads.append("read_{}{} {}".format(
language, includes, filename))
return "\n".join(reads)


class LatticeTrellisToolchain:
attr_translate = {
# FIXME: document
"keep": ("keep", "true"),
"no_retiming": None,
"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_ecpx_trellis_special_overrides

def __init__(self):
self.yosys_template = [
"{read_files}",
"attrmap -tocase keep -imap keep=\"true\" keep=1 -imap keep=\"false\" keep=0 -remove keep=0",
"synth_ecp5 -nomux -json {build_name}.json -top {build_name}",
]

self.build_template = [
"yosys -q -l {build_name}.rpt {build_name}.ys",
"nextpnr-ecp5 --json {build_name}.json --lpf {build_name}.lpf --textcfg {build_name}.config --basecfg {basecfg} --{architecture} --freq {freq_constraint}",
"ecppack {build_name}.config {build_name}.bit"
]

self.freq_constraints = dict()

def build(self, platform, fragment, build_dir="build", build_name="top",
toolchain_path=None, run=True):
if toolchain_path is None:
toolchain_path = "/usr/share/trellis/"
os.makedirs(build_dir, exist_ok=True)
cwd = os.getcwd()
os.chdir(build_dir)

# generate verilog
if not isinstance(fragment, _Fragment):
fragment = fragment.get_fragment()
platform.finalize(fragment)

top_output = platform.get_verilog(fragment, name=build_name)
named_sc, named_pc = platform.resolve_signals(top_output.ns)
top_file = build_name + ".v"
top_output.write(top_file)
platform.add_source(top_file)

# generate constraints
tools.write_to_file(build_name + ".lpf",
_build_lpf(named_sc, named_pc))

# generate yosys script
yosys_script_file = build_name + ".ys"
yosys_script_contents = "\n".join(_.format(build_name=build_name,
read_files=yosys_import_sources(platform))
for _ in self.yosys_template)
tools.write_to_file(yosys_script_file, yosys_script_contents)

# transform platform.device to nextpnr's architecture / basecfg
(family, size, package) = platform.device.split("-")
architecture = nextpnr_ecp5_architectures[(family + "-" + size).lower()]
basecfg = "empty_" + (family + "-" + size).lower() + ".config"
basecfg = os.path.join(toolchain_path, "misc", "basecfgs", basecfg)
freq_constraint = str(max(self.freq_constraints.values(),
default=0.0))

script = _build_script(False, self.build_template, build_name,
architecture, basecfg, freq_constraint)

# run scripts
if run:
_run_script(script)

os.chdir(cwd)

return top_output.ns

# Until nextpnr-ecp5 can handle multiple clock domains, use the same
# approach as the icestorm and use the fastest clock for timing
# constraints.
def add_period_constraint(self, platform, clk, period):
new_freq = 1000.0/period

if clk not in self.freq_constraints.keys():
self.freq_constraints[clk] = new_freq
else:
raise ConstraintError("Period constraint already added to signal.")

0 comments on commit 0c5d42c

Please sign in to comment.