From ef4ae95cd67c132253200d4209f2bda4092467d9 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sun, 24 Nov 2024 22:59:15 -0300 Subject: [PATCH] Improve bitstream handling --- pyfpga/diamond.py | 2 +- pyfpga/ise.py | 2 +- pyfpga/libero.py | 2 +- pyfpga/openflow.py | 6 +++++- pyfpga/project.py | 10 +++++++--- pyfpga/quartus.py | 2 +- pyfpga/templates/openflow-prog.jinja | 4 ++-- pyfpga/vivado.py | 2 +- tests/mocks/FPExpress | 25 +++++++++++++++++++++++++ tests/mocks/libero | 13 ++++++++++++- tests/mocks/quartus_sh | 9 +++++++++ tests/mocks/vivado | 9 +++++++++ tests/mocks/xtclsh | 9 +++++++++ tests/test_tools.py | 8 ++++++++ 14 files changed, 91 insertions(+), 12 deletions(-) create mode 100755 tests/mocks/FPExpress diff --git a/pyfpga/diamond.py b/pyfpga/diamond.py index a5b3e622..01226a6d 100644 --- a/pyfpga/diamond.py +++ b/pyfpga/diamond.py @@ -21,7 +21,7 @@ def _configure(self): 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_bit'] = ['bit'] self.conf['prog_cmd'] = f'sh {tool}-prog.sh' self.conf['prog_ext'] = 'sh' diff --git a/pyfpga/ise.py b/pyfpga/ise.py index 5ca52f8a..048b140b 100644 --- a/pyfpga/ise.py +++ b/pyfpga/ise.py @@ -21,7 +21,7 @@ def _configure(self): self.conf['tool'] = tool self.conf['make_cmd'] = f'xtclsh {tool}.tcl' self.conf['make_ext'] = 'tcl' - self.conf['prog_bit'] = 'bit' + self.conf['prog_bit'] = ['bit'] self.conf['prog_cmd'] = f'impact -batch {tool}-prog.tcl' self.conf['prog_ext'] = 'tcl' diff --git a/pyfpga/libero.py b/pyfpga/libero.py index b0661d14..37c56650 100644 --- a/pyfpga/libero.py +++ b/pyfpga/libero.py @@ -21,7 +21,7 @@ def _configure(self): self.conf['tool'] = tool self.conf['make_cmd'] = f'{tool} SCRIPT:{tool}.tcl' self.conf['make_ext'] = 'tcl' - self.conf['prog_bit'] = 'ppd' + self.conf['prog_bit'] = ['ppd', 'stp', 'bit', 'jed'] self.conf['prog_cmd'] = f'FPExpress SCRIPT:{tool}-prog.tcl' self.conf['prog_ext'] = 'tcl' diff --git a/pyfpga/openflow.py b/pyfpga/openflow.py index 25ab9fc4..299abc7c 100644 --- a/pyfpga/openflow.py +++ b/pyfpga/openflow.py @@ -19,7 +19,7 @@ def _configure(self): self.conf['tool'] = tool self.conf['make_cmd'] = f'bash {tool}.sh' self.conf['make_ext'] = 'sh' - self.conf['prog_bit'] = 'bit' + self.conf['prog_bit'] = ['svf', 'bit'] self.conf['prog_cmd'] = f'bash {tool}-prog.sh' self.conf['prog_ext'] = 'sh' @@ -29,6 +29,10 @@ def _make_custom(self): self.data['device'] = info['device'] self.data['package'] = info['package'] + def _prog_custom(self): + info = get_info(self.data.get('part', 'hx8k-ct256')) + self.data['family'] = info['family'] + def get_info(part): """Get info about the FPGA part. diff --git a/pyfpga/project.py b/pyfpga/project.py index b5d947ac..75eaec47 100644 --- a/pyfpga/project.py +++ b/pyfpga/project.py @@ -253,10 +253,14 @@ def prog(self, bitstream=None, position=1): raise ValueError('Invalid position.') self.logger.info('Programming') if not bitstream: - bitstream = f'{self.data["project"]}.{self.conf["prog_bit"]}' + for ext in self.conf['prog_bit']: + candidate = Path(self.odir) / f'{self.data["project"]}.{ext}' + if candidate.exists(): + bitstream = candidate.resolve() + break else: - bitstream = Path(bitstream).resolve().as_posix() - if not os.path.exists(bitstream): + bitstream = Path(bitstream).resolve() + if not bitstream or not bitstream.exists(): raise FileNotFoundError(bitstream) self.data['bitstream'] = bitstream self._prog_custom() diff --git a/pyfpga/quartus.py b/pyfpga/quartus.py index 8cd8f66a..747c6628 100644 --- a/pyfpga/quartus.py +++ b/pyfpga/quartus.py @@ -19,7 +19,7 @@ def _configure(self): self.conf['tool'] = tool self.conf['make_cmd'] = f'quartus_sh --script {tool}.tcl' self.conf['make_ext'] = 'tcl' - self.conf['prog_bit'] = 'sof' + self.conf['prog_bit'] = ['sof', 'pof'] self.conf['prog_cmd'] = f'bash {tool}-prog.tcl' self.conf['prog_ext'] = 'tcl' diff --git a/pyfpga/templates/openflow-prog.jinja b/pyfpga/templates/openflow-prog.jinja index 5055b505..b26de159 100644 --- a/pyfpga/templates/openflow-prog.jinja +++ b/pyfpga/templates/openflow-prog.jinja @@ -11,7 +11,7 @@ set -e DOCKER="docker run --user $(id -u):$(id -g) --rm -v $HOME:$HOME -w $PWD" {% if family == 'ecp5' %} -$DOCKER --device /dev/bus/usb hdlc/prog openocd -f /usr/share/trellis/misc/openocd/ecp5-evn.cfg -c "transport select jtag; init; svf {{ project }}.svf; exit" +$DOCKER --device /dev/bus/usb hdlc/prog openocd -f /usr/share/trellis/misc/openocd/ecp5-evn.cfg -c "transport select jtag; init; svf {{ bitstream }}; exit" {% else %} -$DOCKER --device /dev/bus/usb hdlc/prog iceprog {{ project }}.bit +$DOCKER --device /dev/bus/usb hdlc/prog iceprog {{ bitstream }} {% endif %} diff --git a/pyfpga/vivado.py b/pyfpga/vivado.py index 0391a17f..2b04819d 100644 --- a/pyfpga/vivado.py +++ b/pyfpga/vivado.py @@ -20,7 +20,7 @@ def _configure(self): self.conf['tool'] = tool self.conf['make_cmd'] = f'{command} {tool}.tcl' self.conf['make_ext'] = 'tcl' - self.conf['prog_bit'] = 'bit' + self.conf['prog_bit'] = ['bit'] self.conf['prog_cmd'] = f'{command} {tool}-prog.tcl' self.conf['prog_ext'] = 'tcl' diff --git a/tests/mocks/FPExpress b/tests/mocks/FPExpress new file mode 100755 index 00000000..8947406b --- /dev/null +++ b/tests/mocks/FPExpress @@ -0,0 +1,25 @@ +#!/usr/bin/env python3 + +# +# Copyright (C) 2024 PyFPGA Project +# +# SPDX-License-Identifier: GPL-3.0-or-later +# + +import argparse +import sys + + +parser = argparse.ArgumentParser() + +parser.add_argument('source') + +args = parser.parse_args() + +tool = parser.prog + +if not args.source.startswith("SCRIPT:", 0): + print('ERROR:the parameter should start width "SCRIPT:"') + sys.exit(1) + +print(f'INFO:the {tool.upper()} mock has been executed') diff --git a/tests/mocks/libero b/tests/mocks/libero index 510b9926..94c8dd05 100755 --- a/tests/mocks/libero +++ b/tests/mocks/libero @@ -7,6 +7,7 @@ # import argparse +import re import subprocess import sys @@ -23,10 +24,12 @@ if not args.source.startswith("SCRIPT:", 0): print('ERROR:the parameter should start width "SCRIPT:"') sys.exit(1) +args.source = args.source.replace('SCRIPT:', '') + tcl = f''' proc unknown args {{ }} -source {args.source.replace('SCRIPT:', '')} +source {args.source} ''' with open(f'{tool}-mock.tcl', 'w', encoding='utf-8') as file: @@ -39,4 +42,12 @@ subprocess.run( universal_newlines=True ) +pattern = r'new_project\s+-name\s+(\S+)\s' +with open(args.source, 'r', encoding='utf-8') as file: + match = re.search(pattern, file.read()) + if match: + project = match.group(1) + with open(f'{project}.ppd', 'w', encoding='utf-8') as file: + pass + print(f'INFO:the {tool.upper()} mock has been executed') diff --git a/tests/mocks/quartus_sh b/tests/mocks/quartus_sh index 7f437145..38f6507d 100755 --- a/tests/mocks/quartus_sh +++ b/tests/mocks/quartus_sh @@ -8,6 +8,7 @@ import argparse import os +import re import subprocess @@ -41,4 +42,12 @@ subprocess.run( universal_newlines=True ) +pattern = r'project_new\s+(\S+)\s' +with open(args.script, 'r', encoding='utf-8') as file: + match = re.search(pattern, file.read()) + if match: + project = match.group(1) + with open(f'{project}.sof', 'w', encoding='utf-8') as file: + pass + print(f'INFO:the {tool.upper()} mock has been executed') diff --git a/tests/mocks/vivado b/tests/mocks/vivado index 3bc95381..2f5cbe69 100755 --- a/tests/mocks/vivado +++ b/tests/mocks/vivado @@ -7,6 +7,7 @@ # import argparse +import re import subprocess @@ -70,4 +71,12 @@ subprocess.run( universal_newlines=True ) +pattern = r'create_project\s+-force\s+(\S+)' +with open(args.source, 'r', encoding='utf-8') as file: + match = re.search(pattern, file.read()) + if match: + project = match.group(1) + with open(f'{project}.bit', 'w', encoding='utf-8') as file: + pass + print(f'INFO:the {tool.upper()} mock has been executed') diff --git a/tests/mocks/xtclsh b/tests/mocks/xtclsh index 22f66a77..4064f790 100755 --- a/tests/mocks/xtclsh +++ b/tests/mocks/xtclsh @@ -7,6 +7,7 @@ # import argparse +import re import subprocess @@ -34,4 +35,12 @@ subprocess.run( universal_newlines=True ) +pattern = r'project\s+new\s+(\S+)\.xise' +with open(args.source, 'r', encoding='utf-8') as file: + match = re.search(pattern, file.read()) + if match: + project = match.group(1) + with open(f'{project}.bit', 'w', encoding='utf-8') as file: + pass + print(f'INFO:the {tool.upper()} mock has been executed') diff --git a/tests/test_tools.py b/tests/test_tools.py index 98f00055..a2b3e489 100644 --- a/tests/test_tools.py +++ b/tests/test_tools.py @@ -9,6 +9,7 @@ def test_diamond(): generate(tool, 'PARTNAME') base = f'results/{tool}/{tool}' assert Path(f'{base}.tcl').exists(), 'file not found' + assert Path(f'{base}-prog.sh').exists(), 'file not found' def test_ise(): @@ -24,6 +25,7 @@ def test_libero(): generate(tool, 'DEVICE-PACKAGE-SPEED') base = f'results/{tool}/{tool}' assert Path(f'{base}.tcl').exists(), 'file not found' + assert Path(f'{base}-prog.tcl').exists(), 'file not found' def test_openflow(): @@ -87,6 +89,12 @@ def generate(tool, part): prj.make() except RuntimeError: pass + if tool == 'libero': + open(f'results/{tool}/{tool}.ppd', 'w').close() + elif tool == 'quartus': + open(f'results/{tool}/{tool}.sof', 'w').close() + else: + open(f'results/{tool}/{tool}.bit', 'w').close() try: prj.prog() except RuntimeError: