-
Notifications
You must be signed in to change notification settings - Fork 211
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
build/quicklogic: add support for Quicklogic toolchain
This commit also adds support for EOS-S3 QuickFeather and Chandalar boards. Signed-off-by: Kamil Rakoczy <krakoczy@antmicro.com> Signed-off-by: Jan Kowalewski <jkowalewski@antmicro.com>
- Loading branch information
1 parent
c50ecde
commit 185a1b9
Showing
5 changed files
with
379 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
# This file is Copyright (c) 2020 Antmicro <www.antmicro.com> | ||
|
||
from migen.build.generic_platform import * | ||
from migen.build.quicklogic import QuicklogicPlatform, JLinkProgrammer, OpenOCD | ||
|
||
_io = [ | ||
("rgb_led", 0, | ||
Subsignal("r", Pins("34")), | ||
Subsignal("g", Pins("39")), | ||
Subsignal("b", Pins("38")), | ||
IOStandard("LVCMOS33"), | ||
), | ||
("i2c", 0, | ||
Subsignal("scl", Pins("4")), | ||
Subsignal("sda", Pins("5")), | ||
IOStandard("LVCMOS33"), | ||
), | ||
("accel", 0, | ||
Subsignal("int", Pins("2")), | ||
IOStandard("LVCMOS33"), | ||
), | ||
("user_btn", 0, Pins("62"), IOStandard("LVCMOS33")), | ||
("serial_debug", 0, | ||
Subsignal("clk", Pins("54")), | ||
Subsignal("data", Pins("53")), | ||
IOStandard("LVCMOS33"), | ||
), | ||
("spi", 0, | ||
Subsignal("clk", Pins("20")), | ||
Subsignal("mosi", Pins("16")), | ||
Subsignal("miso", Pins("17")), | ||
IOStandard("LVCMOS33"), | ||
), | ||
("serial", 0, | ||
Subsignal("tx", Pins("8")), | ||
Subsignal("rx", Pins("9")), | ||
IOStandard("LVCMOS33"), | ||
), | ||
] | ||
|
||
_connectors = [ | ||
("J2", "28 22 21 37 36 42 40 7"), | ||
("J3", "6 55 31 25 47 41"), | ||
("J8", "27 26 33 32 23 57 56 3 64 63 61 59"), | ||
] | ||
|
||
class Platform(QuicklogicPlatform): | ||
default_clk_name = "IO_CLK" | ||
default_clk_period = 10.00 | ||
|
||
def __init__(self, programmer="jlink"): | ||
QuicklogicPlatform.__init__(self, "quickfeather", _io, _connectors, | ||
toolchain="quicklogic") | ||
self.programmer = programmer | ||
|
||
def create_programmer(self): | ||
if self.programmer is "jlink": | ||
return JLinkProgrammer() | ||
elif self.programmer is "openocd": | ||
return OpenOCD() | ||
else: | ||
raise ValueError("{} programmer is not supported" | ||
.format(self.programmer)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
from migen.build.quicklogic.platform import QuicklogicPlatform | ||
from migen.build.quicklogic.programmer import JLinkProgrammer, OpenOCD | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
import os | ||
import shutil | ||
|
||
from migen.build.generic_platform import GenericPlatform | ||
from migen.build.quicklogic import quicklogic | ||
|
||
class QuicklogicPlatform(GenericPlatform): | ||
bitstream_ext = ".bit" | ||
|
||
def __init__(self, *args, toolchain="quicklogic", **kwargs): | ||
GenericPlatform.__init__(self, *args, **kwargs) | ||
self.edifs = set() | ||
self.ips = set() | ||
|
||
self.board_type = "ql-eos-s3_wlcsp" | ||
if self.device == "chandalar": | ||
self.part = "PD64" | ||
elif self.device == "quickfeather": | ||
self.part = "PU64" | ||
else: | ||
raise ValueError("Unknown device") | ||
|
||
if toolchain == "quicklogic": | ||
self.toolchain = quicklogic.QuicklogicToolchain() | ||
else: | ||
raise ValueError("Unknown toolchain") | ||
|
||
def add_edif(self, filename): | ||
self.edifs.add((os.path.abspath(filename))) | ||
|
||
def add_ip(self, filename): | ||
self.ips.add((os.path.abspath(filename))) | ||
|
||
def copy_ips(self, build_dir, subdir="ip"): | ||
copied_ips = set() | ||
|
||
target = os.path.join(build_dir, subdir) | ||
os.makedirs(target, exist_ok=True) | ||
for filename in self.ips: | ||
path = os.path.join(subdir, os.path.basename(filename)) | ||
dest = os.path.join(build_dir, path) | ||
shutil.copyfile(filename, dest) | ||
copied_ips.add(path) | ||
|
||
return copied_ips | ||
|
||
def get_verilog(self, *args, special_overrides=dict(), **kwargs): | ||
return GenericPlatform.get_verilog(self, *args, | ||
attr_translate=self.toolchain.attr_translate, **kwargs) | ||
|
||
def get_edif(self, fragment, **kwargs): | ||
return GenericPlatform.get_edif(self, fragment, "UNISIMS", "Quicklogic", self.device, **kwargs) | ||
|
||
def build(self, *args, **kwargs): | ||
return self.toolchain.build(self, *args, **kwargs) | ||
|
||
def add_period_constraint(self, clk, period): | ||
if hasattr(clk, "p"): | ||
clk = clk.p | ||
self.toolchain.add_period_constraint(self, clk, period) | ||
|
||
def add_false_path_constraint(self, from_, to): | ||
if hasattr(from_, "p"): | ||
from_ = from_.p | ||
if hasattr(to, "p"): | ||
to = to.p | ||
self.toolchain.add_false_path_constraint(self, from_, to) | ||
|
||
def do_finalize(self, fragment, *args, **kwargs): | ||
super().do_finalize(fragment, *args, **kwargs) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import os | ||
import sys | ||
import subprocess | ||
|
||
from migen.build.generic_programmer import GenericProgrammer | ||
|
||
# This programmer requires OpenOCD with support for eos-s3 | ||
# it has not been merged with mainline yet, but is available at: | ||
# https://github.com/antmicro/openocd/tree/eos-s3-support | ||
class OpenOCD(GenericProgrammer): | ||
|
||
def __init__(self, flash_proxy_basename=None): | ||
GenericProgrammer.__init__(self, flash_proxy_basename) | ||
|
||
def load_bitstream(self, bitstream_file): | ||
bitstream_folder = os.path.dirname(bitstream_file) | ||
top_path = bitstream_folder + "/top.cfg" | ||
subprocess.call(["python", "-m", "quicklogic_fasm.bitstream_to_openocd", bitstream_file, top_path]) | ||
try: | ||
openocd_proc = subprocess.Popen(["openocd", "-s", "tcl", | ||
"-f", "interface/ftdi/antmicro-ftdi-adapter.cfg", | ||
"-f", "interface/ftdi/swd-resistor-hack.cfg", | ||
"-f", "board/quicklogic_quickfeather.cfg", | ||
"-f", top_path]) | ||
gdb_commands = ["tar rem :3333", "monitor reset halt", "monitor load_bitstream"] | ||
gdb_output_path = bitstream_folder + "/gdb.commands" | ||
with open(gdb_output_path, 'w') as f: | ||
f.write("\n".join(gdb_commands)) | ||
path_env = os.environ['PATH'].split(":") | ||
import glob | ||
gdb = None | ||
for path in path_env: | ||
gdb_glob = glob.glob(path + '/arm-*-eabi-gdb') | ||
if len(gdb_glob): | ||
gdb = gdb_glob[0].strip() | ||
break; | ||
if gdb is None: | ||
raise Exception("No arm-*-eabi-gdb found in PATH") | ||
subprocess.call([gdb, "-x", gdb_output_path]) | ||
except Exception as e: | ||
openocd_proc.kill() | ||
raise e | ||
|
||
class JLinkProgrammer(GenericProgrammer): | ||
|
||
def __init__(self, flash_proxy_basename=None): | ||
GenericProgrammer.__init__(self, flash_proxy_basename) | ||
|
||
def load_bitstream(self, bitstream_file): | ||
bitstream_folder = os.path.dirname(bitstream_file) | ||
jlink_output_path = bitstream_folder + "/top.jlink" | ||
jlink_output_reset_path = bitstream_folder + "/top_reset.jlink" | ||
subprocess.call(["python", "-m", "quicklogic_fasm.bitstream_to_jlink", bitstream_file, jlink_output_path]) | ||
with open(jlink_output_path, 'r') as f: | ||
with open(jlink_output_reset_path,'w') as f2: # add reset command at the beginning | ||
f2.write("r\n") | ||
f2.write(f.read()) | ||
|
||
subprocess.call(["JLinkExe", "-Device", "Cortex-M4", "-If", "SWD", "-Speed", "4000", "-commandFile", jlink_output_reset_path]) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,184 @@ | ||
# This file is Copyright (c) 2020 Antmicro <www.antmicro.com> | ||
# License: BSD | ||
|
||
import os | ||
import subprocess | ||
import sys | ||
import math | ||
import re | ||
import warnings | ||
import shlex | ||
|
||
from migen.fhdl.structure import _Fragment, wrap, Constant | ||
from migen.fhdl.specials import Instance | ||
|
||
from migen.build.generic_platform import * | ||
from migen.build import tools | ||
|
||
def _unwrap(value): | ||
return value.value if isinstance(value, Constant) else value | ||
|
||
def _build_pcf(named_sc): | ||
r = "" | ||
current_resname = "" | ||
for sig, pins, _, resname in named_sc: | ||
if current_resname != resname[0]: | ||
if current_resname: | ||
r += "\n" | ||
current_resname = resname[0] | ||
r += f"# {current_resname}\n" | ||
if len(pins) > 1: | ||
for i, p in enumerate(pins): | ||
r += f"set_io {sig}[{i}] {Pins(p).identifiers[0]}\n" | ||
elif pins: | ||
r += f"set_io {sig} {Pins(pins[0]).identifiers[0]}\n" | ||
return r | ||
|
||
def _build_sdc(named_pc): | ||
return "\n".join(named_pc) if named_pc else "" | ||
|
||
try: | ||
import colorama | ||
colorama.init() # install escape sequence translation on Windows | ||
_have_colorama = True | ||
except ImportError: | ||
_have_colorama = False | ||
|
||
colors = [] | ||
if _have_colorama: | ||
colors += [ | ||
("^(Error( [0-9]+)?:|.*: ERROR: ).*$", colorama.Fore.RED + colorama.Style.BRIGHT + | ||
r"\g<0>" + colorama.Style.RESET_ALL), | ||
("^(Warning( [0-9]+)?:|.*: WARNING: ).*$", colorama.Fore.YELLOW + | ||
r"\g<0>" + colorama.Style.RESET_ALL), | ||
] | ||
|
||
def _run_script(build_name): | ||
if sys.platform == "win32" or sys.platform == "cygwin": | ||
# TODO: add support for Windows | ||
raise NotImplementedError("Windows support is not implemented") | ||
else: | ||
command = ["sh", f"build_{build_name}.sh"] | ||
r = tools.subprocess_call_filtered(command, colors, stderr=subprocess.STDOUT) | ||
if r != 0: | ||
raise OSError("Subprocess failed") | ||
|
||
class QuicklogicToolchain: | ||
attr_translate = { | ||
"keep": ("dont_touch", "true"), | ||
"no_retiming": ("dont_touch", "true"), | ||
"async_reg": ("async_reg", "true"), | ||
"mr_ff": ("mr_ff", "true"), # user-defined attribute | ||
"ars_ff1": ("ars_ff1", "true"), # user-defined attribute | ||
"ars_ff2": ("ars_ff2", "true"), # user-defined attribute | ||
"no_shreg_extract": None | ||
} | ||
|
||
def __init__(self): | ||
self.clocks = dict() | ||
self.false_paths = set() | ||
|
||
def __setattr__(self, name, value): | ||
if name in ["bitstream_commands", "additional_commands", "pre_synthesis_commands", "with_phys_opt"]: | ||
warnings.warn(f"'{name}' is not supported by Quicklogic toolchain.", Warning, stacklevel=2) | ||
else: | ||
object.__setattr__(self, name, value) | ||
|
||
def _generate_build_script(self, platform, sources, build_name): | ||
if sys.platform == "win32" or sys.platform == "cygwin": | ||
# TODO: add support for Windows | ||
raise NotImplementedError("Windows support is not implemented") | ||
|
||
sh = [ | ||
"#!/bin/sh", | ||
"# Autogenerated by Migen", | ||
"set -ex", # stop execution when any command fail (e); print each command to stderr (x) | ||
] | ||
escaped_sources = " ".join([shlex.quote(f) for f,language,_ in sources if language in ["verilog", "system_verilog"]]) | ||
sh.append(f"synth -t {build_name} -v {escaped_sources} -d {platform.board_type} -p {build_name}.pcf -P {platform.part}") | ||
sh.append(f"pack -e {build_name}.eblif -d {platform.board_type} -s {build_name}.sdc") | ||
sh.append(f"place -e {build_name}.eblif -d {platform.board_type} -n {build_name}.net -P {platform.part} -s {build_name}.sdc -p {build_name}.pcf") | ||
sh.append(f"route -e {build_name}.eblif -d {platform.board_type} -s {build_name}.sdc") | ||
sh.append(f"write_fasm -e {build_name}.eblif -d {platform.board_type} -s {build_name}") | ||
sh.append(f"write_bitstream -d {platform.board_type} -f {build_name}.fasm -P {platform.part} -b {build_name}.bit") | ||
tools.write_to_file(f"build_{build_name}.sh", "\n".join(sh) + "\n") | ||
|
||
def _build_clock_constraints(self, platform): | ||
for clk, period in sorted(self.clocks.items(), key=lambda x: x[0].duid): | ||
platform.add_platform_command(f"create_clock -period {period} {{clk}}", clk=clk) | ||
for from_, to in sorted(self.false_paths, key=lambda x: (x[0].duid, x[1].duid)): | ||
platform.add_platform_command("set_clock_groups -exclusive -group {{{from_}}} -group {{{to}}}", from_=from_, to=to) | ||
# Make sure add_*_constraint cannot be used again | ||
del self.clocks | ||
del self.false_paths | ||
|
||
def build(self, platform, fragment, | ||
build_dir = "build", | ||
build_name = "top", | ||
run = 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 timing constraints | ||
self._build_clock_constraints(platform) | ||
|
||
# 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) | ||
sources = platform.copy_sources(build_dir) | {(v_file, "verilog", "work")} | ||
|
||
self._generate_build_script( | ||
platform = platform, | ||
sources = sources, | ||
build_name = build_name | ||
) | ||
|
||
sdc_commands = [ | ||
"create_clock", | ||
"set_clock_groups", | ||
"set_false_path", | ||
"set_max_delay", "set_min_delay", | ||
"set_multicycle_path", | ||
"set_input_delay", "set_output_delay", | ||
"set_clock_uncertainty", | ||
"set_clock_latency", | ||
"set_disable_timing" | ||
] | ||
named_pc_sdc = [] | ||
for pc in named_pc: | ||
cmd = pc.split(maxsplit=1)[0] | ||
if cmd in sdc_commands: | ||
named_pc_sdc.append(pc) | ||
|
||
# Generate design constraints | ||
tools.write_to_file(build_name + ".pcf", _build_pcf(named_sc)) | ||
tools.write_to_file(build_name + ".sdc", _build_sdc(named_pc_sdc)) | ||
|
||
if run: | ||
_run_script(build_name) | ||
|
||
os.chdir(cwd) | ||
|
||
return v_output.ns | ||
|
||
def add_period_constraint(self, platform, clk, period): | ||
if clk in self.clocks: | ||
raise ValueError("A period constraint already exists") | ||
self.clocks[clk] = period | ||
|
||
def add_false_path_constraint(self, platform, from_, to): | ||
if (from_, to) in self.false_paths or (to, from_) in self.false_paths: | ||
return | ||
self.false_paths.add((from_, to)) | ||
|