Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Command Generator Enhancements #251

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
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
2 changes: 1 addition & 1 deletion edalize/apicula.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ def configure_main(self):
depends = self.name+'.pack'
targets = self.name+'.fs'
command = ['gowin_pack', '-d', self.tool_options.get('device'), '-o', targets, depends]
commands.add(command, [targets], [depends])
commands.add([command], [self.EdaCommands.Target(targets)], [depends])

commands.set_default_target(targets)
commands.write(os.path.join(self.work_root, 'Makefile'))
24 changes: 20 additions & 4 deletions edalize/edatool.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,14 @@ def __init__(self, command, targets, depends):
self.targets = targets
self.depends = depends

class Target:
def __init__(self, target, phony=False):
self.target = target
self.phony = phony

def __str__(self):
return str(self.target)

def __init__(self):
self.commands = []
self.header = "#Auto generated by Edalize\n\n"
Expand All @@ -254,14 +262,22 @@ def write(self, outfile):

f.write(f"all: {self.default_target}\n")

phony_targets = ["all"]
for c in self.commands:
f.write(f"\n{' '.join(c.targets)}:")
cmd_targets = []
for t in c.targets:
cmd_targets.append(str(t))
if t.phony:
phony_targets.append(str(t))
f.write(f"\n{' '.join(cmd_targets)}:")
for d in c.depends:
f.write(" "+d)
f.write(" "+str(d))
f.write("\n")

if c.command:
f.write(f"\t$(EDALIZE_LAUNCHER) {' '.join(c.command)}\n")
for cmd in c.command:
f.write(f"\t$(EDALIZE_LAUNCHER) {' '.join(cmd)}\n")

f.write(f"\n.PHONY: {' '.join(phony_targets)}\n")

def set_default_target(self, target):
self.default_target = target
Expand Down
12 changes: 6 additions & 6 deletions edalize/icestorm.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ def configure_main(self):
command = ['arachne-pnr']
command += self.tool_options.get('arachne_pnr_options', [])
command += ['-p', depends, '-o', targets]
commands.add(command, [depends], [targets])
commands.add([command], [self.EdaCommands.Target(depends)], [targets])
set_default_target(self.name+'.bin')
elif pnr == 'next':
nextpnr = Nextpnr(yosys.edam, self.work_root)
Expand All @@ -82,20 +82,20 @@ def configure_main(self):
depends = self.name+'.asc'
targets = self.name+'.bin'
command = ['icepack', depends, targets]
commands.add(command, [targets], [depends])
commands.add([command], [self.EdaCommands.Target(targets)], [depends])

#Timing analysis
depends = self.name+'.asc'
targets = self.name+'.tim'
command = ['icetime', '-tmd', part or '', depends, targets]
commands.add(command, [targets], [depends])
commands.add([], ["timing"], [targets])
commands.add([command], [self.EdaCommands.Target(targets)], [depends])
commands.add([], [self.EdaCommands.Target("timing", phony=True)], [targets])

#Statistics
depends = self.name+'.asc'
targets = self.name+'.stat'
command = ['icebox_stat', depends, targets]
commands.add(command, [targets], [depends])
commands.add([], ["stats"], [targets])
commands.add([command], [self.EdaCommands.Target(targets)], [depends])
commands.add([], [self.EdaCommands.Target("stats", phony=True)], [targets])

commands.write(os.path.join(self.work_root, 'Makefile'))
4 changes: 2 additions & 2 deletions edalize/nextpnr.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,8 @@ def configure_main(self):
command += constraints + ['--json', depends] + output

#CLI target
commands.add(command, [targets], [depends])
commands.add([command], [self.EdaCommands.Target(targets)], [depends])

#GUI target
commands.add(command+['--gui'], ["build-gui"], [depends])
commands.add([command+['--gui']], [self.EdaCommands.Target("build-gui", phony=True)], [depends])
self.commands = commands.commands
157 changes: 125 additions & 32 deletions edalize/quartus.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import logging
import os.path
import os
import platform
import subprocess
import re
import xml.etree.ElementTree as ET
Expand All @@ -20,8 +19,6 @@ class Quartus(Edatool):

# Define Standard edition to be our default version
isPro = False
makefile_template = {False : "quartus-std-makefile.j2",
True : "quartus-pro-makefile.j2"}

@classmethod
def get_doc(cls, api_ver):
Expand Down Expand Up @@ -106,10 +103,8 @@ def __init__(self, edam=None, work_root=None, eda_api=None, verbose=False):
def configure_main(self):
(src_files, incdirs) = self._get_fileset_files(force_slash=True)
self.jinja_env.filters['src_file_filter'] = self.src_file_filter
self.jinja_env.filters['qsys_file_filter'] = self.qsys_file_filter

has_vhdl2008 = 'vhdlSource-2008' in [x.file_type for x in src_files]
has_qsys = 'QSYS' in [x.file_type for x in src_files]

escaped_name = self.name.replace(".", "_")

Expand All @@ -125,51 +120,149 @@ def configure_main(self):
'has_vhdl2008' : has_vhdl2008
}

# Render Makefile based on detected version
self.render_template(self.makefile_template[self.isPro],
'Makefile',
{ 'name' : escaped_name,
'src_files' : src_files,
'tool_options' : self.tool_options})
self._write_makefile(escaped_name, src_files)

# Render the TCL project file
self.render_template('quartus-project.tcl.j2',
escaped_name + '.tcl',
template_vars)

""" Write a the Makefile using EdaCommands from the base class
"""

# Helper to extract file type
def file_type(self, f):
return f.file_type.split('-')[0]
def _write_makefile(self, escaped_name, src_files):
# Write Makefile
commands = self.EdaCommands()

# Add variables
#
# It would be better to make an official interface rather than muck with
# the header

commands.header += f"""NAME := {escaped_name}
OPTIONS := {" ".join(self.tool_options["quartus_options"])}
DSE_OPTIONS := {" ".join(self.tool_options["dse_options"])}

"""

# Project file
depends = "$(NAME).tcl"
cmd = ["quartus_sh", "$(OPTIONS)", "-t", depends]
targets = [self.EdaCommands.Target("project", phony=True)]
commands.add([cmd], targets, [depends])

# Qsys
qsys_files = [f for f in src_files if f.file_type == "QSYS"]
depends = "project"
targets = [self.EdaCommands.Target("qsys", phony=True)]
cmds = []

for f in qsys_files:

# Give QSYS files special attributes to make the logic in
# the Jinja2 templates much simplier
setattr(f, "simplename", os.path.basename(f.name).split(".qsys")[0])
setattr(f, "srcdir", os.path.dirname(f.name) or ".")
setattr(f, "dstdir", os.path.join("qsys", f.simplename))

# Filter for just QSYS files. This verifies that they are compatible
# with the identified Quartus version
def qsys_file_filter(self, f):
name = ''
if self.file_type(f) == 'QSYS':
# Compatibility checks
try:
qsysTree = ET.parse(os.path.join(self.work_root, f.name))
try:
tool = qsysTree.find('component').attrib['tool']
if tool == 'QsysPro' and self.isPro:
name = f.name
tool = qsysTree.find("component").attrib["tool"]
if not (tool == "QsysPro" and self.isPro):
continue
except (AttributeError, KeyError):
# Either a component wasn't found in the QSYS file, or it
# had no associated tool information. Make the assumption
# it was a Standard edition file
if not self.isPro:
name = f.name
if self.isPro:
continue
except (ET.ParseError, IOError):
logger.warning("Unable to parse QSYS file " + f.name)

# Give QSYS files special attributes to make the logic in
# the Jinja2 templates much simplier
setattr(f, "simplename", os.path.basename(f.name).split('.qsys')[0])
setattr(f, "srcdir", os.path.dirname(f.name) or '.')
setattr(f, "dstdir", os.path.join('qsys', f.simplename))

return name
family = self.tool_options["family"]
device = self.tool_options["device"]
if self.isPro:
cmds.append(
[
"qsys-generate",
f.name,
"--synthesis=VERILOG",
f'--family="{family}"',
f"--part={device}",
"--quartus-project=$(NAME)",
]
)
else:
cmds.append(
[
"ip-generate",
f"--project-directory={f.srcdir}",
f"--output-directory={f.dstdir}",
f"--report-file=bsf:{f.dstdir}/{f.simplename}.bsf",
f'--system-info=DEVICE_FAMILY="{family}"',
f"--system-info=DEVICE={device}",
f"--component-file={f.srcdir}/{f.simplename}.qsys",
]
)
cmds.append(
[
"ip-generate",
f"--project-directory={f.srcdir}",
f"--output-directory={f.dstdir}/synthesis",
"--file-set=QUARTUS_SYNTH",
f"--report-file=sopcinfo:{f.dstdir}/{f.simplename}.sopcinfo",
f"--report-file=html:{f.dstdir}/{f.simplename}.html",
f"--report-file=qip:{f.dstdir}/{f.simplename}.qip",
f"--report-file=cmp:{f.dstdir}/{f.simplename}.cmp",
"--report-file=svd",
f'--system-info=DEVICE_FAMILY="{family}"',
f"--system-info=DEVICE={device}",
f"--component-file={f.srcdir}/{f.simplename}.qsys",
"--language=VERILOG",
]
)

commands.add(cmds, targets, [depends])

# syn
if self.isPro:
syn = "quartus_syn"
else:
syn = "quartus_map"
cmd = [syn, "$(OPTIONS)", "$(NAME)"]
targets = [self.EdaCommands.Target("syn", phony=True)]
commands.add([cmd], targets, ["qsys"])

# fit, asm, sta
for target, depends in [("fit", "syn"), ("asm", "fit"), ("sta", "asm")]:
cmd = [f"quartus_{target}", "$(OPTIONS)", "$(NAME)"]
targets = [self.EdaCommands.Target(target, phony=True)]
commands.add([cmd], targets, [depends])

# DSE
cmd = ["quartus_dse", "$(NAME)", "$(DSE_OPTIONS)"]
targets = [self.EdaCommands.Target("dse", phony=True)]
commands.add([cmd], targets, ["syn"])

# clean
cmd = ["rm", "-rf", "*.*"]
if self.isPro:
cmd += ["qdb", "tmp-clearbox"]
else:
cmd += ["db", "incremental_db"]

targets = [self.EdaCommands.Target("clean", phony=True)]
commands.add([cmd], targets, [])

commands.set_default_target("sta")

commands.write(os.path.join(self.work_root, "Makefile"))

# Helper to extract file type
def file_type(self, f):
return f.file_type.split('-')[0]

# Allow the templates to get source file information
def src_file_filter(self, f):
Expand All @@ -181,7 +274,7 @@ def _append_library(f):

def _handle_qsys(t, f):
# Quartus Pro just passes QSYS files onto the compiler, but Standard
# expects to see them sepecified as QIP. The Makefile is responsible
# expects to see them specified as QIP. The Makefile is responsible
# for creating that QIP file for Standard edition in a known place
# which can be used below
if self.isPro:
Expand Down
Loading