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

Add missing features from symbiflow #259

Closed
wants to merge 1 commit into from
Closed
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
37 changes: 27 additions & 10 deletions edalize/edatool.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,15 @@ def run(self, args={}):
self.run_main()
self.run_post()

def check_args(self, unknown):
# If a tool is using subtools some of the argument may be
# parsed by the subtool. This function is used to check if
# all the provided arguments were correct. A tool can override
# this function to provide custom args checking logic.

if unknown:
raise Exception(f'Unknown command line option {unknown[0]}')

def run_pre(self, args=None):
if type(args) == list:
parsed_args = self.parse_args(args, self.argtypes)
Expand All @@ -239,6 +248,7 @@ def __init__(self, command, targets, depends):
def __init__(self):
self.commands = []
self.header = "#Auto generated by Edalize\n\n"
self.header += "SHELL := /bin/bash\n\n"

def add(self, command, targets, depends):
self.commands.append(self.Command(command, targets, depends))
Expand Down Expand Up @@ -321,7 +331,9 @@ def parse_args(self, args, paramtypes):
help=_opt['desc'])

args_dict = {}
for key, value in vars(parser.parse_args(args)).items():
known, unknown = parser.parse_known_args(args)
self.check_args(unknown)
for key, value in vars(known).items():
if value is None:
continue
if type(value) == list:
Expand Down Expand Up @@ -408,8 +420,12 @@ def _run_scripts(self, scripts, hook_name):
cp = run(script['cmd'],
cwd = self.work_root,
env = _env,
capture_output=not self.verbose,
stdin = subprocess.PIPE,
stdout = subprocess.PIPE,
stderr = subprocess.STDOUT,
capture_output=not self.verbose,
check = True)
logger.info(cp.stdout)
except FileNotFoundError as e:
msg = "Unable to run {} script '{}': {}"
raise RuntimeError(msg.format(hook_name, script['name'], str(e)))
Expand All @@ -431,17 +447,18 @@ def _run_tool(self, cmd, args=[], quiet=False):
capture_output = quiet and not (self.verbose or self.stdout or self.stderr)
try:
cp = run([cmd] + args,
cwd = self.work_root,
stdin=subprocess.PIPE,
stdout=self.stdout,
stderr=self.stderr,
capture_output=capture_output,
check=True)
cwd = self.work_root,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
capture_output=capture_output,
check=True)
logger.info(cp.stdout)
except FileNotFoundError:
_s = "Command '{}' not found. Make sure it is in $PATH".format(cmd)
raise RuntimeError(_s)
except subprocess.CalledProcessError as e:
_s = "'{}' exited with an error: {}".format(e.cmd, e.returncode)
_s = "'{}' exited with an error: {}\n{}".format(e.cmd, e.returncode, e.output.decode())
logger.debug(_s)

if e.stdout:
Expand All @@ -450,7 +467,7 @@ def _run_tool(self, cmd, args=[], quiet=False):
logger.error(e.stderr.decode())
logger.debug("=== STDERR ===")
logger.debug(e.stderr)

raise RuntimeError(_s)
return cp.returncode, cp.stdout, cp.stderr

Expand Down
9 changes: 7 additions & 2 deletions edalize/icestorm.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ def get_doc(cls, api_ver):
'members' : options['members'],
'lists' : options['lists']}

def check_args(self, unknown):
Yosys.validate_args(unknown)

def configure_main(self):
# Write yosys script file
yosys_synth_options = self.tool_options.get('yosys_synth_options', '')
Expand All @@ -42,8 +45,10 @@ def configure_main(self):
{'yosys' : {
'arch' : 'ice40',
'yosys_synth_options' : yosys_synth_options,
'yosys_read_options' : self.tool_options.get('yosys_read_options', []),
'yosys_as_subtool' : True,
'yosys_template' : self.tool_options.get('yosys_template'),
'frontend_options' : self.tool_options.get('frontedn_options',[])
},
'nextpnr' : {
'nextpnr_options' : self.tool_options.get('nextpnr_options', [])
Expand Down Expand Up @@ -87,14 +92,14 @@ def configure_main(self):
#Timing analysis
depends = self.name+'.asc'
targets = self.name+'.tim'
command = ['icetime', '-tmd', part or '', depends, targets]
command = ['icetime', '-tmd', part or '', depends, "-r", targets]
commands.add(command, [targets], [depends])
commands.add([], ["timing"], [targets])

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

Expand Down
59 changes: 59 additions & 0 deletions edalize/surelog.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@

import logging
import os.path

from edalize.edatool import Edatool
import shutil

logger = logging.getLogger(__name__)

class Surelog(Edatool):

argtypes = ['vlogdefine', 'vlogparam']

@classmethod
def get_doc(cls, api_ver):
if api_ver == 0:
return {'description' : "Surelog",
'members' : [
{'name' : 'arch',
'type' : 'String',
'desc' : 'Target architecture. Legal values are *xilinx*, *ice40* and *ecp5*'}
],
'lists' : [
{'name' : 'surelog_options',
'type' : 'String',
'desc' : 'List of the Surelog parameters'},
]}

def configure_main(self):
(src_files, incdirs) = self._get_fileset_files()
verilog_file_list = []
systemverilog_file_list = []
for f in src_files:
if f.file_type.startswith('verilogSource'):
verilog_file_list.append(f.name)
if f.file_type.startswith('systemVerilogSource'):
systemverilog_file_list.append("-sv " + f.name)

surelog_options = self.tool_options.get('surelog_options', [])
arch = self.tool_options.get('arch', None)

pattern = len(self.vlogparam.keys()) * " -P%s=%%s"
verilog_params_command = pattern % tuple(self.vlogparam.keys()) % tuple(self.vlogparam.values())

verilog_defines_command = "+define" if self.vlogdefine.items() else ""
pattern = len(self.vlogdefine.keys()) * "+%s=%%s"
verilog_defines_command += pattern % tuple(self.vlogdefine.keys()) % tuple(self.vlogdefine.values())

pattern = len(incdirs) * " -I%s"
include_files_command = pattern % tuple(incdirs)

commands = self.EdaCommands()
share_path = os.path.join(os.path.dirname(shutil.which("uhdm-yosys")), "../share/uhdm-yosys")
commands.add(["surelog", f"{' '.join(surelog_options)}", "-parse", f"{verilog_defines_command}",
f"{verilog_params_command}", f"-top {self.toplevel}", f"{include_files_command}",
f"{' '.join(verilog_file_list)}", f"{' '.join(systemverilog_file_list)}"],
["slpp_all/surelog.uhdm"], [])
commands.add([f"cp slpp_all/surelog.uhdm {self.toplevel}.uhdm"] ,[self.toplevel + '.uhdm'], ["slpp_all/surelog.uhdm"])
self.commands = commands.commands
101 changes: 96 additions & 5 deletions edalize/symbiflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

from edalize.edatool import Edatool
from edalize.yosys import Yosys
from edalize.surelog import Surelog
from importlib import import_module

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -71,6 +72,31 @@ def get_doc(cls, api_ver):
"type": "String",
"desc": "Additional options for Nextpnr tool. If not used, default options for the tool will be used",
},
{
"name": 'fasm2bels',
"type": 'Boolean',
"desc": 'Value to state whether fasm2bels is to be used'
},
{
"name": 'dbroot',
"type": 'String',
"desc": 'Path to the database root (needed by fasm2bels).'
},
{
"name": 'clocks',
"type": 'dict',
"desc": 'Clocks to be added for having tools correctly handling timing based routing.'
},
{
"name": 'seed',
"type": 'String',
"desc": 'Seed assigned to the PnR tool.'
},
{
"name": "schema_dir",
"type": "String",
"desc": "Path if Capnp schema used by fpga_interchange",
},
],
}

Expand All @@ -90,6 +116,7 @@ def configure_nextpnr(self):

# Yosys configuration
yosys_synth_options = self.tool_options.get("yosys_synth_options", "")
yosys_additional_commands = self.tool_options.get('yosys_additional_commands', '')
yosys_template = self.tool_options.get("yosys_template")
yosys_edam = {
"files" : self.files,
Expand All @@ -100,8 +127,11 @@ def configure_nextpnr(self):
"yosys" : {
"arch" : vendor,
"yosys_synth_options" : yosys_synth_options,
"yosys_additional_commands" : yosys_additional_commands,
'yosys_read_options' : self.tool_options.get('yosys_read_options', []),
"yosys_template" : yosys_template,
"yosys_as_subtool" : True,
'frontend_options' : self.tool_options.get('frontend_options', []),
}
}
}
Expand Down Expand Up @@ -174,14 +204,16 @@ def configure_nextpnr(self):
commands = self.EdaCommands()
commands.commands = yosys.commands
if arch == "fpga_interchange":
commands.header += """ifndef INTERCHANGE_SCHEMA_PATH
schema_dir = self.tool_options.get('schema_dir', None)
if schema_dir is None:
commands.header += """ifndef INTERCHANGE_SCHEMA_PATH
$(error Environment variable INTERCHANGE_SCHEMA_PATH was not found. It should be set to <fpga-interchange-schema path>/interchange)
endif

"""
targets = self.name+'.netlist'
command = ['python', '-m', 'fpga_interchange.yosys_json']
command += ['--schema_dir', '$(INTERCHANGE_SCHEMA_PATH)']
command += ['--schema_dir', '$(INTERCHANGE_SCHEMA_PATH)' if schema_dir is None else schema_dir]
command += ['--device', device]
command += ['--top', self.toplevel]
command += [depends, targets]
Expand All @@ -201,7 +233,7 @@ def configure_nextpnr(self):
depends = self.name+'.phys'
targets = self.name+'.fasm'
command = ['python', '-m', 'fpga_interchange.fasm_generator']
command += ['--schema_dir', '$(INTERCHANGE_SCHEMA_PATH)']
command += ['--schema_dir', '$(INTERCHANGE_SCHEMA_PATH)' if schema_dir is None else schema_dir]
command += ['--family', family, device, self.name+'.netlist', depends, targets]
commands.add(command, [targets], [depends])
else:
Expand All @@ -227,7 +259,7 @@ def configure_nextpnr(self):
def configure_vpr(self):
(src_files, incdirs) = self._get_fileset_files(force_slash=True)

has_vhdl = "vhdlSource" in [x.file_type for x in src_files]
has_vhdl = "vhdlSource" in [x.file_type for x in src_files]
has_vhdl2008 = "vhdlSource-2008" in [x.file_type for x in src_files]

if has_vhdl or has_vhdl2008:
Expand All @@ -237,6 +269,10 @@ def configure_vpr(self):
pins_constraints = []
placement_constraints = []

vpr_grid = None
rr_graph = None
vpr_capnp_schema = None

for f in src_files:
if f.file_type in ["verilogSource"]:
file_list.append(f.name)
Expand All @@ -246,6 +282,14 @@ def configure_vpr(self):
pins_constraints.append(f.name)
if f.file_type in ["xdc"]:
placement_constraints.append(f.name)
if f.file_type in ["RRGraph"]:
rr_graph = f.name
if f.file_type in ["VPRGrid"]:
vpr_grid = f.name
if f.file_type in ['capnp']:
vpr_capnp_schema = f.name

builddir = self.tool_options.get('builddir', 'build')

part = self.tool_options.get("part")
package = self.tool_options.get("package")
Expand Down Expand Up @@ -282,13 +326,36 @@ def configure_vpr(self):
sdc_opts = ['-s']+timing_constraints if timing_constraints else []
xdc_opts = ['-x']+placement_constraints if placement_constraints else []

fasm2bels = self.tool_options.get('fasm2bels', False)
dbroot = self.tool_options.get('dbroot', None)
clocks = self.tool_options.get('clocks', None)

if fasm2bels:
if any(v is None for v in [rr_graph, vpr_grid, dbroot]):
logger.error("When using fasm2bels, rr_graph, vpr_grid and database root must be provided")
tcl_params = {
'top': self.toplevel,
'part': partname,
'xdc': ' '.join(placement_constraints),
'clocks': clocks,
}

self.render_template('symbiflow-fasm2bels-tcl.j2',
'fasm2bels.tcl',
tcl_params)
self.render_template('vivado-sh.j2',
'vivado.sh',
dict())

seed = self.tool_options.get('seed', None)

commands = self.EdaCommands()
#Synthesis
targets = self.toplevel+'.eblif'
command = ['symbiflow_synth', '-t', self.toplevel]
command += ['-v'] + file_list
command += ['-d', bitstream_device]
command += ['-p' if vendor == 'xilinx' else '-P', partname]
command += ['-p', partname]
command += xdc_opts
commands.add(command, [targets], [])

Expand Down Expand Up @@ -328,6 +395,30 @@ def configure_vpr(self):
command += ['-b', targets]
commands.add(command, [targets], [depends])

if fasm2bels:
targets = self.toplevel+".bit.v"
command = ['python -m fasm2bels']
command += ['--db_root', dbroot+f'/{bitstream_device}']
command += ['--part', partname]
command += ['--bitread bitread']
command += ['--bit_file', self.toplevel+'.bit']
command += ['--fasm_file', self.toplevel+'.bit.fasm']
command += ['--eblif', self.toplevel+'.eblif']
command += ['--connection_database channels.db']
command += ['--rr_graph', rr_graph]
command += ['--route_file', self.toplevel+".route"]
command += ['--vpr_grid_map', vpr_grid]
command += ['--vpr_capnp_schema_dir', vpr_capnp_schema]
if len(pins_constraints) > 0:
command += [f"--pcf {' '.join(pins_constraints)}"]
command += ['--verilog_file', self.toplevel+".bit.v"]
command += ['--xdc_file', self.toplevel+".bit.xdc"]
command += ["&& rm channels.db"]
commands.add(command, [targets], [])
depends = targets
targets = "timing_summary.rpt"
command = ["bash vivado.sh"]
commands.add(command, [targets], [depends])
commands.set_default_target(targets)
commands.write(os.path.join(self.work_root, 'Makefile'))

Expand Down
16 changes: 16 additions & 0 deletions edalize/templates/symbiflow/symbiflow-fasm2bels-tcl.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
create_project -force -part {{ part }} design design

read_verilog {{ top }}.bit.v
synth_design -top top

read_xdc {{ top }}.bit.xdc

write_checkpoint -force design_{{ top }}_after_source.dcp

{% if clocks %}
{% for key, value in clocks.items() %}
create_clock -period {{ value }} [get_ports {{ key }}]
{% endfor %}
{% endif %}

report_timing_summary -datasheet -max_paths 10 -file timing_summary.rpt
7 changes: 7 additions & 0 deletions edalize/templates/symbiflow/vivado-sh.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/bin/bash

VIVADO_SETTINGS=`find /opt -wholename "*Xilinx/Vivado/*/settings64.sh" | sort | head -n 1`

source $VIVADO_SETTINGS

vivado -mode batch -source fasm2bels.tcl
Loading