From fe6937afec60b92dc27c224ac0f8f1450b43831f Mon Sep 17 00:00:00 2001 From: Markus Koch Date: Thu, 1 Aug 2024 07:53:32 +0200 Subject: [PATCH] Implement support for Lattice Diamond --- README.md | 1 + examples/hooks/diamond.py | 52 +++++++++++++ examples/projects/diamond.py | 51 +++++++++++++ examples/sources/cons/brevia2/clk.lpf | 3 + examples/sources/cons/brevia2/io.lpf | 4 + pyfpga/diamond.py | 30 ++++++++ pyfpga/factory.py | 2 + pyfpga/templates/diamond-prog.jinja | 19 +++++ pyfpga/templates/diamond.jinja | 105 ++++++++++++++++++++++++++ tests/mocks/diamondc | 38 ++++++++++ tests/test_tools.py | 7 ++ 11 files changed, 312 insertions(+) create mode 100644 examples/hooks/diamond.py create mode 100644 examples/projects/diamond.py create mode 100644 examples/sources/cons/brevia2/clk.lpf create mode 100644 examples/sources/cons/brevia2/io.lpf create mode 100644 pyfpga/diamond.py create mode 100644 pyfpga/templates/diamond-prog.jinja create mode 100644 pyfpga/templates/diamond.jinja create mode 100755 tests/mocks/diamondc diff --git a/README.md b/README.md index c3e12f21..28441fcf 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ # PyFPGA [![License](https://img.shields.io/badge/License-GPL--3.0-darkgreen?style=flat-square)](LICENSE) +![Diamond](https://img.shields.io/badge/Diamond-3.13-blue.svg?style=flat-square) ![ISE](https://img.shields.io/badge/ISE-14.7-blue.svg?style=flat-square) ![Libero](https://img.shields.io/badge/Libero--Soc-2024.1-blue.svg?style=flat-square) ![Quartus](https://img.shields.io/badge/Quartus--Prime-23.1-blue.svg?style=flat-square) diff --git a/examples/hooks/diamond.py b/examples/hooks/diamond.py new file mode 100644 index 00000000..eac463fa --- /dev/null +++ b/examples/hooks/diamond.py @@ -0,0 +1,52 @@ +"""Diamond example hooks.""" + +from pyfpga.diamond import Diamond + +prj = Diamond(odir='../build/diamond') + +hooks = { + "reports": """ +prj_run Map -task MapTrace -forceOne +prj_run PAR -task PARTrace -forceOne +prj_run PAR -task IOTiming -forceOne + """, + + "netlist_simulation": """ +prj_run Map -task MapVerilogSimFile +prj_run Map -task MapVHDLSimFile -forceOne +prj_run Export -task TimingSimFileVHD -forceOne +prj_run Export -task TimingSimFileVlg -forceOne +prj_run Export -task IBIS -forceOne + """, + + "progfile_ecp5u": """ +prj_run Export -task Promgen -forceOne + """, + + "progfile_machxo2": """ +prj_run Export -task Jedecgen -forceOne + """ +} + +prj.set_part('LFXP2-5E-5TN144C') + +prj.add_param('FREQ', '50000000') +prj.add_param('SECS', '1') + +prj.add_cons('../sources/cons/brevia2/clk.lpf', 'syn') +prj.add_cons('../sources/cons/brevia2/clk.lpf', 'par') +prj.add_cons('../sources/cons/brevia2/io.lpf', 'par') + +prj.add_include('../sources/vlog/include1') +prj.add_include('../sources/vlog/include2') +prj.add_vlog('../sources/vlog/*.v') + +prj.add_define('DEFINE1', '1') +prj.add_define('DEFINE2', '1') + +prj.set_top('Top') + +for hook_name, hook in hooks.items(): + prj.add_hook('postpar', hook) + +prj.make() diff --git a/examples/projects/diamond.py b/examples/projects/diamond.py new file mode 100644 index 00000000..9e602d5c --- /dev/null +++ b/examples/projects/diamond.py @@ -0,0 +1,51 @@ +"""Diamond examples.""" + +import argparse + +from pyfpga.diamond import Diamond + + +parser = argparse.ArgumentParser() +parser.add_argument( + '--board', choices=['brevia2'], default='brevia2' +) +parser.add_argument( + '--source', choices=['vlog', 'vhdl', 'slog'], default='vlog' +) +parser.add_argument( + '--action', choices=['make', 'prog', 'all'], default='make' +) +args = parser.parse_args() + +prj = Diamond(odir='../build/diamond') + +if args.board == 'brevia2': + prj.set_part('LFXP2-5E-5TN144C') + prj.add_param('FREQ', '50000000') + prj.add_cons('../sources/cons/brevia2/clk.lpf', 'syn') + prj.add_cons('../sources/cons/brevia2/clk.lpf', 'par') + prj.add_cons('../sources/cons/brevia2/io.lpf', 'par') +prj.add_param('SECS', '1') + +if args.source == 'vhdl': + prj.add_vhdl('../sources/vhdl/*.vhdl', 'blink_lib') + prj.add_vhdl('../sources/vhdl/top.vhdl') +if args.source == 'vlog': + prj.add_include('../sources/vlog/include1') + prj.add_include('../sources/vlog/include2') + prj.add_vlog('../sources/vlog/*.v') +if args.source == 'slog': + prj.add_include('../sources/slog/include1') + prj.add_include('../sources/slog/include2') + prj.add_slog('../sources/slog/*.sv') +if args.source in ['vlog', 'slog']: + prj.add_define('DEFINE1', '1') + prj.add_define('DEFINE2', '1') + +prj.set_top('Top') + +if args.action in ['make', 'all']: + prj.make() + +if args.action in ['prog', 'all']: + prj.prog() diff --git a/examples/sources/cons/brevia2/clk.lpf b/examples/sources/cons/brevia2/clk.lpf new file mode 100644 index 00000000..c0d5f789 --- /dev/null +++ b/examples/sources/cons/brevia2/clk.lpf @@ -0,0 +1,3 @@ +BLOCK RESETPATHS ; +BLOCK ASYNCPATHS ; +FREQUENCY NET "clk_i_c" 50.000000 MHz ; diff --git a/examples/sources/cons/brevia2/io.lpf b/examples/sources/cons/brevia2/io.lpf new file mode 100644 index 00000000..9933563f --- /dev/null +++ b/examples/sources/cons/brevia2/io.lpf @@ -0,0 +1,4 @@ +LOCATE COMP "clk_i" SITE "21" ; +IOBUF PORT "clk_i" IO_TYPE=LVCMOS33 ; +LOCATE COMP "led_o" SITE "37" ; +IOBUF PORT "led_o" IO_TYPE=LVCMOS33 ; diff --git a/pyfpga/diamond.py b/pyfpga/diamond.py new file mode 100644 index 00000000..a5b3e622 --- /dev/null +++ b/pyfpga/diamond.py @@ -0,0 +1,30 @@ +# +# Copyright (C) 2024 PyFPGA Project +# +# SPDX-License-Identifier: GPL-3.0-or-later +# + +""" +Implements support for Diamond. +""" + +import os +from pyfpga.project import Project + + +class Diamond(Project): + """Class to support Diamond projects.""" + + def _configure(self): + tool = 'diamond' + executable = 'pnmainc' if os.name == 'nt' else 'diamondc' + self.conf['tool'] = tool + self.conf['make_cmd'] = f'{executable} {tool}.tcl' + self.conf['make_ext'] = 'tcl' + self.conf['prog_bit'] = 'bit' + self.conf['prog_cmd'] = f'sh {tool}-prog.sh' + self.conf['prog_ext'] = 'sh' + + def _make_custom(self): + if 'part' not in self.data: + self.data['part'] = 'LFXP2-5E-5TN144C' diff --git a/pyfpga/factory.py b/pyfpga/factory.py index 80782183..753ce84d 100644 --- a/pyfpga/factory.py +++ b/pyfpga/factory.py @@ -10,6 +10,7 @@ # pylint: disable=too-few-public-methods +from pyfpga.diamond import Diamond from pyfpga.ise import Ise from pyfpga.libero import Libero from pyfpga.openflow import Openflow @@ -18,6 +19,7 @@ TOOLS = { + 'diamond': Diamond, 'ise': Ise, 'libero': Libero, 'openflow': Openflow, diff --git a/pyfpga/templates/diamond-prog.jinja b/pyfpga/templates/diamond-prog.jinja new file mode 100644 index 00000000..e1a1e57c --- /dev/null +++ b/pyfpga/templates/diamond-prog.jinja @@ -0,0 +1,19 @@ +{# +# Copyright (C) 2024 PyFPGA Project +# +# SPDX-License-Identifier: GPL-3.0-or-later +# +#} + +if [ "$DIAMOND_XCF" == "" ]; then + DIAMOND_XCF=impl1/impl1.xcf +fi + +if [ -f "$DIAMOND_XCF" ]; then + pgrcmd -infile $DIAMOND_XCF +else + echo "ERROR: Automatic programming with Diamond is not yet supported." + echo " Please create the `realpath $DIAMOND_XCF` file manually and rerun the prog command." + echo " Hint: You can change the location of the XCF file by setting the DIAMOND_XCF environment variable." + exit 1 +fi diff --git a/pyfpga/templates/diamond.jinja b/pyfpga/templates/diamond.jinja new file mode 100644 index 00000000..3a191524 --- /dev/null +++ b/pyfpga/templates/diamond.jinja @@ -0,0 +1,105 @@ +{# +# +# Copyright (C) 2015-2024 PyFPGA Project +# +# SPDX-License-Identifier: GPL-3.0-or-later +# +#} + +{% if 'cfg' in steps %}# Project configuration ------------------------------------------------------- + +prj_project new -name {{ project }} -dev {{ part }} + +# For now, let's enforce Synplify as LSE (the default) has broken top level generic handling +prj_syn set synplify + +{% if hooks %}{{ hooks.precfg | join('\n') }}{% endif %} + +{% if files %}# Files inclusion +{% for name, attr in files.items() %} +prj_src add {% if 'lib' in attr %}-work {{ attr.lib }}{% else %}{% endif %} {{ name }} +{% endfor %} +{% endif %} + +{% if constraints %} +# Constraints inclusion +# Diamond only supports one constraints file, so we need to combine them into the default diamond.lpf. +# We can't just do `prj_src add ` multiple times. +set fileId [open diamond.lpf "w"] +{% for name, attr in constraints.items() %} +set fp [open "{{ name }}" r] +set file_data [read $fp] +close $fp +puts -nonewline $fileId $file_data +{% endfor %} +close $fileId +{% endif %} + +{% if top %}# Top-level specification +prj_impl option top "{{ top }}" +{% endif %} + +{% if includes %}# Verilog Includes +{% for include in includes %} +prj_impl option -append {include path} {{ "{"+include+"}" }} +{% endfor %} +{% endif %} + +{% if defines %}# Verilog Defines +{% for key, value in defines.items() %} +prj_impl option -append VERILOG_DIRECTIVES {{ key }}={{ value }} +{% endfor %} +{% endif %} + +{% if params %}# Verilog Parameters / VHDL Generics +{% for key, value in params.items() %} +prj_impl option -append HDL_PARAM {{ key }}={{ value }} +{% endfor %} +{% endif %} + +{% if hooks %}{{ hooks.postcfg | join('\n') }}{% endif %} + +prj_project save +prj_project close + +{% endif %} + +{% if 'syn' in steps or 'par' in steps or 'bit' in steps %}# Design flow ----------------------------------------------------------------- + +prj_project open {{ project }}.ldf + +{% if 'syn' in steps %}# Synthesis + +{% if hooks %}{{ hooks.presyn | join('\n') }}{% endif %} + +prj_run Synthesis -forceOne + +{% if hooks %}{{ hooks.postsyn | join('\n') }}{% endif %} + +{% endif %} + +{% if 'par' in steps %} # Translate, Map, and Place and Route +{% if hooks %}{{ hooks.prepar | join('\n') }}{% endif %} + +prj_run Translate -forceOne +prj_run Map -forceOne +prj_run PAR -forceOne + +{% if hooks %}{{ hooks.postpar | join('\n') }}{% endif %} + +{% endif %} + +{% if 'bit' in steps %}# Bitstream generation + +{% if hooks %}{{ hooks.prebit | join('\n') }}{% endif %} + +prj_run Export -task Bitgen -forceOne + +{% if hooks %}{{ hooks.postbit | join('\n') }}{% endif %} + +{% endif %} + +prj_project save +prj_project close + +{% endif %} diff --git a/tests/mocks/diamondc b/tests/mocks/diamondc new file mode 100755 index 00000000..1beac9c1 --- /dev/null +++ b/tests/mocks/diamondc @@ -0,0 +1,38 @@ +#!/usr/bin/env python3 + +# +# Copyright (C) 2024 PyFPGA Project +# +# SPDX-License-Identifier: GPL-3.0-or-later +# + +import argparse +import subprocess +import sys + + +parser = argparse.ArgumentParser() + +parser.add_argument('source') + +args = parser.parse_args() + +tool = parser.prog + +tcl = f''' +proc unknown args {{ }} + +source {args.source} +''' + +with open(f'{tool}-mock.tcl', 'w', encoding='utf-8') as file: + file.write(tcl) + +subprocess.run( + f'tclsh {tool}-mock.tcl', + shell=True, + check=True, + universal_newlines=True +) + +print(f'INFO:the {tool.upper()} mock has been executed') diff --git a/tests/test_tools.py b/tests/test_tools.py index 45f972ed..ffaa7e5b 100644 --- a/tests/test_tools.py +++ b/tests/test_tools.py @@ -4,6 +4,13 @@ tdir = Path(__file__).parent.resolve() +def test_diamond(): + tool = 'diamond' + generate(tool, 'PARTNAME') + base = f'results/{tool}/{tool}' + assert Path(f'{base}.tcl').exists(), 'file not found' + + def test_ise(): tool = 'ise' generate(tool, 'DEVICE-PACKAGE-SPEED')