Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions edalize/flows/gatemate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Copyright edalize contributors
# Licensed under the 2-Clause BSD License, see LICENSE for details.
# SPDX-License-Identifier: BSD-2-Clause

import os.path

from edalize.flows.edaflow import Edaflow, FlowGraph

class Gatemate(Edaflow):
"""Open source toolchain for Cologne Chip GateMate FPGAs. Uses yosys for synthesis and nextpnr for Place & Route"""

argtypes = ["vlogdefine", "vlogparam"]

_flow = {
"yosys": {"fdto": {"arch": "gatemate", "output_format": "json",
"yosys_synth_options": ["-luttree", "-nomx8"],
}},
"nextpnr": {"deps": ["yosys"],
"fdto": {"arch": "gatemate",
"nextpnr_options": ["--router router2"],
}},
"gmpack": {"deps": ["nextpnr"]},
}

@classmethod
def get_tool_options(cls, flow_options):
tools = flow_options.get("frontends", []) + list(cls._flow)

flow_defined_tool_options = {}
for k, v in cls._flow.items():
flow_defined_tool_options[k] = v.get("fdto", {})
return cls.get_filtered_tool_options(tools, flow_defined_tool_options)

def configure_flow(self, flow_options):

flow = self._flow.copy()

# Add any user-specified frontends to the flow
deps = []
for frontend in flow_options.get("frontends", []):
flow[frontend] = {"deps": deps}
deps = [frontend]

flow["yosys"]["deps"] = deps

name = self.edam["name"]
self.commands.add([], ["synth"], [name + ".json"])
self.commands.add([], ["bitstream"], [name + ".bit"])
self.commands.set_default_target("bitstream")

return FlowGraph.fromdict(flow)
58 changes: 31 additions & 27 deletions edalize/gatemate.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,16 @@ def get_doc(cls, api_ver):
options = {
"lists": [
{
"name": "p_r_options",
"name": "nextpnr_options",
"type": "string",
"desc": "Additional option for p_r",
"desc": "Additional option for nextpnr",
},
],
"members": [
{
"name": "device",
"type": "String",
"desc": "Required device option for p_r command (e.g. CCGM1A1)",
"desc": "Required device option for nextpnr command (e.g. CCGM1A1)",
},
],
}
Expand All @@ -44,11 +44,11 @@ def get_doc(cls, api_ver):

def configure_main(self):
(src_files, incdirs) = self._get_fileset_files()
synth_out = self.name + "_synth.v"
synth_out = self.name + ".json"

device = self.tool_options.get("device")
if not device:
raise RuntimeError("Missing required option 'device' for p_r")
raise RuntimeError("Missing required option 'device' for nextpnr")

match = re.search("^CCGM1A([1-9]{1,2})$", device)
if not match:
Expand All @@ -64,21 +64,21 @@ def configure_main(self):
if f.file_type == "CCF":
if ccf_file:
raise RuntimeError(
"p_r only supports one ccf file. Found {} and {}".format(
"nextpnr only supports one ccf file. Found {} and {}".format(
ccf_file, f.name
)
)
else:
ccf_file = f.name

# p_r_log_file = None
p_r_log_file = "p_r.log"
# nextpnr_log_file = None
nextpnr_log_file = "nextpnr.log"

# Pass GateMate tool options to yosys
self.edam["tool_options"] = {
"yosys": {
"arch": "gatemate",
"output_format": "verilog",
"output_format": "json",
"output_name": synth_out,
"yosys_synth_options": self.tool_options.get("yosys_synth_options", []),
"yosys_as_subtool": True,
Expand All @@ -103,28 +103,32 @@ def configure_main(self):
commands = EdaCommands()
commands.commands = yosys.commands

# PnR & image generation
commands.add_var("P_R := $(shell which p_r)")
targets = self.name + "_00.cfg.bit"
# nextpnr & image generation
commands.add_var("NEXTPNR := $(shell which nextpnr-himbaechel)")
cfg_target = self.name + ".cfg"
command = [
"$(P_R)",
"-A",
device_number,
"-i",
synth_out,
"-o",
self.name,
"-lib",
"ccag",
" ".join(self.tool_options.get("p_r_options", "")),
"$(NEXTPNR)",
"--device",
device,
"--json",
"$<",
"-o out=" + cfg_target,
" ".join(self.tool_options.get("nextpnr_options", "")),
]
if ccf_file is not None:
command += ["-ccf", ccf_file]
command += ["-o ccf=" + ccf_file]

if p_r_log_file is not None:
command += [">", p_r_log_file]
if nextpnr_log_file is not None:
command += ["--log", nextpnr_log_file]

commands.add(command, [targets], [synth_out])
commands.add(command, [cfg_target], [synth_out])

commands.set_default_target(targets)
commands.add_var("GMPACK := $(shell which gmpack)")
bit_target = self.name + ".bit"
command = [
"$(GMPACK) $< $@",
]
commands.add(command, [bit_target], [cfg_target])

commands.set_default_target(bit_target)
commands.write(os.path.join(self.work_root, "Makefile"))
57 changes: 57 additions & 0 deletions edalize/tools/gmpack.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Copyright edalize contributors
# Licensed under the 2-Clause BSD License, see LICENSE for details.
# SPDX-License-Identifier: BSD-2-Clause

from edalize.tools.edatool import Edatool
from edalize.utils import EdaCommands

class Gmpack(Edatool):

description = "Generate binary image for GateMate FPGAs"

TOOL_OPTIONS = {
"gmpack_options": {
"type": "str",
"desc": "Additional options for gmpack",
"list": True,
},
}

def setup(self, edam):
super().setup(edam)

unused_files = []
bit_file = self.edam["name"] + ".bit"
gmcfg_file = ""
for f in self.files:
if f.get("file_type") == "gatemateConfig":
if gmcfg_file:
raise RuntimeError(
"gmpack only supports one input file. Found {} and {}".format(
gmcfg_file, f["name"]
)
)
gmcfg_file = f["name"]
else:
unused_files.append(f)

if not gmcfg_file:
raise RuntimeError("No input file specified for gmpack")

self.edam = edam.copy()
self.edam["files"] = unused_files
self.edam["files"].append({"name": bit_file, "file_type": "gatemateBitFile"})

# Image generation
depends = gmcfg_file
targets = bit_file
command = (
["gmpack"]
+ self.tool_options.get("gmpack_options", [])
+ [depends, targets]
)

commands = EdaCommands()
commands.add(command, [targets], [depends])
commands.set_default_target(targets)
self.commands = commands
26 changes: 25 additions & 1 deletion edalize/tools/nextpnr.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ def setup(self, edam):
lpf_file = ""
pcf_file = ""
sdc_file = ""
ccf_file = ""
netlist = ""
chipdb_file = ""
placement_constraints = []
Expand Down Expand Up @@ -59,6 +60,14 @@ def setup(self, edam):
)
)
pcf_file = f["name"]
if file_type == "CCF":
if ccf_file:
raise RuntimeError(
"Nextpnr only supports one CCF file. Found {} and {}".format(
ccf_file, f["name"]
)
)
ccf_file = f["name"]
if file_type == "chipdb":
if chipdb_file:
raise RuntimeError(
Expand Down Expand Up @@ -89,7 +98,7 @@ def setup(self, edam):
unused_files.append(f)

arch = self._require_tool_option("arch")
arches = ["xilinx", "ecp5", "gowin", "ice40"]
arches = ["xilinx", "ecp5", "gowin", "ice40", "gatemate"]
if not arch in arches:
raise RuntimeError("Invalid arch. Allowed options are " + ", ".join(arches))

Expand Down Expand Up @@ -149,6 +158,21 @@ def setup(self, edam):
{"name": targets, "file_type": "nextpnrRoutedJson"},
]
nextpnr_postfix = "himbaechel"
elif arch == "gatemate":
device = self.tool_options.get("device")
if not device:
raise RuntimeError(
"Missing required option 'device' for nextpnr-himbaechel"
)
arch_options += ["--device", device]

targets = self.name + ".cfg"
constraints = ["-o ccf={}".format(ccf_file)] if ccf_file else []
output = ["-o out={}".format(targets)]
output_files += [
{"name": targets, "file_type": "gatemateConfig"},
]
nextpnr_postfix = "himbaechel"
else:
targets = self.name + ".asc"
constraints = ["--pcf", pcf_file] if pcf_file else []
Expand Down