Skip to content

Commit

Permalink
build/quicklogic: add support for Quicklogic toolchain
Browse files Browse the repository at this point in the history
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
kamilrakoczy authored and sbourdeauducq committed Oct 15, 2020
1 parent c50ecde commit 185a1b9
Show file tree
Hide file tree
Showing 5 changed files with 379 additions and 0 deletions.
63 changes: 63 additions & 0 deletions migen/build/platforms/quickfeather.py
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))
3 changes: 3 additions & 0 deletions migen/build/quicklogic/__init__.py
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

70 changes: 70 additions & 0 deletions migen/build/quicklogic/platform.py
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)
59 changes: 59 additions & 0 deletions migen/build/quicklogic/programmer.py
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])
184 changes: 184 additions & 0 deletions migen/build/quicklogic/quicklogic.py
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))

0 comments on commit 185a1b9

Please sign in to comment.