From 3d9b185a98fca180c97e3dc53c3f3c4ae322e60a Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sun, 15 May 2022 20:38:50 -0300 Subject: [PATCH 001/254] Updated README.md and modified version (0.3.0-dev) --- README.md | 30 +++++++++++++++--------------- fpga/__init__.py | 2 +- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 8222587b..eb969487 100644 --- a/README.md +++ b/README.md @@ -1,30 +1,30 @@ # PyFPGA [![License](https://img.shields.io/badge/License-GPL--3.0-darkgreen?style=flat-square)](LICENSE) ![GDHL](https://img.shields.io/badge/GHDL-last-brightgreen.svg?style=flat-square) -![icestorm](https://img.shields.io/badge/icestorm-last-brightgreen.svg?style=flat-square) +![Yosys](https://img.shields.io/badge/Yosys-last-brightgreen.svg?style=flat-square) ![nextpnr](https://img.shields.io/badge/nextpnr-last-brightgreen.svg?style=flat-square) +![icestorm](https://img.shields.io/badge/icestorm-last-brightgreen.svg?style=flat-square) ![prjtrellis](https://img.shields.io/badge/prjtrellis-last-brightgreen.svg?style=flat-square) -![Yosys](https://img.shields.io/badge/Yosys-last-brightgreen.svg?style=flat-square) +![Vivado](https://img.shields.io/badge/Vivado-2019.2-blue.svg?style=flat-square) +![Quartus](https://img.shields.io/badge/Quartus--Prime-19.1-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-12.2-blue.svg?style=flat-square) -![Quartus](https://img.shields.io/badge/Quartus--Prime-19.1-blue.svg?style=flat-square) -![Vivado](https://img.shields.io/badge/Vivado-2019.2-blue.svg?style=flat-square) -PyFPGA is a **Python** Class for **vendor-independent FPGA development**. -It allows using **a single project file** and **programmatically** executing -**synthesis**, **implementation**, generation of **bitstream** and/or -**transference** to supported boards. +> **WARNING:** (2022-05-15) PyFPGA is in the process of being strongly rewritten/simplified. +> Most changes are internal, but the API (`Project` class) will change. -- The workflow is command-line centric. -- It's friendly with *Version Control Systems* and *Continuous Integration* (CI). -- Allows reproducibility and repeatability. -- Consumes fewer system resources than GUI based workflows. +PyFPGA is a **Python Package** for **vendor-agnostic** FPGA development. +It provides a **Class** which allows the programmatically execution of **synthesis**, +**place and route**, **bitstream generation** and/or **programming** of FPGA devices. +Additionally, a set of **command-line helpers** are provided for quick and simple runs. -Create your custom FPGA Tool using a workflow tailored to your needs! +Features: +* It's *Version Control Systems* and *Continuous Integration* friendly. +* Allows reproducibility and repeatability. +* Consumes fewer system resources than GUI based workflows. -> **WARNING:** (2022-05-15) PyFPGA is in the process of being strongly rewritten/simplified. -> Most changes are internal, but the API (`Project` class) will change. +With PyFPGA you can create your custom FPGA tool using a workflow tailored to your needs! ## Usage diff --git a/fpga/__init__.py b/fpga/__init__.py index ffaf5fbc..bd3bc779 100644 --- a/fpga/__init__.py +++ b/fpga/__init__.py @@ -1,5 +1,5 @@ """PyFPGA""" -__version__ = '0.2.0' +__version__ = '0.3.0-dev' from fpga.project import Project From c18b44b6b89eafaaceb1aa985ef86728d1b9aa31 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sun, 15 May 2022 20:40:25 -0300 Subject: [PATCH 002/254] resources: submodule added --- .gitmodules | 3 +++ Makefile | 3 +++ resources | 1 + 3 files changed, 7 insertions(+) create mode 100644 .gitmodules create mode 160000 resources diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..362bc8ae --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "resources"] + path = resources + url = https://github.com/PyFPGA/resources diff --git a/Makefile b/Makefile index 278baebc..8f1d8dc1 100644 --- a/Makefile +++ b/Makefile @@ -9,3 +9,6 @@ check: clean: py3clean . rm -fr build .pytest_cache + +submodule: + git submodule update --init diff --git a/resources b/resources new file mode 160000 index 00000000..6a58bba3 --- /dev/null +++ b/resources @@ -0,0 +1 @@ +Subproject commit 6a58bba3b1c36d238d1111e910db02f0b284aef7 From 1e8a5468811c4fc265e8bcb8e573a0cc965a2a7f Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sun, 22 May 2022 21:28:21 -0300 Subject: [PATCH 003/254] test: added mocks (for vendors tools) --- .github/workflows/test.yml | 2 +- examples/Makefile | 6 +++++- examples/ghdl/Makefile | 2 +- examples/ghdl/ghdl.py | 5 +---- examples/hooks/Makefile | 2 +- examples/hooks/strategies.py | 5 +---- examples/ise/ise.py | 16 +++++----------- examples/libero/Makefile | 2 +- examples/libero/libero.py | 5 +---- examples/misc/Makefile | 2 +- examples/misc/capture.py | 16 +++++----------- examples/multi/Makefile | 2 +- examples/multi/memory.py | 5 +---- examples/multi/parameters.py | 5 +---- examples/multi/projects.py | 5 +---- examples/multi/verilog.py | 5 +---- examples/multi/vhdl.py | 5 +---- examples/openflow/icestorm.py | 10 ++-------- examples/openflow/prjtrellis.py | 10 ++-------- examples/quartus/Makefile | 2 +- examples/quartus/quartus.py | 10 ++-------- examples/vivado/Makefile | 2 +- examples/vivado/design.py | 5 +---- examples/vivado/vivado.py | 10 ++-------- examples/yosys/Makefile | 2 +- examples/yosys/ise.py | 10 ++-------- examples/yosys/vivado.py | 10 ++-------- examples/yosys/yosys.py | 5 +---- fpga/project.py | 16 +++++++--------- test/mocks/impact | 28 ++++++++++++++++++++++++++++ test/mocks/libero | 33 +++++++++++++++++++++++++++++++++ test/mocks/quartus | 27 +++++++++++++++++++++++++++ test/mocks/quartus_pgm | 29 +++++++++++++++++++++++++++++ test/mocks/quartus_sh | 27 +++++++++++++++++++++++++++ test/mocks/vivado | 30 ++++++++++++++++++++++++++++++ test/mocks/xtclsh | 27 +++++++++++++++++++++++++++ 36 files changed, 254 insertions(+), 129 deletions(-) create mode 100755 test/mocks/impact create mode 100755 test/mocks/libero create mode 100644 test/mocks/quartus create mode 100755 test/mocks/quartus_pgm create mode 100755 test/mocks/quartus_sh create mode 100755 test/mocks/vivado create mode 100755 test/mocks/xtclsh diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 96b267ed..2e5564e1 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -29,4 +29,4 @@ jobs: - name: Test run: | pytest - make -C examples + cd examples; make MOCKS=1 diff --git a/examples/Makefile b/examples/Makefile index 77edba5c..7bfe3fc4 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -1,7 +1,11 @@ #!/usr/bin/make +ifdef MOCKS +export PATH := $(PATH):$(PWD)/../test/mocks +$(info INFO: using MOCKS for the vendor EDA tools) +endif DIRS=$(wildcard */) all: - @$(foreach DIR, $(DIRS), make -C $(DIR);) + @$(foreach DIR, $(DIRS), make -C $(DIR) || exit;) diff --git a/examples/ghdl/Makefile b/examples/ghdl/Makefile index cd77fa54..f166a9f9 100644 --- a/examples/ghdl/Makefile +++ b/examples/ghdl/Makefile @@ -3,4 +3,4 @@ SCRIPTS = $(wildcard *.py) all: - @$(foreach SCRIPT, $(SCRIPTS), python3 $(SCRIPT);) + @$(foreach SCRIPT, $(SCRIPTS), python3 $(SCRIPT) || exit;) diff --git a/examples/ghdl/ghdl.py b/examples/ghdl/ghdl.py index 9962d1bf..da2ef275 100644 --- a/examples/ghdl/ghdl.py +++ b/examples/ghdl/ghdl.py @@ -14,7 +14,4 @@ prj.add_files('../../hdl/top.vhdl') prj.set_top('Top') -try: - prj.generate() -except RuntimeError: - print('ERROR:generate:Docker not found') +prj.generate() diff --git a/examples/hooks/Makefile b/examples/hooks/Makefile index cd77fa54..f166a9f9 100644 --- a/examples/hooks/Makefile +++ b/examples/hooks/Makefile @@ -3,4 +3,4 @@ SCRIPTS = $(wildcard *.py) all: - @$(foreach SCRIPT, $(SCRIPTS), python3 $(SCRIPT);) + @$(foreach SCRIPT, $(SCRIPTS), python3 $(SCRIPT) || exit;) diff --git a/examples/hooks/strategies.py b/examples/hooks/strategies.py index 89954eba..018b60eb 100644 --- a/examples/hooks/strategies.py +++ b/examples/hooks/strategies.py @@ -105,7 +105,4 @@ 'project' ) PRJ.add_hook(commands[tool][strategy], 'project') - try: - PRJ.generate(to_task='syn') - except RuntimeError: - print('ERROR:generate:{} not found'.format(tool)) + PRJ.generate(to_task='syn') diff --git a/examples/ise/ise.py b/examples/ise/ise.py index 14a95ae0..c4f90b41 100644 --- a/examples/ise/ise.py +++ b/examples/ise/ise.py @@ -37,16 +37,10 @@ prj.add_files(BOARDS[args.board][2]) if args.action in ['generate', 'all']: - try: - prj.generate() - except RuntimeError: - print('ERROR:generate:ISE not found') + prj.generate() if args.action in ['transfer', 'all']: - try: - prj.transfer('fpga') - # prj.transfer('detect') - # prj.transfer('unlock') - # prj.transfer('spi', 1, 'N25Q128', 4) - except RuntimeError: - print('ERROR:transfer:ISE not found') + prj.transfer('fpga') + # prj.transfer('detect') + # prj.transfer('unlock') + # prj.transfer('spi', 1, 'N25Q128', 4) diff --git a/examples/libero/Makefile b/examples/libero/Makefile index cd77fa54..f166a9f9 100644 --- a/examples/libero/Makefile +++ b/examples/libero/Makefile @@ -3,4 +3,4 @@ SCRIPTS = $(wildcard *.py) all: - @$(foreach SCRIPT, $(SCRIPTS), python3 $(SCRIPT);) + @$(foreach SCRIPT, $(SCRIPTS), python3 $(SCRIPT) || exit;) diff --git a/examples/libero/libero.py b/examples/libero/libero.py index 7f0b39bb..32ef38a5 100644 --- a/examples/libero/libero.py +++ b/examples/libero/libero.py @@ -26,10 +26,7 @@ prj.add_files('mkr.sdc') if args.action in ['generate', 'all']: - try: - prj.generate() - except RuntimeError: - print('ERROR:generate:Libero not found') + prj.generate() if args.action in ['transfer', 'all']: print('ERROR:transfer:Not yet implemented') diff --git a/examples/misc/Makefile b/examples/misc/Makefile index cd77fa54..f166a9f9 100644 --- a/examples/misc/Makefile +++ b/examples/misc/Makefile @@ -3,4 +3,4 @@ SCRIPTS = $(wildcard *.py) all: - @$(foreach SCRIPT, $(SCRIPTS), python3 $(SCRIPT);) + @$(foreach SCRIPT, $(SCRIPTS), python3 $(SCRIPT) || exit;) diff --git a/examples/misc/capture.py b/examples/misc/capture.py index 8caa5a56..4284b212 100644 --- a/examples/misc/capture.py +++ b/examples/misc/capture.py @@ -16,14 +16,8 @@ PRJ.add_files('../../hdl/*.vhdl', library='examples') PRJ.set_top('Top') -try: - output = PRJ.generate(to_task='syn', capture=True) - print(output) -except RuntimeError: - print('ERROR:generate:ISE not found') - -try: - output = PRJ.transfer(devtype='detect', capture=True) - print(output) -except RuntimeError: - print('ERROR:transfer:ISE not found') +output = PRJ.generate(to_task='syn', capture=True) +print(output) + +output = PRJ.transfer(devtype='detect', capture=True) +print(output) diff --git a/examples/multi/Makefile b/examples/multi/Makefile index cd77fa54..f166a9f9 100644 --- a/examples/multi/Makefile +++ b/examples/multi/Makefile @@ -3,4 +3,4 @@ SCRIPTS = $(wildcard *.py) all: - @$(foreach SCRIPT, $(SCRIPTS), python3 $(SCRIPT);) + @$(foreach SCRIPT, $(SCRIPTS), python3 $(SCRIPT) || exit;) diff --git a/examples/multi/memory.py b/examples/multi/memory.py index fe67ee57..bbec7300 100644 --- a/examples/multi/memory.py +++ b/examples/multi/memory.py @@ -21,7 +21,4 @@ else: PRJ.add_files('../../hdl/ram.v') PRJ.set_top('ram') - try: - PRJ.generate(to_task='syn') - except RuntimeError: - print('ERROR:generate:{} not found'.format(tool)) + PRJ.generate(to_task='syn') diff --git a/examples/multi/parameters.py b/examples/multi/parameters.py index 45d73ad9..30012888 100644 --- a/examples/multi/parameters.py +++ b/examples/multi/parameters.py @@ -41,7 +41,4 @@ # else: # PRJ.add_files('../../hdl/fakes/parameters.v') # PRJ.set_top('Params') - try: - PRJ.generate(to_task='syn') - except RuntimeError: - print('ERROR:generate:{} not found'.format(tool)) + PRJ.generate(to_task='syn') diff --git a/examples/multi/projects.py b/examples/multi/projects.py index 97cc83ec..dd7bb7ce 100644 --- a/examples/multi/projects.py +++ b/examples/multi/projects.py @@ -57,7 +57,4 @@ } for prj in PROJECTS: - try: - PROJECTS[prj].generate('syn') - except RuntimeError: - print('ERROR:generate:tool not found') + PROJECTS[prj].generate('syn') diff --git a/examples/multi/verilog.py b/examples/multi/verilog.py index bfce523e..a751f566 100644 --- a/examples/multi/verilog.py +++ b/examples/multi/verilog.py @@ -21,7 +21,4 @@ PRJ.add_files('../../hdl/blinking.v') PRJ.add_files('../../hdl/top.v') PRJ.set_top('Top') - try: - PRJ.generate(to_task='syn') - except RuntimeError: - print('ERROR:generate:{} not found'.format(tool)) + PRJ.generate(to_task='syn') diff --git a/examples/multi/vhdl.py b/examples/multi/vhdl.py index b2563e41..7c6ee7d8 100644 --- a/examples/multi/vhdl.py +++ b/examples/multi/vhdl.py @@ -18,7 +18,4 @@ PRJ.add_files('../../hdl/examples_pkg.vhdl', library='examples') PRJ.add_files('../../hdl/top.vhdl') PRJ.set_top('Top') - try: - PRJ.generate(to_task='syn') - except RuntimeError: - print('ERROR:generate:{} not found'.format(tool)) + PRJ.generate(to_task='syn') diff --git a/examples/openflow/icestorm.py b/examples/openflow/icestorm.py index 05d5f96b..77a0e529 100644 --- a/examples/openflow/icestorm.py +++ b/examples/openflow/icestorm.py @@ -44,13 +44,7 @@ prj.set_top('Top') if args.action in ['generate', 'all']: - try: - prj.generate() - except RuntimeError: - print('ERROR:generate:Docker not found') + prj.generate() if args.action in ['transfer', 'all']: - try: - prj.transfer() - except RuntimeError: - print('ERROR:transfer:Docker not found') + prj.transfer() diff --git a/examples/openflow/prjtrellis.py b/examples/openflow/prjtrellis.py index 59eb8311..86255567 100644 --- a/examples/openflow/prjtrellis.py +++ b/examples/openflow/prjtrellis.py @@ -44,13 +44,7 @@ prj.set_top('Top') if args.action in ['generate', 'all']: - try: - prj.generate() - except RuntimeError: - print('ERROR:generate:Docker not found') + prj.generate() if args.action in ['transfer', 'all']: - try: - prj.transfer() - except RuntimeError: - print('ERROR:transfer:Docker not found') + prj.transfer() diff --git a/examples/quartus/Makefile b/examples/quartus/Makefile index cd77fa54..f166a9f9 100644 --- a/examples/quartus/Makefile +++ b/examples/quartus/Makefile @@ -3,4 +3,4 @@ SCRIPTS = $(wildcard *.py) all: - @$(foreach SCRIPT, $(SCRIPTS), python3 $(SCRIPT);) + @$(foreach SCRIPT, $(SCRIPTS), python3 $(SCRIPT) || exit;) diff --git a/examples/quartus/quartus.py b/examples/quartus/quartus.py index 809036b0..6516fcd0 100644 --- a/examples/quartus/quartus.py +++ b/examples/quartus/quartus.py @@ -26,13 +26,7 @@ prj.add_files('de10nano.tcl') if args.action in ['generate', 'all']: - try: - prj.generate() - except RuntimeError: - print('ERROR:generate:Quartus not found') + prj.generate() if args.action in ['transfer', 'all']: - try: - prj.transfer('fpga', 2) - except RuntimeError: - print('ERROR:transfer:Quartus not found') + prj.transfer('fpga', 2) diff --git a/examples/vivado/Makefile b/examples/vivado/Makefile index cd77fa54..f166a9f9 100644 --- a/examples/vivado/Makefile +++ b/examples/vivado/Makefile @@ -3,4 +3,4 @@ SCRIPTS = $(wildcard *.py) all: - @$(foreach SCRIPT, $(SCRIPTS), python3 $(SCRIPT);) + @$(foreach SCRIPT, $(SCRIPTS), python3 $(SCRIPT) || exit;) diff --git a/examples/vivado/design.py b/examples/vivado/design.py index 4ea1cf31..7ac4eef3 100644 --- a/examples/vivado/design.py +++ b/examples/vivado/design.py @@ -30,7 +30,4 @@ prj.add_hook(export, 'postbit') -try: - prj.generate() -except Exception as e: - logging.warning('{} ({})'.format(type(e).__name__, e)) +prj.generate() diff --git a/examples/vivado/vivado.py b/examples/vivado/vivado.py index 5b00bd1a..e28c861b 100644 --- a/examples/vivado/vivado.py +++ b/examples/vivado/vivado.py @@ -24,13 +24,7 @@ prj.set_top('Blinking') if args.action in ['generate', 'all']: - try: - prj.generate() - except RuntimeError: - print('ERROR:generate:Vivado not found') + prj.generate() if args.action in ['transfer', 'all']: - try: - prj.transfer('fpga') - except RuntimeError: - print('ERROR:transfer:Vivado not found') + prj.transfer('fpga') diff --git a/examples/yosys/Makefile b/examples/yosys/Makefile index 9546d682..f07825c7 100644 --- a/examples/yosys/Makefile +++ b/examples/yosys/Makefile @@ -3,4 +3,4 @@ SCRIPTS = $(wildcard *.py) all: - @$(foreach SCRIPT, $(SCRIPTS), python3 $(SCRIPT); python3 $(SCRIPT) --lang vhdl; ) + @$(foreach SCRIPT, $(SCRIPTS), python3 $(SCRIPT) || exit; python3 $(SCRIPT) --lang vhdl || exit; ) diff --git a/examples/yosys/ise.py b/examples/yosys/ise.py index b48d78b5..0d41d445 100644 --- a/examples/yosys/ise.py +++ b/examples/yosys/ise.py @@ -34,13 +34,7 @@ prj.set_top('Top') if args.action in ['generate', 'all']: - try: - prj.generate() - except RuntimeError: - print('ERROR:generate:Docker or ISE not found') + prj.generate() if args.action in ['transfer', 'all']: - try: - prj.transfer() - except RuntimeError: - print('ERROR:transfer:ISE not found') + prj.transfer() diff --git a/examples/yosys/vivado.py b/examples/yosys/vivado.py index 1a5ea22f..d77061f8 100644 --- a/examples/yosys/vivado.py +++ b/examples/yosys/vivado.py @@ -34,13 +34,7 @@ prj.set_top('Top') if args.action in ['generate', 'all']: - try: - prj.generate() - except RuntimeError: - print('ERROR:generate:Docker or Vivado not found') + prj.generate() if args.action in ['transfer', 'all']: - try: - prj.transfer() - except RuntimeError: - print('ERROR:transfer:Vivado not found') + prj.transfer() diff --git a/examples/yosys/yosys.py b/examples/yosys/yosys.py index 29316969..3f93ebfe 100644 --- a/examples/yosys/yosys.py +++ b/examples/yosys/yosys.py @@ -28,7 +28,4 @@ prj.set_top('Top') -try: - prj.generate() -except RuntimeError: - print('ERROR:generate:Docker not found') +prj.generate() diff --git a/fpga/project.py b/fpga/project.py index d9bc2f81..c6b5f18f 100644 --- a/fpga/project.py +++ b/fpga/project.py @@ -1,5 +1,5 @@ # -# Copyright (C) 2019-2021 Rodrigo A. Melo +# Copyright (C) 2019-2022 Rodrigo A. Melo # Copyright (C) 2019-2020 INTI # # This program is free software: you can redistribute it and/or modify @@ -17,10 +17,8 @@ # """fpga.project - -This module implements the main class of PyFPGA, which provides -functionalities to create a project, generate a bitstream and transfer it to a -Device. +This module implements the entry-point of PyFPGA, which provides +functionalities to create a project, generate a bitstream and program a device. """ import contextlib @@ -48,7 +46,7 @@ class Project: :param tool: FPGA tool to be used :param project: project name (the tool name is used if none specified) - :param init: a dict to initialize some parameters + :param init: a dict with metadata about the project :param relative_to_script: specifies if the files/directories are relative to the script or the execution directory :raises NotImplementedError: when tool is unsupported @@ -289,8 +287,8 @@ def generate(self, to_task='bit', from_task='prj', capture=False): :param capture: capture STDOUT and STDERR :returns: STDOUT and STDERR messages :raises ValueError: when from_task is later than to_task + :raises ValueError: when to_task or from_task are unsupported :raises RuntimeError: when the tool to be used is not found - :raises ValueError: when to_task or from_task are is unsupported .. note:: Valid values for **tasks** are ``prj`` (to creates the project file), @@ -329,9 +327,9 @@ def transfer( :param width: bits width of the memory (when device is not *fpga*) :param capture: capture STDOUT and STDERR :returns: STDOUT and STDERR messages - :raises RuntimeError: when the tool to be used is not found :raises FileNotFoundError: when the bitstream is not found :raises ValueError: when devtype, position or width are unsupported + :raises RuntimeError: when the tool to be used is not found """ _log.info( 'transfering "%s" project using "%s" tool from "%s" directory', @@ -352,7 +350,7 @@ def clean(self): @contextlib.contextmanager def _run_in_dir(self): - """Runs the tool in another directory.""" + """Run the tool in another directory.""" start = time.time() try: if not os.path.exists(self.outdir): diff --git a/test/mocks/impact b/test/mocks/impact new file mode 100755 index 00000000..17eee4a6 --- /dev/null +++ b/test/mocks/impact @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022 Rodrigo A. Melo +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# + +import argparse + +parser = argparse.ArgumentParser() + +parser.add_argument('-batch', action='store_true', required=True) +parser.add_argument('source') + +args = parser.parse_args() + +print(f'INFO:the {parser.prog.upper()} mock has been executed') diff --git a/test/mocks/libero b/test/mocks/libero new file mode 100755 index 00000000..3124978a --- /dev/null +++ b/test/mocks/libero @@ -0,0 +1,33 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022 Rodrigo A. Melo +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# + +import argparse +import sys + +parser = argparse.ArgumentParser() + +parser.add_argument('source') + +args = parser.parse_args() + + +if not args.source.startswith("SCRIPT:", 0): + print('ERROR:the parameter should start width "SCRIPT:"') + sys.exit(1) + +print(f'INFO:the {parser.prog.upper()} mock has been executed') diff --git a/test/mocks/quartus b/test/mocks/quartus new file mode 100644 index 00000000..495c255e --- /dev/null +++ b/test/mocks/quartus @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022 Rodrigo A. Melo +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# + +import argparse + +parser = argparse.ArgumentParser() + +parser.add_argument('--script', required=True) + +args = parser.parse_args() + +print(f'INFO:the {parser.prog.upper()} mock has been executed') diff --git a/test/mocks/quartus_pgm b/test/mocks/quartus_pgm new file mode 100755 index 00000000..3353ee46 --- /dev/null +++ b/test/mocks/quartus_pgm @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022 Rodrigo A. Melo +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# + +import argparse + +parser = argparse.ArgumentParser() + +parser.add_argument('-c', required=True) +parser.add_argument('--mode', choices=['jtag'], required=True) +parser.add_argument('-o', required=True) + +args = parser.parse_args() + +print(f'INFO:the {parser.prog.upper()} mock has been executed') diff --git a/test/mocks/quartus_sh b/test/mocks/quartus_sh new file mode 100755 index 00000000..495c255e --- /dev/null +++ b/test/mocks/quartus_sh @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022 Rodrigo A. Melo +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# + +import argparse + +parser = argparse.ArgumentParser() + +parser.add_argument('--script', required=True) + +args = parser.parse_args() + +print(f'INFO:the {parser.prog.upper()} mock has been executed') diff --git a/test/mocks/vivado b/test/mocks/vivado new file mode 100755 index 00000000..7d598f7a --- /dev/null +++ b/test/mocks/vivado @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022 Rodrigo A. Melo +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# + +import argparse + +parser = argparse.ArgumentParser() + +parser.add_argument('-mode', choices=['batch'], required=True) +parser.add_argument('-notrace', action='store_true', required=True) +parser.add_argument('-quiet', action='store_true', required=True) +parser.add_argument('-source', required=True) + +args = parser.parse_args() + +print(f'INFO:the {parser.prog.upper()} mock has been executed') diff --git a/test/mocks/xtclsh b/test/mocks/xtclsh new file mode 100755 index 00000000..71046506 --- /dev/null +++ b/test/mocks/xtclsh @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022 Rodrigo A. Melo +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# + +import argparse + +parser = argparse.ArgumentParser() + +parser.add_argument('source') + +args = parser.parse_args() + +print(f'INFO:the {parser.prog.upper()} mock has been executed') From 06c31d433abf5b8de08e4c34d2139ad3eaf4575b Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Tue, 24 May 2022 21:53:10 -0300 Subject: [PATCH 004/254] ci: fixed an 'exit status 2' issue --- .github/workflows/lint.yml | 1 - .github/workflows/test.yml | 22 +++++++++++++--------- fpga/tool/template.sh | 3 ++- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 0564dc22..7904c2d7 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -12,7 +12,6 @@ jobs: run: | pip install pycodestyle pip install pylint - pip install . - name: Lint run: | pycodestyle fpga examples test diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2e5564e1..4af9850d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -11,6 +11,16 @@ jobs: python-version: [3.6, 3.7, 3.8, 3.9] steps: - uses: actions/checkout@v2 + with: + submodules: true + fetch-depth: 0 + - name: Pull container images + run: | + docker pull hdlc/ghdl:yosys + docker pull hdlc/nextpnr:ice40 + docker pull hdlc/nextpnr:ecp5 + docker pull hdlc/icestorm + docker pull hdlc/prjtrellis - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v2 with: @@ -19,14 +29,8 @@ jobs: run: | pip install pytest pip install . - - name: Pull container images - run: | - docker pull hdlc/prjtrellis - docker pull hdlc/ghdl:yosys - docker pull hdlc/icestorm - docker pull hdlc/nextpnr:ecp5 - docker pull hdlc/nextpnr:ice40 - - name: Test + - name: Run test + run: pytest + - name: Run examples run: | - pytest cd examples; make MOCKS=1 diff --git a/fpga/tool/template.sh b/fpga/tool/template.sh index c10dbdd6..06d82b20 100644 --- a/fpga/tool/template.sh +++ b/fpga/tool/template.sh @@ -73,7 +73,8 @@ MODULE= [ -n "$VHDLS" ] && MODULE="-m ghdl" function print () {{ - tput setaf 6; echo ">>> PyFPGA ($1): $2"; tput sgr0; + # tput setaf 6; echo ">>> PyFPGA ($1): $2"; tput sgr0; + echo ">>> PyFPGA ($1): $2" }} ############################################################################### From b896134e26977808809a731d44fa91376623c7b2 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Tue, 24 May 2022 22:20:55 -0300 Subject: [PATCH 005/254] ci: added python 3.10 --- .github/workflows/test.yml | 6 +++--- setup.py | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4af9850d..533e2019 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -8,7 +8,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.6, 3.7, 3.8, 3.9] + pyver: ['3.6', '3.7', '3.8', '3.9', '3.10'] steps: - uses: actions/checkout@v2 with: @@ -21,10 +21,10 @@ jobs: docker pull hdlc/nextpnr:ecp5 docker pull hdlc/icestorm docker pull hdlc/prjtrellis - - name: Set up Python ${{ matrix.python-version }} + - name: Set up Python ${{ matrix.pyver }} uses: actions/setup-python@v2 with: - python-version: ${{ matrix.python-version }} + python-version: ${{ matrix.pyver }} - name: Install dependencies run: | pip install pytest diff --git a/setup.py b/setup.py index 90266a5d..c0e7ae4c 100644 --- a/setup.py +++ b/setup.py @@ -34,6 +34,7 @@ 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', 'Topic :: Utilities', 'Topic :: Software Development :: Build Tools', "Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)" From fda3a825c0dbec21a1a2ba8dc0c54fb73f74f5e9 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Tue, 24 May 2022 22:35:30 -0300 Subject: [PATCH 006/254] ci: added schedule based on cron --- .github/workflows/test.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 533e2019..9ff37c04 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -2,13 +2,18 @@ name: 'test' on: push: + pull_request: + schedule: # Run once a week to ensure tests pass with updated dependencies + - cron: '0 0 * * 6' jobs: test: - runs-on: ubuntu-latest strategy: matrix: + os: ['ubuntu'] pyver: ['3.6', '3.7', '3.8', '3.9', '3.10'] + runs-on: ${{ matrix.os }}-latest + name: ${{ matrix.os }} | ${{ matrix.pyver }} steps: - uses: actions/checkout@v2 with: From dbfc133ad0bcdf0a44361fd0911e17b365aec710 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Wed, 25 May 2022 23:01:04 -0300 Subject: [PATCH 007/254] fpga: renamed add_path as add_vlog_include --- doc/basic.rst | 4 ++-- doc/intro.rst | 6 +++--- examples/multi/parameters.py | 4 ++-- examples/multi/verilog.py | 4 ++-- examples/openflow/icestorm.py | 4 ++-- examples/openflow/prjtrellis.py | 4 ++-- examples/yosys/ise.py | 4 ++-- examples/yosys/vivado.py | 4 ++-- examples/yosys/yosys.py | 4 ++-- fpga/helpers/hdl2bit.py | 2 +- fpga/project.py | 8 ++++---- fpga/tool/__init__.py | 4 ++-- 12 files changed, 26 insertions(+), 26 deletions(-) diff --git a/doc/basic.rst b/doc/basic.rst index a0d081ba..7a5456c8 100644 --- a/doc/basic.rst +++ b/doc/basic.rst @@ -70,8 +70,8 @@ file extension, and if it is a member of a VHDL package. change the order if needed. * If a file seems unsupported, you can always use the ``prefile`` or ``project`` :ref:`hooks`. - * In case of Verilog, ``add_path`` can be used to specify where to search for - included files. + * In case of Verilog, ``add_vlog_include`` can be used to specify where to + search for included files. Finally, the top-level must be specified: diff --git a/doc/intro.rst b/doc/intro.rst index db1d89d4..55d3576c 100644 --- a/doc/intro.rst +++ b/doc/intro.rst @@ -46,11 +46,11 @@ Detailed support +------------------------------+---------+----------+------------+-----------+----------+ |``std_logic_vector`` (*VHDL*) | ``TBD`` | ``TBD`` |``TBD`` | ``TBD`` | ``TBD`` | +------------------------------+---------+----------+------------+-----------+----------+ -|**add_path** | ``Yes`` | ``Yes`` | ``Yes`` | ``Yes`` | ``Yes`` | +|**add_vlog_include** | ``Yes`` | ``Yes`` | ``Yes`` | ``Yes`` | ``Yes`` | +------------------------------+---------+----------+------------+-----------+----------+ -|**set_define** (*Verilog*) | ``TBI`` | ``TBI`` | ``TBI`` | ``TBI`` | ``TBI`` | +|**add_vlog_define** | ``TBI`` | ``TBI`` | ``TBI`` | ``TBI`` | ``TBI`` | +------------------------------+---------+----------+------------+-----------+----------+ -|**set_arch** (*VHDL*) | ``TBI`` | ``TBI`` | ``TBI`` | ``TBI`` | ``TBI`` | +|**set_vhdl_arch** | ``TBI`` | ``TBI`` | ``TBI`` | ``TBI`` | ``TBI`` | +------------------------------+---------+----------+------------+-----------+----------+ |**generate** | | | | | | +------------------------------+---------+----------+------------+-----------+----------+ diff --git a/examples/multi/parameters.py b/examples/multi/parameters.py index 30012888..107bd2d4 100644 --- a/examples/multi/parameters.py +++ b/examples/multi/parameters.py @@ -26,8 +26,8 @@ if hdl == 'vhdl': PRJ.add_files('../../hdl/blinking.vhdl') else: - PRJ.add_path('../../hdl/headers1') - PRJ.add_path('../../hdl/headers2') + PRJ.add_vlog_include('../../hdl/headers1') + PRJ.add_vlog_include('../../hdl/headers2') PRJ.add_files('../../hdl/blinking.v') PRJ.set_top('Blinking') # PRJ.set_param('INT', '15') diff --git a/examples/multi/verilog.py b/examples/multi/verilog.py index a751f566..b16be138 100644 --- a/examples/multi/verilog.py +++ b/examples/multi/verilog.py @@ -16,8 +16,8 @@ continue PRJ = Project(tool) PRJ.set_outdir('../../build/multi/verilog/%s' % tool) - PRJ.add_path('../../hdl/headers1') - PRJ.add_path('../../hdl/headers2') + PRJ.add_vlog_include('../../hdl/headers1') + PRJ.add_vlog_include('../../hdl/headers2') PRJ.add_files('../../hdl/blinking.v') PRJ.add_files('../../hdl/top.v') PRJ.set_top('Top') diff --git a/examples/openflow/icestorm.py b/examples/openflow/icestorm.py index 77a0e529..a71bec78 100644 --- a/examples/openflow/icestorm.py +++ b/examples/openflow/icestorm.py @@ -31,8 +31,8 @@ prj.set_part(BOARDS[args.board][0]) if args.lang == 'verilog': - prj.add_path('../../hdl/headers1') - prj.add_path('../../hdl/headers2') + prj.add_vlog_include('../../hdl/headers1') + prj.add_vlog_include('../../hdl/headers2') prj.add_files('../../hdl/blinking.v') prj.add_files('../../hdl/top.v') else: # args.lang == 'vhdl' diff --git a/examples/openflow/prjtrellis.py b/examples/openflow/prjtrellis.py index 86255567..bece88a9 100644 --- a/examples/openflow/prjtrellis.py +++ b/examples/openflow/prjtrellis.py @@ -31,8 +31,8 @@ prj.set_part(BOARDS[args.board][0]) if args.lang == 'verilog': - prj.add_path('../../hdl/headers1') - prj.add_path('../../hdl/headers2') + prj.add_vlog_include('../../hdl/headers1') + prj.add_vlog_include('../../hdl/headers2') prj.add_files('../../hdl/blinking.v') prj.add_files('../../hdl/top.v') else: # args.lang == 'vhdl' diff --git a/examples/yosys/ise.py b/examples/yosys/ise.py index 0d41d445..6a900be9 100644 --- a/examples/yosys/ise.py +++ b/examples/yosys/ise.py @@ -21,8 +21,8 @@ prj.set_part('XC6SLX9-2-CSG324') if args.lang == 'verilog': - prj.add_path('../../hdl/headers1') - prj.add_path('../../hdl/headers2') + prj.add_vlog_include('../../hdl/headers1') + prj.add_vlog_include('../../hdl/headers2') prj.add_files('../../hdl/blinking.v') prj.add_files('../../hdl/top.v') else: # args.lang == 'vhdl' diff --git a/examples/yosys/vivado.py b/examples/yosys/vivado.py index d77061f8..4dfae60a 100644 --- a/examples/yosys/vivado.py +++ b/examples/yosys/vivado.py @@ -21,8 +21,8 @@ prj.set_part('xc7z010-1-clg400') if args.lang == 'verilog': - prj.add_path('../../hdl/headers1') - prj.add_path('../../hdl/headers2') + prj.add_vlog_include('../../hdl/headers1') + prj.add_vlog_include('../../hdl/headers2') prj.add_files('../../hdl/blinking.v') prj.add_files('../../hdl/top.v') else: # args.lang == 'vhdl' diff --git a/examples/yosys/yosys.py b/examples/yosys/yosys.py index 3f93ebfe..3553d515 100644 --- a/examples/yosys/yosys.py +++ b/examples/yosys/yosys.py @@ -17,8 +17,8 @@ prj.set_outdir('../../build/yosys-{}'.format(args.lang)) if args.lang == 'verilog': - prj.add_path('../../hdl/headers1') - prj.add_path('../../hdl/headers2') + prj.add_vlog_include('../../hdl/headers1') + prj.add_vlog_include('../../hdl/headers2') prj.add_files('../../hdl/blinking.v') prj.add_files('../../hdl/top.v') else: # args.lang == 'vhdl' diff --git a/fpga/helpers/hdl2bit.py b/fpga/helpers/hdl2bit.py index 68378ca9..b73d0b8a 100644 --- a/fpga/helpers/hdl2bit.py +++ b/fpga/helpers/hdl2bit.py @@ -133,7 +133,7 @@ def main(): if args.include is not None: for include in args.include: - prj.add_path(include) + prj.add_vlog_include(include) if args.file is not None: for file in args.file: diff --git a/fpga/project.py b/fpga/project.py index c6b5f18f..e2b2d6e9 100644 --- a/fpga/project.py +++ b/fpga/project.py @@ -108,7 +108,7 @@ def _initialize(self, init): if 'paths' in init: for path in init['paths']: _log.debug('PATH = %s', path) - self.add_path(path) + self.add_vlog_include(path) for filetype in ['vhdl', 'verilog', 'constraint']: if filetype in init: for file in init[filetype]: @@ -208,8 +208,8 @@ def get_files(self): """ return self.tool.get_files() - def add_path(self, path): - """Add a search path. + def add_vlog_include(self, path): + """Add a Verilog include path. Useful to specify where to search Verilog Included Files or IP repositories. @@ -221,7 +221,7 @@ def add_path(self, path): path = os.path.normpath(path) if os.path.isdir(path): path = os.path.relpath(path, self.outdir) - self.tool.add_path(path) + self.tool.add_vlog_include(path) else: raise NotADirectoryError(path) diff --git a/fpga/tool/__init__.py b/fpga/tool/__init__.py index 1f82fc3e..54e8f594 100644 --- a/fpga/tool/__init__.py +++ b/fpga/tool/__init__.py @@ -143,8 +143,8 @@ def get_files(self): """Get the files of the project.""" return self.files - def add_path(self, path): - """Add a search path.""" + def add_vlog_include(self, path): + """Add a Verilog include path.""" self.paths.append(path) def set_top(self, top): From 579f275d51a7f1c87e196b90679f71e4e7e9015a Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Wed, 25 May 2022 23:06:45 -0300 Subject: [PATCH 008/254] fpga: added add_vlog_define and set_vhdl_arch in project.py (not implemented) --- fpga/project.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/fpga/project.py b/fpga/project.py index e2b2d6e9..6567baa1 100644 --- a/fpga/project.py +++ b/fpga/project.py @@ -225,6 +225,14 @@ def add_vlog_include(self, path): else: raise NotADirectoryError(path) + def add_vlog_define(self, name, value): + """Add a Verilog define.""" + raise NotImplementedError() + + def set_vhdl_arch(self, name): + """Set the VHDL architecture.""" + raise NotImplementedError() + def set_top(self, toplevel): """Set the top level of the project. From 6699cd91bd097a1fce2abbb6a647cabf70a75473 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Thu, 26 May 2022 22:14:25 -0300 Subject: [PATCH 009/254] fpga: renamed set_param as add_param --- doc/advanced.rst | 4 ++-- doc/intro.rst | 2 +- examples/multi/parameters.py | 14 +++++++------- examples/vivado/vivado.py | 2 +- fpga/helpers/hdl2bit.py | 2 +- fpga/project.py | 8 ++++---- fpga/tool/__init__.py | 4 ++-- 7 files changed, 18 insertions(+), 18 deletions(-) diff --git a/doc/advanced.rst b/doc/advanced.rst index 0cfea3af..9ff52609 100644 --- a/doc/advanced.rst +++ b/doc/advanced.rst @@ -99,9 +99,9 @@ The generics/parameters of the project can be optionally changed with: .. code-block:: python - prj.set_param('param1', value1) + prj.add_param('param1', value1) ... - prj.set_param('paramN', valueN) + prj.add_param('paramN', valueN) Generate options ================ diff --git a/doc/intro.rst b/doc/intro.rst index 55d3576c..62d93d7e 100644 --- a/doc/intro.rst +++ b/doc/intro.rst @@ -32,7 +32,7 @@ Detailed support +------------------------------+---------+----------+------------+-----------+----------+ |``block_design`` | ``NY`` | ``NY`` | ``NY`` | ``NY`` | ``Yes`` | +------------------------------+---------+----------+------------+-----------+----------+ -|**set_param** | | | | | | +|**add_param** | | | | | | +------------------------------+---------+----------+------------+-----------+----------+ |``boolean`` (*VHDL/Verilog*) | ``TBD`` | ``TBD`` |``TBD`` | ``TBD`` | ``TBD`` | +------------------------------+---------+----------+------------+-----------+----------+ diff --git a/examples/multi/parameters.py b/examples/multi/parameters.py index 107bd2d4..f1912c85 100644 --- a/examples/multi/parameters.py +++ b/examples/multi/parameters.py @@ -20,8 +20,8 @@ if tool in ['openflow', 'yosys', 'yosys-ise', 'yosys-vivado']: continue PRJ = Project(tool) - PRJ.set_param('FREQ', '50000000') - PRJ.set_param('SECS', '2') + PRJ.add_param('FREQ', '50000000') + PRJ.add_param('SECS', '2') PRJ.set_outdir('../../build/multi/params/%s/%s' % (tool, hdl)) if hdl == 'vhdl': PRJ.add_files('../../hdl/blinking.vhdl') @@ -30,11 +30,11 @@ PRJ.add_vlog_include('../../hdl/headers2') PRJ.add_files('../../hdl/blinking.v') PRJ.set_top('Blinking') - # PRJ.set_param('INT', '15') - # PRJ.set_param('REA', '1.5') - # PRJ.set_param('LOG', "'1'") - # PRJ.set_param('VEC', '"10101010"') - # PRJ.set_param('STR', '"WXYZ"') + # PRJ.add_param('INT', '15') + # PRJ.add_param('REA', '1.5') + # PRJ.add_param('LOG', "'1'") + # PRJ.add_param('VEC', '"10101010"') + # PRJ.add_param('STR', '"WXYZ"') # PRJ.set_outdir('../../build/multi/params/%s/%s' % (tool, hdl)) # if hdl == 'vhdl': # PRJ.add_files('../../hdl/fakes/generics.vhdl') diff --git a/examples/vivado/vivado.py b/examples/vivado/vivado.py index e28c861b..bf1830a1 100644 --- a/examples/vivado/vivado.py +++ b/examples/vivado/vivado.py @@ -18,7 +18,7 @@ prj.set_outdir('../../build/vivado') -prj.set_param('FREQ', '125000000') +prj.add_param('FREQ', '125000000') prj.add_files('../../hdl/blinking.vhdl') prj.add_files('zybo.xdc') prj.set_top('Blinking') diff --git a/fpga/helpers/hdl2bit.py b/fpga/helpers/hdl2bit.py index b73d0b8a..87ed4159 100644 --- a/fpga/helpers/hdl2bit.py +++ b/fpga/helpers/hdl2bit.py @@ -145,7 +145,7 @@ def main(): if args.param is not None: for param in args.param: - prj.set_param(param[0], param[1]) + prj.add_param(param[0], param[1]) prj.add_files(args.top) prj.set_top(args.top) diff --git a/fpga/project.py b/fpga/project.py index 6567baa1..d3f84a68 100644 --- a/fpga/project.py +++ b/fpga/project.py @@ -125,7 +125,7 @@ def _initialize(self, init): if 'params' in init: for parname, parvalue in init['params'].items(): _log.debug('PARAM = %s %s', parname, parvalue) - self.set_param(parname, parvalue) + self.add_param(parname, parvalue) if 'top' in init: _log.debug('TOP = %s', init['top']) self.set_top(init['top']) @@ -154,13 +154,13 @@ def set_part(self, part): """ self.tool.set_part(part) - def set_param(self, name, value): - """Set a Generic/Parameter Value. + def add_param(self, name, value): + """Add a Generic/Parameter Value. :param name: parameter/generic name :param value: value to be assigned """ - self.tool.set_param(name, value) + self.tool.add_param(name, value) def add_files(self, pathname, filetype=None, library=None, options=None): """Adds files to the project. diff --git a/fpga/tool/__init__.py b/fpga/tool/__init__.py index 54e8f594..8ced52bb 100644 --- a/fpga/tool/__init__.py +++ b/fpga/tool/__init__.py @@ -130,8 +130,8 @@ def set_part(self, part): """Set the target PART.""" self.part['name'] = part - def set_param(self, name, value): - """Set a Generic/Parameter Value.""" + def add_param(self, name, value): + """Add a Generic/Parameter Value.""" self.params.append([name, value]) def add_file(self, file, filetype, library, options): From d1bd35cffa035104761e12e3c22f1e4b2f00de4b Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Thu, 26 May 2022 22:52:45 -0300 Subject: [PATCH 010/254] fpga: renamed 'imp' as 'par' --- doc/advanced.rst | 8 ++++---- doc/intro.rst | 2 +- fpga/project.py | 4 ++-- fpga/tool/__init__.py | 8 ++++---- fpga/tool/template.sh | 6 +++--- fpga/tool/template.tcl | 20 ++++++++++---------- 6 files changed, 24 insertions(+), 24 deletions(-) diff --git a/doc/advanced.rst b/doc/advanced.rst index 9ff52609..3ee54d32 100644 --- a/doc/advanced.rst +++ b/doc/advanced.rst @@ -68,9 +68,9 @@ The following table depicts the parts of the *Project Creation* and the +--------------------------+----------------------+ | Files addition | **postsyn** hook | +--------------------------+----------------------+ -| Top specification | Implementation | +| Top specification | Place and Route | +--------------------------+----------------------+ -| Parameters specification | **postimp** hook | +| Parameters specification | **postpar** hook | +--------------------------+----------------------+ | **project** hook | Bitstream generation | +--------------------------+----------------------+ @@ -87,7 +87,7 @@ specify additional *hooks* in different parts of the flow, using: .. NOTE:: * Valid vaues for *phase* are ``prefile``, ``project`` (default), ``preflow``, - ``postsyn``, ``postimp`` and ``postbit``. + ``postsyn``, ``postpar`` and ``postbit``. * The *hook* string must be a valid command (supported by the used tool). * If more than one *hook* is needed in the same *phase*, you can call this method several times (the commands will be executed in order). @@ -115,7 +115,7 @@ The method ``generate`` (previously seen at the end of With *to_task* and *from_taks* (with default values ``bit`` and ``prj``), you are selecting the first and last task to execute when `generate` is -invoqued. The order and available tasks are ``prj``, ``syn``, ``imp`` and ``bit``. +invoqued. The order and available tasks are ``prj``, ``syn``, ``par`` and ``bit``. It can be useful in at least two cases: * Maybe you created a file project with the GUI of the Tool and only want to diff --git a/doc/intro.rst b/doc/intro.rst index 62d93d7e..67c8e087 100644 --- a/doc/intro.rst +++ b/doc/intro.rst @@ -58,7 +58,7 @@ Detailed support +------------------------------+---------+----------+------------+-----------+----------+ |``syn`` | ``Yes`` | ``Yes`` | ``Yes`` | ``Yes`` | ``Yes`` | +------------------------------+---------+----------+------------+-----------+----------+ -|``imp`` | ``Yes`` | ``Yes`` | ``Yes`` | ``Yes`` | ``Yes`` | +|``par`` | ``Yes`` | ``Yes`` | ``Yes`` | ``Yes`` | ``Yes`` | +------------------------------+---------+----------+------------+-----------+----------+ |``bit`` | ``Yes`` | ``Yes`` | ``Yes`` | ``Yes`` | ``Yes`` | +------------------------------+---------+----------+------------+-----------+----------+ diff --git a/fpga/project.py b/fpga/project.py index d3f84a68..f3f99b7a 100644 --- a/fpga/project.py +++ b/fpga/project.py @@ -278,8 +278,8 @@ def add_hook(self, hook, phase='project'): ``prefile`` (to add options needed to find files), ``project`` (to add project related options), ``preflow`` (to change options previous to run the flow), - ``postsyn`` (to perform an action between *syn* and *imp*), - ``postimp`` (to perform an action between *imp* and *bit*) and + ``postsyn`` (to perform an action between *syn* and *par*), + ``postpar`` (to perform an action between *par* and *bit*) and ``postbit`` (to perform an action after *bit*) .. warning:: Using a hook, you will be probably broken the vendor diff --git a/fpga/tool/__init__.py b/fpga/tool/__init__.py index 8ced52bb..9fc09685 100644 --- a/fpga/tool/__init__.py +++ b/fpga/tool/__init__.py @@ -30,8 +30,8 @@ FILETYPES = ['verilog', 'vhdl', 'constraint', 'design'] MEMWIDTHS = [1, 2, 4, 8, 16, 32] -PHASES = ['prefile', 'project', 'preflow', 'postsyn', 'postimp', 'postbit'] -TASKS = ['prj', 'syn', 'imp', 'bit'] +PHASES = ['prefile', 'project', 'preflow', 'postsyn', 'postpar', 'postbit'] +TASKS = ['prj', 'syn', 'par', 'bit'] def check_value(value, values): @@ -83,7 +83,7 @@ def __init__(self, project): 'project': [], 'preflow': [], 'postsyn': [], - 'postimp': [], + 'postpar': [], 'postbit': [] } self.files = { @@ -202,7 +202,7 @@ def _create_gen_script(self, tasks): tcl = tcl.replace('#PROJECT_CMDS#', '\n'.join(self.cmds['project'])) tcl = tcl.replace('#PREFLOW_CMDS#', '\n'.join(self.cmds['preflow'])) tcl = tcl.replace('#POSTSYN_CMDS#', '\n'.join(self.cmds['postsyn'])) - tcl = tcl.replace('#POSTIMP_CMDS#', '\n'.join(self.cmds['postimp'])) + tcl = tcl.replace('#POSTPAR_CMDS#', '\n'.join(self.cmds['postpar'])) tcl = tcl.replace('#POSTBIT_CMDS#', '\n'.join(self.cmds['postbit'])) with open(f'{self._TOOL}.tcl', 'w', encoding='utf-8') as file: file.write(tcl) diff --git a/fpga/tool/template.sh b/fpga/tool/template.sh index 06d82b20..f2b271eb 100644 --- a/fpga/tool/template.sh +++ b/fpga/tool/template.sh @@ -40,7 +40,7 @@ INCLUDES="{includes}" VERILOGS="{verilogs}" CONSTRAINTS="{constraints}" -# taks = prj syn imp bit +# taks = prj syn par bit TASKS="{tasks}" # @@ -137,9 +137,9 @@ fi # Place and Route ############################################################################### -if [[ $TASKS == *"imp"* ]]; then +if [[ $TASKS == *"par"* ]]; then -print "nextpnr-$FAMILY" "running 'implementation'" +print "nextpnr-$FAMILY" "running 'place and route'" INPUT="--json $PROJECT.json" diff --git a/fpga/tool/template.tcl b/fpga/tool/template.tcl index 238f6891..7466d534 100644 --- a/fpga/tool/template.tcl +++ b/fpga/tool/template.tcl @@ -18,7 +18,7 @@ # along with this program. If not, see . # # Description: Tcl script to create a new project and performs synthesis, -# implementation and bitstream generation. +# place and route, and bitstream generation. # # Supported TOOLs: ise, libero, quartus, vivado # @@ -41,7 +41,7 @@ set DEVICE #DEVICE# set PACKAGE #PACKAGE# set SPEED #SPEED# set TOP #TOP# -# TASKS = prj syn imp bit +# TASKS = prj syn par bit set TASKS [list #TASKS#] set PARAMS [list #PARAMS#] @@ -65,8 +65,8 @@ proc fpga_commands { PHASE } { "postsyn" { #POSTSYN_CMDS# } - "postimp" { -#POSTIMP_CMDS# + "postpar" { +#POSTPAR_CMDS# } "postbit" { #POSTBIT_CMDS# @@ -384,9 +384,9 @@ proc fpga_run_syn {} { } } -proc fpga_run_imp {} { +proc fpga_run_par {} { global TOOL PRESYNTH - fpga_print "running 'implementation'" + fpga_print "running 'place and route'" switch $TOOL { "ise" { process run "Translate" @@ -470,7 +470,7 @@ if { [lsearch -exact $TASKS "prj"] >= 0 } { # Design Flow # -if { [lsearch -regexp $TASKS "syn|imp|bit"] >= 0 } { +if { [lsearch -regexp $TASKS "syn|par|bit"] >= 0 } { fpga_print "running the Design Flow" if { [catch { fpga_open $PROJECT @@ -479,9 +479,9 @@ if { [lsearch -regexp $TASKS "syn|imp|bit"] >= 0 } { fpga_run_syn fpga_commands "postsyn" } - if { [lsearch -exact $TASKS "imp"] >= 0 } { - fpga_run_imp - fpga_commands "postimp" + if { [lsearch -exact $TASKS "par"] >= 0 } { + fpga_run_par + fpga_commands "postpar" } if { [lsearch -exact $TASKS "bit"] >= 0 } { fpga_run_bit From bbdc605c5ab17c680af87db097bec8a550a1831d Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Thu, 26 May 2022 23:32:58 -0300 Subject: [PATCH 011/254] Updated shields in README.md --- README.md | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index eb969487..97fad22f 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,10 @@ # PyFPGA [![License](https://img.shields.io/badge/License-GPL--3.0-darkgreen?style=flat-square)](LICENSE) -![GDHL](https://img.shields.io/badge/GHDL-last-brightgreen.svg?style=flat-square) -![Yosys](https://img.shields.io/badge/Yosys-last-brightgreen.svg?style=flat-square) -![nextpnr](https://img.shields.io/badge/nextpnr-last-brightgreen.svg?style=flat-square) -![icestorm](https://img.shields.io/badge/icestorm-last-brightgreen.svg?style=flat-square) -![prjtrellis](https://img.shields.io/badge/prjtrellis-last-brightgreen.svg?style=flat-square) - ![Vivado](https://img.shields.io/badge/Vivado-2019.2-blue.svg?style=flat-square) ![Quartus](https://img.shields.io/badge/Quartus--Prime-19.1-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-12.2-blue.svg?style=flat-square) +![ISE](https://img.shields.io/badge/ISE-14.7-blue.svg?style=flat-square) +![Openflow](https://img.shields.io/badge/Openflow-GHDL%20%7C%20Yosys%20%7C%20nextpnr%20%7C%20icestorm%20%7C%20prjtrellis-darkgreen.svg?style=flat-square) > **WARNING:** (2022-05-15) PyFPGA is in the process of being strongly rewritten/simplified. > Most changes are internal, but the API (`Project` class) will change. @@ -95,7 +90,7 @@ Should you achieve either success of failure on non-POSIX systems, please let us **Notes:** - The open-source tools are supported trough container images from the -[ghdl/docker](https://github.com/ghdl/docker) project, so +[HDL containers](https://hdl.github.io/containers) project, so [Docker](https://www.docker.com/) ~~or [Podman](https://podman.io/)~~ must be installed. The same workflow can be used in CI services. - ISE, Libero-Soc, Quartus Prime and Vivado, must be ready to be executed from From fe8bcbcd383037fe893d34be206896c4b3c62bff Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Fri, 27 May 2022 00:02:29 -0300 Subject: [PATCH 012/254] fpga: renamed 'init' as 'meta' --- examples/multi/projects.py | 6 +++--- fpga/project.py | 40 +++++++++++++++++++------------------- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/examples/multi/projects.py b/examples/multi/projects.py index dd7bb7ce..3d267a93 100644 --- a/examples/multi/projects.py +++ b/examples/multi/projects.py @@ -14,7 +14,7 @@ 'prj1': Project( 'vivado', 'vivado-prj', - { + meta={ 'outdir': '../../build/multi/projects/vivado', 'part': 'xc7k70t-3-fbg484', 'vhdl': [ @@ -28,7 +28,7 @@ 'prj2': Project( 'ise', 'ise-prj', - { + meta={ 'outdir': '../../build/multi/projects/ise', 'part': 'xc6slx9-2-csg324', 'vhdl': [ @@ -40,7 +40,7 @@ 'prj3': Project( 'quartus', 'qurtus-prj', - { + meta={ 'outdir': '../../build/multi/projects/quartus', 'part': '5CEBA2F17A7', 'paths': [ diff --git a/fpga/project.py b/fpga/project.py index f3f99b7a..069d2be7 100644 --- a/fpga/project.py +++ b/fpga/project.py @@ -46,7 +46,7 @@ class Project: :param tool: FPGA tool to be used :param project: project name (the tool name is used if none specified) - :param init: a dict with metadata about the project + :param meta: a dict with metadata about the project :param relative_to_script: specifies if the files/directories are relative to the script or the execution directory :raises NotImplementedError: when tool is unsupported @@ -57,7 +57,7 @@ class Project: """ def __init__( - self, tool='vivado', project=None, init=None, + self, tool='vivado', project=None, meta=None, relative_to_script=True): """Class constructor.""" if tool == 'ghdl': @@ -93,25 +93,25 @@ def __init__( self._absdir = os.path.join(self._rundir, self._reldir) _log.debug('ABSDIR = %s', self._absdir) self.set_outdir('build') - self._initialize(init) + self._initialize(meta) - def _initialize(self, init): + def _initialize(self, meta): """Set some of the most used internal parameters.""" - if init is None: + if meta is None: return - if 'outdir' in init: - _log.debug('OUTDIR = %s', init['outdir']) - self.set_outdir(init['outdir']) - if 'part' in init: - _log.debug('PART = %s', init['part']) - self.set_part(init['part']) - if 'paths' in init: - for path in init['paths']: + if 'outdir' in meta: + _log.debug('OUTDIR = %s', meta['outdir']) + self.set_outdir(meta['outdir']) + if 'part' in meta: + _log.debug('PART = %s', meta['part']) + self.set_part(meta['part']) + if 'paths' in meta: + for path in meta['paths']: _log.debug('PATH = %s', path) self.add_vlog_include(path) for filetype in ['vhdl', 'verilog', 'constraint']: - if filetype in init: - for file in init[filetype]: + if filetype in meta: + for file in meta[filetype]: if isinstance(file, list): filename = file[0] library = file[1] @@ -122,13 +122,13 @@ def _initialize(self, init): 'FILE = %s %s %s', filename, filetype, library ) self.add_files(filename, filetype, library) - if 'params' in init: - for parname, parvalue in init['params'].items(): + if 'params' in meta: + for parname, parvalue in meta['params'].items(): _log.debug('PARAM = %s %s', parname, parvalue) self.add_param(parname, parvalue) - if 'top' in init: - _log.debug('TOP = %s', init['top']) - self.set_top(init['top']) + if 'top' in meta: + _log.debug('TOP = %s', meta['top']) + self.set_top(meta['top']) def set_outdir(self, outdir): """Sets the OUTput DIRectory (where to put the resulting files). From 8777ee8ebfba2dffd41b29f5d6d330d96610e353 Mon Sep 17 00:00:00 2001 From: lmcapacho Date: Tue, 31 Jan 2023 18:46:32 -0500 Subject: [PATCH 013/254] fix: ModuleNotFoundError: No module named 'fpga.helpers' --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index c0e7ae4c..05036c4a 100644 --- a/setup.py +++ b/setup.py @@ -15,7 +15,7 @@ author_email='rodrigomelo9@gmail.com', license='GPLv3', url='https://github.com/PyFPGA/pyfpga', - package_data={'': ['tool/*.sh', 'tool/*.tcl']}, + package_data={'': ['tool/*.sh', 'tool/*.tcl', 'helpers/*']}, packages=find_packages(), entry_points={ 'console_scripts': [ From 7f49fdf6dc873e56132c61bf902ee463199fa90e Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sun, 19 May 2024 21:27:38 -0300 Subject: [PATCH 014/254] Improved existing mocks --- test/mocks/libero | 22 +++++++++++++++++++++- test/mocks/quartus | 27 --------------------------- test/mocks/quartus_sh | 42 +++++++++++++++++++++++++++++++++++++++++- test/mocks/vivado | 43 ++++++++++++++++++++++++++++++++++++++++++- test/mocks/xtclsh | 23 ++++++++++++++++++++++- 5 files changed, 126 insertions(+), 31 deletions(-) delete mode 100644 test/mocks/quartus diff --git a/test/mocks/libero b/test/mocks/libero index 3124978a..b0f0b691 100755 --- a/test/mocks/libero +++ b/test/mocks/libero @@ -17,17 +17,37 @@ # import argparse +import subprocess 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 {parser.prog.upper()} mock has been executed') +tcl = f''' +proc unknown {{ cmmd args }} {{ }} + +source {args.source.replace('SCRIPT:', '')} +''' + +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, + #stdout=output, stderr=subprocess.STDOUT +) + +print(f'INFO:the {tool.upper()} mock has been executed') diff --git a/test/mocks/quartus b/test/mocks/quartus deleted file mode 100644 index 495c255e..00000000 --- a/test/mocks/quartus +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2022 Rodrigo A. Melo -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# - -import argparse - -parser = argparse.ArgumentParser() - -parser.add_argument('--script', required=True) - -args = parser.parse_args() - -print(f'INFO:the {parser.prog.upper()} mock has been executed') diff --git a/test/mocks/quartus_sh b/test/mocks/quartus_sh index 495c255e..a002e73d 100755 --- a/test/mocks/quartus_sh +++ b/test/mocks/quartus_sh @@ -17,6 +17,9 @@ # import argparse +import os +import subprocess + parser = argparse.ArgumentParser() @@ -24,4 +27,41 @@ parser.add_argument('--script', required=True) args = parser.parse_args() -print(f'INFO:the {parser.prog.upper()} mock has been executed') +tool = parser.prog + +tcl = f''' +lappend auto_path pkg + +proc unknown {{ cmmd args }} {{ }} + +source {args.script} +''' + +with open(f'{tool}-mock.tcl', 'w', encoding='utf-8') as file: + file.write(tcl) + +tcl = f''' +namespace eval ::quartus {{ + namespace export project +}} +''' + +if not os.path.exists('pkg'): + os.makedirs('pkg') + +package ifneeded ::quartus 1.0 [list source quartus-pkg.tcl] + +pkgIndex.tcl + +with open(f'pkg/quartus.tcl', 'w', encoding='utf-8') as file: + file.write(tcl) + +subprocess.run( + f'tclsh {tool}-mock.tcl', + shell=True, + check=True, + universal_newlines=True, + #stdout=output, stderr=subprocess.STDOUT +) + +print(f'INFO:the {tool.upper()} mock has been executed') diff --git a/test/mocks/vivado b/test/mocks/vivado index 7d598f7a..af5614d1 100755 --- a/test/mocks/vivado +++ b/test/mocks/vivado @@ -17,6 +17,8 @@ # import argparse +import subprocess + parser = argparse.ArgumentParser() @@ -27,4 +29,43 @@ parser.add_argument('-source', required=True) args = parser.parse_args() -print(f'INFO:the {parser.prog.upper()} mock has been executed') +tool = parser.prog + + +#proc create_project {{ args }} {{ }} +#proc open_project {{ args }} {{ }} +#proc current_project {{ args }} {{ }} +#proc current_fileset {{ args }} {{ }} +#proc get_filesets {{ args }} {{ }} +#proc set_property {{ args }} {{ }} +#proc add_files {{ args }} {{ }} +#proc get_files {{ args }} {{ }} +#proc reset_run {{ args }} {{ }} +#proc launch_runs {{ args }} {{ }} +#proc get_runs {{ args }} {{ }} +#proc wait_on_run {{ args }} {{ }} +#proc open_run {{ args }} {{ }} +#proc write_bitstream {{ args }} {{ }} +#proc close_project {{ args }} {{ }} +#proc current_bd_design {{ args }} {{ }} +#proc get_bd_cells {{ args }} {{ }} + + +tcl = f''' +proc unknown {{ cmmd 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, + #stdout=output, stderr=subprocess.STDOUT +) + +print(f'INFO:the {tool.upper()} mock has been executed') diff --git a/test/mocks/xtclsh b/test/mocks/xtclsh index 71046506..12046fa3 100755 --- a/test/mocks/xtclsh +++ b/test/mocks/xtclsh @@ -17,6 +17,8 @@ # import argparse +import subprocess + parser = argparse.ArgumentParser() @@ -24,4 +26,23 @@ parser.add_argument('source') args = parser.parse_args() -print(f'INFO:the {parser.prog.upper()} mock has been executed') +tool = parser.prog + +tcl = f''' +proc unknown {{ cmmd 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, + #stdout=output, stderr=subprocess.STDOUT +) + +print(f'INFO:the {tool.upper()} mock has been executed') From 4cc3513e05b2643475e174b53542e533c6719b77 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sat, 25 May 2024 23:51:41 -0300 Subject: [PATCH 015/254] Added a new WIP Project class under the pyfpga directory --- pyfpga/project.py | 119 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 pyfpga/project.py diff --git a/pyfpga/project.py b/pyfpga/project.py new file mode 100644 index 00000000..881a7187 --- /dev/null +++ b/pyfpga/project.py @@ -0,0 +1,119 @@ +# +# Copyright (C) 2019-2024 Rodrigo A. Melo +# +# SPDX-License-Identifier: GPL-3.0-or-later +# + +"""pyfpga.project +This module implements the entry-point of PyFPGA, which provides +functionalities to create a project, generate a bitstream and +program a device. +""" + +from pathlib import Path + +TASKS = ['prj', 'elb', 'syn', 'par', 'bit'] + +TOOLS = [ + 'ghdl', + 'ise', + 'libero', + 'openflow', + 'quartus', + 'vivado', + 'yosys', + 'yosys-ise', + 'yosys-vivado' +] + + +class Project: + """Class to manage an FPGA project. + + :param tool: tool name + :type tool: str + :param name: project name (tool name by default) + :type name: str, optional + :param data: pre-populated data for the project + :type data: dict, optional + :param odir: output directory + :type odir: str, optional + :raises NotImplementedError: unsupported tool + + .. note:: + Supported tool names are: + ``ghdl`` + ``ise`` + ``libero`` + ``openflow`` + ``quartus`` + ``vivado`` + ``yosys`` + ``yosys-ise`` + ``yosys-vivado`` + """ + + def __init__(self, tool, name=None, data=None, odir='results'): + """Class constructor.""" + if tool not in TOOLS: + raise NotImplementedError(f'unsupported tool ({tool}).') + self.tool = tool + self.name = name or tool + self.data = data or {} + self.odir = Path(odir) + self.odir.mkdir(parents=True, exist_ok=True) + + def set_part(self, name): + """Templ placeholder""" + raise NotImplementedError('Method is not implemented yet.') + + def add_file(self, pathname, filetype=None, library=None, options=None): + """Templ placeholder""" + raise NotImplementedError('Method is not implemented yet.') + + def add_vlog(self, pathname, options=None): + """Templ placeholder""" + self.add_file(pathname, filetype='vlog', options=options) + + def add_slog(self, pathname, options=None): + """Templ placeholder""" + self.add_file(pathname, filetype='slog', options=options) + + def add_vhdl(self, pathname, library=None, options=None): + """Templ placeholder""" + self.add_file( + pathname, filetype='vhdl', + library=library, options=options + ) + + def add_param(self, name, value): + """Templ placeholder""" + raise NotImplementedError('Method is not implemented yet.') + + def add_include(self, path): + """Templ placeholder""" + raise NotImplementedError('Method is not implemented yet.') + + def add_define(self, name, value): + """Templ placeholder""" + raise NotImplementedError('Method is not implemented yet.') + + def set_arch(self, name): + """Templ placeholder""" + raise NotImplementedError('Method is not implemented yet.') + + def set_top(self, name): + """Templ placeholder""" + raise NotImplementedError('Method is not implemented yet.') + + def add_hook(self, stage, hook): + """Templ placeholder""" + raise NotImplementedError('Method is not implemented yet.') + + def make(self, last='bit', first='prj', capture=False): + """Templ placeholder""" + raise NotImplementedError('Method is not implemented yet.') + + def prog(self, position=1, bitstream=None): + """Templ placeholder""" + raise NotImplementedError('Method is not implemented yet.') From a9a25b1e37753146b38a21d3723c1ee279341c0d Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sat, 25 May 2024 23:52:35 -0300 Subject: [PATCH 016/254] ci: updated, modified to analyze pyfpga instead of fpga --- .github/workflows/lint.yml | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 7904c2d7..61d227d9 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -7,12 +7,25 @@ jobs: lint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Install dependencies run: | pip install pycodestyle pip install pylint - name: Lint run: | - pycodestyle fpga examples test - pylint fpga + pycodestyle pyfpga examples test + pylint pyfpga + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + - name: Install dependencies + run: pip install pycodestyle pylint + - name: Run linters + run: | + pycodestyle pyfpga examples test + pylint pyfpga From 4b037ae4b5b9f55522ac00518c8aea1bbf23684a Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sat, 25 May 2024 23:57:13 -0300 Subject: [PATCH 017/254] ci: renamed doc as docs, disabled docs and test --- .github/workflows/{doc.yml => docs.yml} | 4 ++-- .github/workflows/test.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) rename .github/workflows/{doc.yml => docs.yml} (93%) diff --git a/.github/workflows/doc.yml b/.github/workflows/docs.yml similarity index 93% rename from .github/workflows/doc.yml rename to .github/workflows/docs.yml index ee0ecc54..a89f8848 100644 --- a/.github/workflows/doc.yml +++ b/.github/workflows/docs.yml @@ -1,11 +1,11 @@ -name: 'doc' +name: 'docs' on: push: branches: - main -jobs: +.jobs: linux: runs-on: ubuntu-latest steps: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9ff37c04..25761f18 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -6,7 +6,7 @@ on: schedule: # Run once a week to ensure tests pass with updated dependencies - cron: '0 0 * * 6' -jobs: +.jobs: test: strategy: matrix: From 828815976d1ed96cfe7666197e51e109a7d1589d Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sat, 25 May 2024 23:59:25 -0300 Subject: [PATCH 018/254] ci: fixed lint action --- .github/workflows/lint.yml | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 61d227d9..73ce2436 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -3,20 +3,6 @@ name: 'lint' on: push: -jobs: - lint: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Install dependencies - run: | - pip install pycodestyle - pip install pylint - - name: Lint - run: | - pycodestyle pyfpga examples test - pylint pyfpga - jobs: lint: runs-on: ubuntu-latest From 73476674de47524c6d6fc9246a9793d294c9e126 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sun, 26 May 2024 09:01:21 -0300 Subject: [PATCH 019/254] ci: updated Makefile and used for the lint action --- .github/workflows/lint.yml | 4 +--- Makefile | 8 +++++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 73ce2436..5f912102 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -12,6 +12,4 @@ jobs: - name: Install dependencies run: pip install pycodestyle pylint - name: Run linters - run: | - pycodestyle pyfpga examples test - pylint pyfpga + run: make lint diff --git a/Makefile b/Makefile index 8f1d8dc1..f8f3ca10 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,11 @@ #!/usr/bin/make -check: - pycodestyle fpga examples test - pylint -s n fpga +lint: + pycodestyle pyfpga examples test + pylint -s n pyfpga git diff --check --cached + +test: pytest test clean: From 4d845b679843cfd66df16fa3fe75df794ba3e9f5 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sun, 26 May 2024 13:42:54 -0300 Subject: [PATCH 020/254] Modified project to employ enumerations --- pyfpga/project.py | 104 ++++++++++++++++++++++++++-------------------- 1 file changed, 59 insertions(+), 45 deletions(-) diff --git a/pyfpga/project.py b/pyfpga/project.py index 881a7187..25da8911 100644 --- a/pyfpga/project.py +++ b/pyfpga/project.py @@ -10,110 +10,124 @@ program a device. """ +from enum import Enum from pathlib import Path TASKS = ['prj', 'elb', 'syn', 'par', 'bit'] -TOOLS = [ - 'ghdl', - 'ise', - 'libero', - 'openflow', - 'quartus', - 'vivado', - 'yosys', - 'yosys-ise', - 'yosys-vivado' -] + +class Tool(Enum): + """Enumeration of supported FPGA tools.""" + GHDL = 'ghdl' + ISE = 'ise' + LIBERO = 'libero' + OPENFLOW = 'openflow' + QUARTUS = 'quartus' + VIVADO = 'vivado' + YOSYS = 'yosys' + YOSYS_ISE = 'yosys-ise' + YOSYS_VIVADO = 'yosys-vivado' + + +class Step(Enum): + """Enumeration of supported Steps""" + PRJ = 'prj' + ELB = 'elb' + SYN = 'syn' + PAR = 'par' + BIT = 'bit' + + +class Hook(Enum): + """Enumeration of supported Hooks""" + PREFILE = 'prefile' + PROJECT = 'project' + PREFLOW = 'preflow' + POSTSYN = 'postsyn' + POSTPAR = 'postpar' + POSTBIT = 'postbit' class Project: """Class to manage an FPGA project. :param tool: tool name - :type tool: str + :type tool: Tool :param name: project name (tool name by default) :type name: str, optional :param data: pre-populated data for the project :type data: dict, optional :param odir: output directory :type odir: str, optional - :raises NotImplementedError: unsupported tool - - .. note:: - Supported tool names are: - ``ghdl`` - ``ise`` - ``libero`` - ``openflow`` - ``quartus`` - ``vivado`` - ``yosys`` - ``yosys-ise`` - ``yosys-vivado`` + :raises TypeError: when a value is not a valid enum + :raises NotImplementedError: when a method is not implemented yet """ def __init__(self, tool, name=None, data=None, odir='results'): """Class constructor.""" - if tool not in TOOLS: - raise NotImplementedError(f'unsupported tool ({tool}).') + if not isinstance(tool, Tool): + raise TypeError('tool must be a Tool enum.') self.tool = tool - self.name = name or tool + self.name = name or tool.value self.data = data or {} self.odir = Path(odir) self.odir.mkdir(parents=True, exist_ok=True) def set_part(self, name): - """Templ placeholder""" + """Temp placeholder""" raise NotImplementedError('Method is not implemented yet.') def add_file(self, pathname, filetype=None, library=None, options=None): - """Templ placeholder""" + """Temp placeholder""" raise NotImplementedError('Method is not implemented yet.') def add_vlog(self, pathname, options=None): - """Templ placeholder""" + """Temp placeholder""" self.add_file(pathname, filetype='vlog', options=options) def add_slog(self, pathname, options=None): - """Templ placeholder""" + """Temp placeholder""" self.add_file(pathname, filetype='slog', options=options) def add_vhdl(self, pathname, library=None, options=None): - """Templ placeholder""" + """Temp placeholder""" self.add_file( pathname, filetype='vhdl', library=library, options=options ) - def add_param(self, name, value): - """Templ placeholder""" + def add_include(self, path): + """Temp placeholder""" raise NotImplementedError('Method is not implemented yet.') - def add_include(self, path): - """Templ placeholder""" + def add_param(self, name, value): + """Temp placeholder""" raise NotImplementedError('Method is not implemented yet.') def add_define(self, name, value): - """Templ placeholder""" + """Temp placeholder""" raise NotImplementedError('Method is not implemented yet.') def set_arch(self, name): - """Templ placeholder""" + """Temp placeholder""" raise NotImplementedError('Method is not implemented yet.') def set_top(self, name): - """Templ placeholder""" + """Temp placeholder""" raise NotImplementedError('Method is not implemented yet.') - def add_hook(self, stage, hook): - """Templ placeholder""" + def add_hook(self, hook, content): + """Temp placeholder""" + # if not isinstance(hook, Hook): + # raise TypeError('hook must be a Hook enum.') raise NotImplementedError('Method is not implemented yet.') - def make(self, last='bit', first='prj', capture=False): - """Templ placeholder""" + def make(self, end=Step.BIT, start=Step.PRJ, capture=False): + """Temp placeholder""" + # if not isinstance(end, Step) or not isinstance(start, Step): + # raise TypeError('start and end must be a Step enum.') raise NotImplementedError('Method is not implemented yet.') def prog(self, position=1, bitstream=None): - """Templ placeholder""" + """Temp placeholder""" raise NotImplementedError('Method is not implemented yet.') From 049af6ba8b6d7654ddbd51ad5a6e26a2ce08eae4 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sun, 26 May 2024 13:43:32 -0300 Subject: [PATCH 021/254] Added docs with the skeleton generated by sphinx-quickstart --- docs/Makefile | 20 +++++++++++++++++++ docs/conf.py | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++ docs/index.rst | 20 +++++++++++++++++++ 3 files changed, 92 insertions(+) create mode 100644 docs/Makefile create mode 100644 docs/conf.py create mode 100644 docs/index.rst diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 00000000..d4bb2cbb --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = . +BUILDDIR = _build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 00000000..cddcb632 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,52 @@ +# Configuration file for the Sphinx documentation builder. +# +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +# import os +# import sys +# sys.path.insert(0, os.path.abspath('.')) + + +# -- Project information ----------------------------------------------------- + +project = 'PyFPGA' +copyright = '2024, Rodrigo Alejandro Melo' +author = 'Rodrigo Alejandro Melo' + + +# -- General configuration --------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = 'alabaster' + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] \ No newline at end of file diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 00000000..d2be65e2 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,20 @@ +.. PyFPGA documentation master file, created by + sphinx-quickstart on Sun May 26 13:21:40 2024. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to PyFPGA's documentation! +================================== + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` From 2f67bdeebc6786a8ea6fe39555cbd0c1ebf721f2 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sun, 26 May 2024 13:48:13 -0300 Subject: [PATCH 022/254] ci: updated docs --- .github/workflows/docs.yml | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index a89f8848..a794a243 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -3,17 +3,20 @@ name: 'docs' on: push: branches: - - main +# - main -.jobs: - linux: +jobs: + build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: buildthedocs/btd@v0 + - name: Checkout repository + uses: actions/checkout@v4 + - name: Install dependencies + run: pip install sphinx + - name: Build Sphinx documentation + run: cd docs; make html + - name: Deploy to GitHub Pages + uses: peaceiris/actions-gh-pages@v4 with: - token: ${{ secrets.GITHUB_TOKEN }} - - uses: actions/upload-artifact@master - with: - name: doc - path: doc/_build/html + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: docs/_build/html From 35534ebb012007b1f6456e8540c819bec60e8cfe Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sun, 26 May 2024 15:40:46 -0300 Subject: [PATCH 023/254] Moved content from doc/images into docs/images and docs/_static --- {doc/images => docs/_static}/logo.png | Bin {doc/images => docs/_static}/schema.png | Bin {doc => docs}/images/images.fodg | 0 {doc => docs}/images/logo.fodg | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename {doc/images => docs/_static}/logo.png (100%) rename {doc/images => docs/_static}/schema.png (100%) rename {doc => docs}/images/images.fodg (100%) rename {doc => docs}/images/logo.fodg (100%) diff --git a/doc/images/logo.png b/docs/_static/logo.png similarity index 100% rename from doc/images/logo.png rename to docs/_static/logo.png diff --git a/doc/images/schema.png b/docs/_static/schema.png similarity index 100% rename from doc/images/schema.png rename to docs/_static/schema.png diff --git a/doc/images/images.fodg b/docs/images/images.fodg similarity index 100% rename from doc/images/images.fodg rename to docs/images/images.fodg diff --git a/doc/images/logo.fodg b/docs/images/logo.fodg similarity index 100% rename from doc/images/logo.fodg rename to docs/images/logo.fodg From fe579279e7b277473732fbbd1303fbecefeceaf3 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sun, 26 May 2024 15:41:16 -0300 Subject: [PATCH 024/254] Moved doc contento to docs/wip --- doc/api.rst | 8 -------- {doc => docs/wip}/Makefile | 0 {doc => docs/wip}/advanced.rst | 0 {doc => docs/wip}/basic.rst | 0 {doc => docs/wip}/conf.py | 0 {doc => docs/wip}/dev.rst | 0 {doc => docs/wip}/index.rst | 0 {doc => docs/wip}/intro.rst | 2 +- {doc => docs/wip}/requirements.txt | 0 {doc => docs/wip}/tools.rst | 0 10 files changed, 1 insertion(+), 9 deletions(-) delete mode 100644 doc/api.rst rename {doc => docs/wip}/Makefile (100%) rename {doc => docs/wip}/advanced.rst (100%) rename {doc => docs/wip}/basic.rst (100%) rename {doc => docs/wip}/conf.py (100%) rename {doc => docs/wip}/dev.rst (100%) rename {doc => docs/wip}/index.rst (100%) rename {doc => docs/wip}/intro.rst (98%) rename {doc => docs/wip}/requirements.txt (100%) rename {doc => docs/wip}/tools.rst (100%) diff --git a/doc/api.rst b/doc/api.rst deleted file mode 100644 index 1be17600..00000000 --- a/doc/api.rst +++ /dev/null @@ -1,8 +0,0 @@ -.. program:: pyfpga - -.. _api: - -API Reference -############# - -.. automodule:: fpga.project diff --git a/doc/Makefile b/docs/wip/Makefile similarity index 100% rename from doc/Makefile rename to docs/wip/Makefile diff --git a/doc/advanced.rst b/docs/wip/advanced.rst similarity index 100% rename from doc/advanced.rst rename to docs/wip/advanced.rst diff --git a/doc/basic.rst b/docs/wip/basic.rst similarity index 100% rename from doc/basic.rst rename to docs/wip/basic.rst diff --git a/doc/conf.py b/docs/wip/conf.py similarity index 100% rename from doc/conf.py rename to docs/wip/conf.py diff --git a/doc/dev.rst b/docs/wip/dev.rst similarity index 100% rename from doc/dev.rst rename to docs/wip/dev.rst diff --git a/doc/index.rst b/docs/wip/index.rst similarity index 100% rename from doc/index.rst rename to docs/wip/index.rst diff --git a/doc/intro.rst b/docs/wip/intro.rst similarity index 98% rename from doc/intro.rst rename to docs/wip/intro.rst index 67c8e087..b10d326a 100644 --- a/doc/intro.rst +++ b/docs/wip/intro.rst @@ -84,4 +84,4 @@ Detailed support Next Steps ---------- -You can read the :ref:`basic` and :ref:`advanced` sections, check the detailed :ref:`api` or start with the available :repo:`Examples `. +You can read the :ref:`basic` and :ref:`advanced` sections, check the detailed :ref:`api` or start with the available :repositoy:`Examples `. diff --git a/doc/requirements.txt b/docs/wip/requirements.txt similarity index 100% rename from doc/requirements.txt rename to docs/wip/requirements.txt diff --git a/doc/tools.rst b/docs/wip/tools.rst similarity index 100% rename from doc/tools.rst rename to docs/wip/tools.rst From 0717dc143b9d722da00512d49c69220fb7685ec1 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sun, 26 May 2024 15:43:08 -0300 Subject: [PATCH 025/254] docs: added intro (empty) and api (automodule) --- docs/api.rst | 6 ++++++ docs/conf.py | 45 +++++++++++++++++---------------------------- docs/index.rst | 26 ++++++++++---------------- docs/intro.rst | 4 ++++ 4 files changed, 37 insertions(+), 44 deletions(-) create mode 100644 docs/api.rst create mode 100644 docs/intro.rst diff --git a/docs/api.rst b/docs/api.rst new file mode 100644 index 00000000..b3e29cd3 --- /dev/null +++ b/docs/api.rst @@ -0,0 +1,6 @@ +.. program:: pyfpga + +API Reference +============= + +.. automodule:: pyfpga.project diff --git a/docs/conf.py b/docs/conf.py index cddcb632..10511f00 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,19 +1,4 @@ -# Configuration file for the Sphinx documentation builder. -# -# This file only contains a selection of the most common options. For a full -# list see the documentation: -# https://www.sphinx-doc.org/en/master/usage/configuration.html - -# -- Path setup -------------------------------------------------------------- - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -# -# import os -# import sys -# sys.path.insert(0, os.path.abspath('.')) - +# -*- coding: utf-8 -*- # -- Project information ----------------------------------------------------- @@ -21,32 +6,36 @@ copyright = '2024, Rodrigo Alejandro Melo' author = 'Rodrigo Alejandro Melo' - # -- General configuration --------------------------------------------------- -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.extlinks', + 'sphinx.ext.intersphinx', + 'sphinx.ext.todo', + 'sphinx.ext.viewcode', ] -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +autodoc_default_options = { + "members": True, + 'undoc-members': True, + 'inherited-members': True, +} -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -# This pattern also affects html_static_path and html_extra_path. -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] +extlinks = { + 'repositoy': ('https://github.com/PyFPGA/pyfpga/tree/main/%s', None) +} +exclude_patterns = ['_build', 'wip'] # -- Options for HTML output ------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # -html_theme = 'alabaster' +html_theme = 'sphinx_rtd_theme' # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] \ No newline at end of file +html_static_path = ['_static'] diff --git a/docs/index.rst b/docs/index.rst index d2be65e2..2c9d734c 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,20 +1,14 @@ -.. PyFPGA documentation master file, created by - sphinx-quickstart on Sun May 26 13:21:40 2024. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. +.. program:: pyfpga -Welcome to PyFPGA's documentation! -================================== - -.. toctree:: - :maxdepth: 2 - :caption: Contents: +PyFPGA's documentation +====================== +.. image:: _static/logo.png + :width: 200 px + :align: center + :target: https://github.com/PyFPGA/pyfpga +.. toctree:: -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` + intro + api diff --git a/docs/intro.rst b/docs/intro.rst new file mode 100644 index 00000000..63b4e29e --- /dev/null +++ b/docs/intro.rst @@ -0,0 +1,4 @@ +.. program:: pyfpga + +Introduction +============ From 33bfdcc0a3c949d377b4ca2063810d89fcb8ab78 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sun, 26 May 2024 15:43:34 -0300 Subject: [PATCH 026/254] ci: updated/enabled docs --- .github/workflows/docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index a794a243..c1dabef5 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -12,7 +12,7 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 - name: Install dependencies - run: pip install sphinx + run: pip install sphinx sphinx-rtd-theme - name: Build Sphinx documentation run: cd docs; make html - name: Deploy to GitHub Pages From ece35b05f221a95f68d922be5bd284cdba27e1da Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sun, 26 May 2024 15:43:54 -0300 Subject: [PATCH 027/254] Removed unused config files --- .btd.yml | 5 ----- .pylintrc | 16 ---------------- 2 files changed, 21 deletions(-) delete mode 100644 .btd.yml delete mode 100644 .pylintrc diff --git a/.btd.yml b/.btd.yml deleted file mode 100644 index a9be9d62..00000000 --- a/.btd.yml +++ /dev/null @@ -1,5 +0,0 @@ -input: doc -output: _build -target: gh-pages -formats: [ html ] -theme: https://codeload.github.com/buildthedocs/sphinx.theme/tar.gz/v0 diff --git a/.pylintrc b/.pylintrc deleted file mode 100644 index 40d501d1..00000000 --- a/.pylintrc +++ /dev/null @@ -1,16 +0,0 @@ -[MASTER] -ignore=ignore -jobs=0 -suggestion-mode=yes - -[REPORTS] -score=no - -[MESSAGES CONTROL] -disable=duplicate-code, - import-outside-toplevel, - raise-missing-from, - too-many-arguments, - too-many-branches, - too-many-instance-attributes, - too-many-locals From c2957c0740a4f41857be713e88c22a3d76777943 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sun, 26 May 2024 15:47:57 -0300 Subject: [PATCH 028/254] ci: updated docs and lint to be similar --- .github/workflows/docs.yml | 4 ++-- .github/workflows/lint.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index c1dabef5..0db33e9b 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -6,14 +6,14 @@ on: # - main jobs: - build: + docs: runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v4 - name: Install dependencies run: pip install sphinx sphinx-rtd-theme - - name: Build Sphinx documentation + - name: Build documentation run: cd docs; make html - name: Deploy to GitHub Pages uses: peaceiris/actions-gh-pages@v4 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 5f912102..3907a5c4 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -7,7 +7,7 @@ jobs: lint: runs-on: ubuntu-latest steps: - - name: Checkout code + - name: Checkout repository uses: actions/checkout@v4 - name: Install dependencies run: pip install pycodestyle pylint From 3d29a99c2f854764074716a54783449da606b673 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sun, 26 May 2024 15:55:30 -0300 Subject: [PATCH 029/254] docs: fixed to find pyfpga.project --- docs/conf.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 10511f00..3e57d268 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,5 +1,10 @@ # -*- coding: utf-8 -*- +import sys, re +from pathlib import Path + +sys.path.insert(0, str(Path.cwd().resolve().parent)) + # -- Project information ----------------------------------------------------- project = 'PyFPGA' @@ -30,12 +35,5 @@ # -- Options for HTML output ------------------------------------------------- -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -# html_theme = 'sphinx_rtd_theme' - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] From fa24be14db12618a4632b68125d29f7493d7d3a9 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sun, 26 May 2024 19:25:14 -0300 Subject: [PATCH 030/254] Implemented some simple methods of project.py --- Makefile | 4 +++- pyfpga/project.py | 22 ++++++++++++++-------- test/test_data.py | 38 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 55 insertions(+), 9 deletions(-) create mode 100644 test/test_data.py diff --git a/Makefile b/Makefile index f8f3ca10..ca7165f0 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,7 @@ #!/usr/bin/make +.PHONY: test + lint: pycodestyle pyfpga examples test pylint -s n pyfpga @@ -13,4 +15,4 @@ clean: rm -fr build .pytest_cache submodule: - git submodule update --init + git submodule update --init diff --git a/pyfpga/project.py b/pyfpga/project.py index 25da8911..778df51f 100644 --- a/pyfpga/project.py +++ b/pyfpga/project.py @@ -13,8 +13,6 @@ from enum import Enum from pathlib import Path -TASKS = ['prj', 'elb', 'syn', 'par', 'bit'] - class Tool(Enum): """Enumeration of supported FPGA tools.""" @@ -67,6 +65,8 @@ def __init__(self, tool, name=None, data=None, odir='results'): """Class constructor.""" if not isinstance(tool, Tool): raise TypeError('tool must be a Tool enum.') + if data and not isinstance(data, dict): + raise TypeError('data must be a dict.') self.tool = tool self.name = name or tool.value self.data = data or {} @@ -75,7 +75,7 @@ def __init__(self, tool, name=None, data=None, odir='results'): def set_part(self, name): """Temp placeholder""" - raise NotImplementedError('Method is not implemented yet.') + self.data['part'] = name def add_file(self, pathname, filetype=None, library=None, options=None): """Temp placeholder""" @@ -98,23 +98,29 @@ def add_vhdl(self, pathname, library=None, options=None): def add_include(self, path): """Temp placeholder""" - raise NotImplementedError('Method is not implemented yet.') + if 'includes' not in self.data: + self.data['includes'] = [] + self.data['includes'].append(path) def add_param(self, name, value): """Temp placeholder""" - raise NotImplementedError('Method is not implemented yet.') + if 'params' not in self.data: + self.data['params'] = {} + self.data['params'][name] = value def add_define(self, name, value): """Temp placeholder""" - raise NotImplementedError('Method is not implemented yet.') + if 'defines' not in self.data: + self.data['defines'] = {} + self.data['defines'][name] = value def set_arch(self, name): """Temp placeholder""" - raise NotImplementedError('Method is not implemented yet.') + self.data['arch'] = name def set_top(self, name): """Temp placeholder""" - raise NotImplementedError('Method is not implemented yet.') + self.data['top'] = name def add_hook(self, hook, content): """Temp placeholder""" diff --git a/test/test_data.py b/test/test_data.py new file mode 100644 index 00000000..b1d03546 --- /dev/null +++ b/test/test_data.py @@ -0,0 +1,38 @@ +import os +import pytest + +from pyfpga.project import Project, Tool + +pattern = { + 'part': 'PARTNAME', + 'top': 'TOPNAME', + 'arch': 'ARCHNAME', + 'includes': ['INC1', 'INC2', 'INC3'], + 'params': { + 'PARAM1': 'VALUE1', + 'PARAM2': 'VALUE2', + 'PARAM3': 'VALUE3' + }, + 'defines': { + 'DEF1': 'VALUE1', + 'DEF2': 'VALUE2', + 'DEF3': 'VALUE3' + }, +} + + +def test_names(): + prj = Project(Tool.VIVADO) + prj.set_part('PARTNAME') + prj.set_top('TOPNAME') + prj.set_arch('ARCHNAME') + prj.add_include('INC1') + prj.add_include('INC2') + prj.add_include('INC3') + prj.add_param('PARAM1', 'VALUE1') + prj.add_param('PARAM2', 'VALUE2') + prj.add_param('PARAM3', 'VALUE3') + prj.add_define('DEF1', 'VALUE1') + prj.add_define('DEF2', 'VALUE2') + prj.add_define('DEF3', 'VALUE3') + assert prj.data == pattern, 'ERROR: unexpected data' From c545884c04d2e717e7ca1dd27d60d8035432dbdb Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sun, 26 May 2024 19:25:33 -0300 Subject: [PATCH 031/254] ci: simplified/enabled test --- .github/workflows/test.yml | 26 ++++++-------------------- Makefile | 2 +- {fpga => pyfpga}/__init__.py | 2 -- 3 files changed, 7 insertions(+), 23 deletions(-) rename {fpga => pyfpga}/__init__.py (54%) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 25761f18..7581de65 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -2,40 +2,26 @@ name: 'test' on: push: - pull_request: - schedule: # Run once a week to ensure tests pass with updated dependencies - - cron: '0 0 * * 6' -.jobs: +jobs: test: strategy: matrix: os: ['ubuntu'] - pyver: ['3.6', '3.7', '3.8', '3.9', '3.10'] + pyver: [3.8, 3.9, 3.10, 3.11, 3.12] runs-on: ${{ matrix.os }}-latest name: ${{ matrix.os }} | ${{ matrix.pyver }} steps: - - uses: actions/checkout@v2 + - name: Checkout repository + uses: actions/checkout@v4 with: submodules: true fetch-depth: 0 - - name: Pull container images - run: | - docker pull hdlc/ghdl:yosys - docker pull hdlc/nextpnr:ice40 - docker pull hdlc/nextpnr:ecp5 - docker pull hdlc/icestorm - docker pull hdlc/prjtrellis - name: Set up Python ${{ matrix.pyver }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.pyver }} - name: Install dependencies - run: | - pip install pytest - pip install . + run: pip install pytest - name: Run test run: pytest - - name: Run examples - run: | - cd examples; make MOCKS=1 diff --git a/Makefile b/Makefile index ca7165f0..0302d56e 100644 --- a/Makefile +++ b/Makefile @@ -8,7 +8,7 @@ lint: git diff --check --cached test: - pytest test + pytest clean: py3clean . diff --git a/fpga/__init__.py b/pyfpga/__init__.py similarity index 54% rename from fpga/__init__.py rename to pyfpga/__init__.py index bd3bc779..b9bae46e 100644 --- a/fpga/__init__.py +++ b/pyfpga/__init__.py @@ -1,5 +1,3 @@ """PyFPGA""" __version__ = '0.3.0-dev' - -from fpga.project import Project From d886fb30c58df9623847c734fcedc52a83a9bb94 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Mon, 27 May 2024 22:33:55 -0300 Subject: [PATCH 032/254] Added the target docs at Makefile --- .github/workflows/docs.yml | 2 +- Makefile | 6 +++++- docs/api.rst | 2 -- docs/index.rst | 2 -- docs/intro.rst | 2 -- 5 files changed, 6 insertions(+), 8 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 0db33e9b..26c51962 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -14,7 +14,7 @@ jobs: - name: Install dependencies run: pip install sphinx sphinx-rtd-theme - name: Build documentation - run: cd docs; make html + run: make docs - name: Deploy to GitHub Pages uses: peaceiris/actions-gh-pages@v4 with: diff --git a/Makefile b/Makefile index 0302d56e..afcec92e 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,9 @@ #!/usr/bin/make -.PHONY: test +.PHONY: docs test + +docs: + cd docs; make html lint: pycodestyle pyfpga examples test @@ -12,6 +15,7 @@ test: clean: py3clean . + cd docs; make clean rm -fr build .pytest_cache submodule: diff --git a/docs/api.rst b/docs/api.rst index b3e29cd3..ef62c294 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -1,5 +1,3 @@ -.. program:: pyfpga - API Reference ============= diff --git a/docs/index.rst b/docs/index.rst index 2c9d734c..dd21b211 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,5 +1,3 @@ -.. program:: pyfpga - PyFPGA's documentation ====================== diff --git a/docs/intro.rst b/docs/intro.rst index 63b4e29e..c516b331 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -1,4 +1,2 @@ -.. program:: pyfpga - Introduction ============ From 5d798be15dff72c813eec46cd54a946d442d5555 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Mon, 27 May 2024 22:37:50 -0300 Subject: [PATCH 033/254] Renamed test to tests --- .github/workflows/test.yml | 4 ++-- Makefile | 4 ++-- {test => tests}/mocks/impact | 0 {test => tests}/mocks/libero | 0 {test => tests}/mocks/quartus_pgm | 0 {test => tests}/mocks/quartus_sh | 0 {test => tests}/mocks/vivado | 0 {test => tests}/mocks/xtclsh | 0 {test => tests}/test_data.py | 0 {test => tests}/test_files.py | 0 {test => tests}/test_part.py | 0 {test => tests}/test_top.py | 0 12 files changed, 4 insertions(+), 4 deletions(-) rename {test => tests}/mocks/impact (100%) rename {test => tests}/mocks/libero (100%) rename {test => tests}/mocks/quartus_pgm (100%) rename {test => tests}/mocks/quartus_sh (100%) rename {test => tests}/mocks/vivado (100%) rename {test => tests}/mocks/xtclsh (100%) rename {test => tests}/test_data.py (100%) rename {test => tests}/test_files.py (100%) rename {test => tests}/test_part.py (100%) rename {test => tests}/test_top.py (100%) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7581de65..6cd55c21 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -23,5 +23,5 @@ jobs: python-version: ${{ matrix.pyver }} - name: Install dependencies run: pip install pytest - - name: Run test - run: pytest + - name: Run tests + run: make test diff --git a/Makefile b/Makefile index afcec92e..3e3d9555 100644 --- a/Makefile +++ b/Makefile @@ -1,12 +1,12 @@ #!/usr/bin/make -.PHONY: docs test +.PHONY: docs docs: cd docs; make html lint: - pycodestyle pyfpga examples test + pycodestyle pyfpga examples tests pylint -s n pyfpga git diff --check --cached diff --git a/test/mocks/impact b/tests/mocks/impact similarity index 100% rename from test/mocks/impact rename to tests/mocks/impact diff --git a/test/mocks/libero b/tests/mocks/libero similarity index 100% rename from test/mocks/libero rename to tests/mocks/libero diff --git a/test/mocks/quartus_pgm b/tests/mocks/quartus_pgm similarity index 100% rename from test/mocks/quartus_pgm rename to tests/mocks/quartus_pgm diff --git a/test/mocks/quartus_sh b/tests/mocks/quartus_sh similarity index 100% rename from test/mocks/quartus_sh rename to tests/mocks/quartus_sh diff --git a/test/mocks/vivado b/tests/mocks/vivado similarity index 100% rename from test/mocks/vivado rename to tests/mocks/vivado diff --git a/test/mocks/xtclsh b/tests/mocks/xtclsh similarity index 100% rename from test/mocks/xtclsh rename to tests/mocks/xtclsh diff --git a/test/test_data.py b/tests/test_data.py similarity index 100% rename from test/test_data.py rename to tests/test_data.py diff --git a/test/test_files.py b/tests/test_files.py similarity index 100% rename from test/test_files.py rename to tests/test_files.py diff --git a/test/test_part.py b/tests/test_part.py similarity index 100% rename from test/test_part.py rename to tests/test_part.py diff --git a/test/test_top.py b/tests/test_top.py similarity index 100% rename from test/test_top.py rename to tests/test_top.py From 833cb4ae3a836d22290b70c7b87498320a66771e Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Mon, 27 May 2024 23:03:58 -0300 Subject: [PATCH 034/254] Removed tool and data from Project --- pyfpga/project.py | 41 ++++++----------------------------------- tests/test_data.py | 4 ++-- 2 files changed, 8 insertions(+), 37 deletions(-) diff --git a/pyfpga/project.py b/pyfpga/project.py index 778df51f..a963bea7 100644 --- a/pyfpga/project.py +++ b/pyfpga/project.py @@ -4,29 +4,14 @@ # SPDX-License-Identifier: GPL-3.0-or-later # -"""pyfpga.project -This module implements the entry-point of PyFPGA, which provides -functionalities to create a project, generate a bitstream and -program a device. +""" +Base class that implements agnostic methods to deal with FPGA projects. """ from enum import Enum from pathlib import Path -class Tool(Enum): - """Enumeration of supported FPGA tools.""" - GHDL = 'ghdl' - ISE = 'ise' - LIBERO = 'libero' - OPENFLOW = 'openflow' - QUARTUS = 'quartus' - VIVADO = 'vivado' - YOSYS = 'yosys' - YOSYS_ISE = 'yosys-ise' - YOSYS_VIVADO = 'yosys-vivado' - - class Step(Enum): """Enumeration of supported Steps""" PRJ = 'prj' @@ -47,29 +32,19 @@ class Hook(Enum): class Project: - """Class to manage an FPGA project. + """Base class to manage an FPGA project. - :param tool: tool name - :type tool: Tool :param name: project name (tool name by default) :type name: str, optional - :param data: pre-populated data for the project - :type data: dict, optional :param odir: output directory :type odir: str, optional - :raises TypeError: when a value is not a valid enum :raises NotImplementedError: when a method is not implemented yet """ - def __init__(self, tool, name=None, data=None, odir='results'): + def __init__(self, name=None, odir='results'): """Class constructor.""" - if not isinstance(tool, Tool): - raise TypeError('tool must be a Tool enum.') - if data and not isinstance(data, dict): - raise TypeError('data must be a dict.') - self.tool = tool - self.name = name or tool.value - self.data = data or {} + self.data = {} + self.name = name self.odir = Path(odir) self.odir.mkdir(parents=True, exist_ok=True) @@ -124,14 +99,10 @@ def set_top(self, name): def add_hook(self, hook, content): """Temp placeholder""" - # if not isinstance(hook, Hook): - # raise TypeError('hook must be a Hook enum.') raise NotImplementedError('Method is not implemented yet.') def make(self, end=Step.BIT, start=Step.PRJ, capture=False): """Temp placeholder""" - # if not isinstance(end, Step) or not isinstance(start, Step): - # raise TypeError('start and end must be a Step enum.') raise NotImplementedError('Method is not implemented yet.') def prog(self, position=1, bitstream=None): diff --git a/tests/test_data.py b/tests/test_data.py index b1d03546..e603af21 100644 --- a/tests/test_data.py +++ b/tests/test_data.py @@ -1,7 +1,7 @@ import os import pytest -from pyfpga.project import Project, Tool +from pyfpga.project import Project pattern = { 'part': 'PARTNAME', @@ -22,7 +22,7 @@ def test_names(): - prj = Project(Tool.VIVADO) + prj = Project() prj.set_part('PARTNAME') prj.set_top('TOPNAME') prj.set_arch('ARCHNAME') From 292e6ea4560111eb90082818fc1eddcf62f80976 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Mon, 27 May 2024 23:12:19 -0300 Subject: [PATCH 035/254] ci: attempt to fix test --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6cd55c21..061381db 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -8,7 +8,7 @@ jobs: strategy: matrix: os: ['ubuntu'] - pyver: [3.8, 3.9, 3.10, 3.11, 3.12] + pyver: ['3.8', '3.9', '3.10', '3.11', '3.12'] runs-on: ${{ matrix.os }}-latest name: ${{ matrix.os }} | ${{ matrix.pyver }} steps: From 13b5169914db1956096d45b5ba929f7cff33fd7e Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Mon, 27 May 2024 23:38:30 -0300 Subject: [PATCH 036/254] Added add_cons for constraint files --- pyfpga/project.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pyfpga/project.py b/pyfpga/project.py index a963bea7..231fcb2c 100644 --- a/pyfpga/project.py +++ b/pyfpga/project.py @@ -56,9 +56,9 @@ def add_file(self, pathname, filetype=None, library=None, options=None): """Temp placeholder""" raise NotImplementedError('Method is not implemented yet.') - def add_vlog(self, pathname, options=None): + def add_cons(self, pathname, options=None): """Temp placeholder""" - self.add_file(pathname, filetype='vlog', options=options) + self.add_file(pathname, filetype='cons', options=options) def add_slog(self, pathname, options=None): """Temp placeholder""" @@ -71,6 +71,10 @@ def add_vhdl(self, pathname, library=None, options=None): library=library, options=options ) + def add_vlog(self, pathname, options=None): + """Temp placeholder""" + self.add_file(pathname, filetype='vlog', options=options) + def add_include(self, path): """Temp placeholder""" if 'includes' not in self.data: From f6958f6f90ef478e7e66814eab6572dfa4377dad Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Tue, 28 May 2024 21:40:51 -0300 Subject: [PATCH 037/254] Added logging into the new Project class and an example --- examples/misc/logging.py | 25 +++++++++++++++++++++++++ pyfpga/project.py | 16 ++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 examples/misc/logging.py diff --git a/examples/misc/logging.py b/examples/misc/logging.py new file mode 100644 index 00000000..55a214c7 --- /dev/null +++ b/examples/misc/logging.py @@ -0,0 +1,25 @@ +""" +This script demonstrates how to utilize the logging functionality within the +pyfpga package. The following steps are covered: + +1. Creating an instance of the Project class. +2. Testing logging with the default INFO level. +3. Setting the logging level to DEBUG to capture more detailed information. +4. Disabling logging by removing all handlers. + +Usage: +- By default, the logger captures messages with level INFO and higher. +- To see more detailed debug information, set the logger level to DEBUG. +- To disable logging, remove all handlers from the logger. +""" + +import logging + +from pyfpga.project import Project + +prj = Project() +prj._test_logging() +prj.logger.setLevel(logging.DEBUG) +prj._test_logging() +prj.logger.handlers = [] +prj._test_logging() diff --git a/pyfpga/project.py b/pyfpga/project.py index 231fcb2c..61d772f5 100644 --- a/pyfpga/project.py +++ b/pyfpga/project.py @@ -8,6 +8,8 @@ Base class that implements agnostic methods to deal with FPGA projects. """ +import logging + from enum import Enum from pathlib import Path @@ -47,6 +49,16 @@ def __init__(self, name=None, odir='results'): self.name = name self.odir = Path(odir) self.odir.mkdir(parents=True, exist_ok=True) + # logging config + self.logger = logging.getLogger(self.__class__.__name__) + self.logger.setLevel(logging.INFO) + handler = logging.StreamHandler() + formatter = logging.Formatter( + '%(asctime)s - %(levelname)s - %(message)s', + datefmt='%Y-%m-%d %H:%M:%S' + ) + handler.setFormatter(formatter) + self.logger.addHandler(handler) def set_part(self, name): """Temp placeholder""" @@ -112,3 +124,7 @@ def make(self, end=Step.BIT, start=Step.PRJ, capture=False): def prog(self, position=1, bitstream=None): """Temp placeholder""" raise NotImplementedError('Method is not implemented yet.') + + def _test_logging(self): + self.logger.info('It is an INFO message') + self.logger.debug('It is anDEBUG message') From 9bcc17074d295e622a54f0569794a63c5248fb11 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Tue, 28 May 2024 21:48:41 -0300 Subject: [PATCH 038/254] Added a Makefile target to update the resources module --- Makefile | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 3e3d9555..351413fc 100644 --- a/Makefile +++ b/Makefile @@ -18,5 +18,8 @@ clean: cd docs; make clean rm -fr build .pytest_cache -submodule: +submodule-init: git submodule update --init + +submodule-update: + cd resources; git checkout main; git pull From 1b2fbe97d43dbe5d883577c43b28ea2c9b57faf9 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Tue, 28 May 2024 23:30:36 -0300 Subject: [PATCH 039/254] Renamed logging.py as logger.py (examples/misc) to avoid circular dependencies --- examples/misc/{logging.py => logger.py} | 6 +++--- pyfpga/project.py | 14 ++++++++++---- 2 files changed, 13 insertions(+), 7 deletions(-) rename examples/misc/{logging.py => logger.py} (90%) diff --git a/examples/misc/logging.py b/examples/misc/logger.py similarity index 90% rename from examples/misc/logging.py rename to examples/misc/logger.py index 55a214c7..7f34ca1c 100644 --- a/examples/misc/logging.py +++ b/examples/misc/logger.py @@ -18,8 +18,8 @@ from pyfpga.project import Project prj = Project() -prj._test_logging() +prj.set_part('EXAMPLE') prj.logger.setLevel(logging.DEBUG) -prj._test_logging() +prj.set_part('EXAMPLE') prj.logger.handlers = [] -prj._test_logging() +prj.set_part('EXAMPLE') diff --git a/pyfpga/project.py b/pyfpga/project.py index 61d772f5..59a9e016 100644 --- a/pyfpga/project.py +++ b/pyfpga/project.py @@ -62,6 +62,7 @@ def __init__(self, name=None, odir='results'): def set_part(self, name): """Temp placeholder""" + self.logger.debug('Executing set_part') self.data['part'] = name def add_file(self, pathname, filetype=None, library=None, options=None): @@ -70,14 +71,17 @@ def add_file(self, pathname, filetype=None, library=None, options=None): def add_cons(self, pathname, options=None): """Temp placeholder""" + self.logger.debug('Executing add_cons') self.add_file(pathname, filetype='cons', options=options) def add_slog(self, pathname, options=None): """Temp placeholder""" + self.logger.debug('Executing add_slog') self.add_file(pathname, filetype='slog', options=options) def add_vhdl(self, pathname, library=None, options=None): """Temp placeholder""" + self.logger.debug('Executing add_vhdl') self.add_file( pathname, filetype='vhdl', library=library, options=options @@ -85,32 +89,38 @@ def add_vhdl(self, pathname, library=None, options=None): def add_vlog(self, pathname, options=None): """Temp placeholder""" + self.logger.debug('Executing add_vlog') self.add_file(pathname, filetype='vlog', options=options) def add_include(self, path): """Temp placeholder""" + self.logger.debug('Executing add_include') if 'includes' not in self.data: self.data['includes'] = [] self.data['includes'].append(path) def add_param(self, name, value): """Temp placeholder""" + self.logger.debug('Executing add_param') if 'params' not in self.data: self.data['params'] = {} self.data['params'][name] = value def add_define(self, name, value): """Temp placeholder""" + self.logger.debug('Executing add_define') if 'defines' not in self.data: self.data['defines'] = {} self.data['defines'][name] = value def set_arch(self, name): """Temp placeholder""" + self.logger.debug('Executing set_arch') self.data['arch'] = name def set_top(self, name): """Temp placeholder""" + self.logger.debug('Executing set_top') self.data['top'] = name def add_hook(self, hook, content): @@ -124,7 +134,3 @@ def make(self, end=Step.BIT, start=Step.PRJ, capture=False): def prog(self, position=1, bitstream=None): """Temp placeholder""" raise NotImplementedError('Method is not implemented yet.') - - def _test_logging(self): - self.logger.info('It is an INFO message') - self.logger.debug('It is anDEBUG message') From abbb2b577891ab758b636487fddd7a6801151e9e Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Wed, 29 May 2024 23:11:27 -0300 Subject: [PATCH 040/254] Added a private method to run the underlaying tool --- pyfpga/project.py | 42 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/pyfpga/project.py b/pyfpga/project.py index 59a9e016..a33cb563 100644 --- a/pyfpga/project.py +++ b/pyfpga/project.py @@ -9,9 +9,13 @@ """ import logging +import os +import subprocess from enum import Enum +from datetime import datetime from pathlib import Path +from time import time class Step(Enum): @@ -47,8 +51,8 @@ def __init__(self, name=None, odir='results'): """Class constructor.""" self.data = {} self.name = name - self.odir = Path(odir) - self.odir.mkdir(parents=True, exist_ok=True) + self.odir = odir + # self.odir.mkdir(parents=True, exist_ok=True) # logging config self.logger = logging.getLogger(self.__class__.__name__) self.logger.setLevel(logging.INFO) @@ -134,3 +138,37 @@ def make(self, end=Step.BIT, start=Step.PRJ, capture=False): def prog(self, position=1, bitstream=None): """Temp placeholder""" raise NotImplementedError('Method is not implemented yet.') + + def _run(self, command): + self.logger.info('Running the underlying tool (%s)', datetime.now()) + run_error = 0 + old_dir = Path.cwd() + new_dir = Path(self.odir) + start = time.time() + try: + os.chdir(new_dir) + with open('run.log', 'w', encoding='utf-8') as logfile: + subprocess.run( + command, shell=True, check=True, text=True, + stdout=logfile, stderr=subprocess.STDOUT + ) + except subprocess.CalledProcessError: + with open('run.log', 'r', encoding='utf-8') as logfile: + lines = logfile.readlines() + last_lines = lines[-10:] if len(lines) >= 10 else lines + for line in last_lines: + self.logger.error(line.strip()) + run_error = 1 + finally: + os.chdir(old_dir) + end = time.time() + self.logger.info('Done (%s)', datetime.now()) + elapsed = end - start + self.logger.info( + 'Elapsed time %dh %dm %.2fs', + int(elapsed // 3600), + int((elapsed % 3600) // 60), + elapsed % 60 + ) + if run_error: + raise RuntimeError('Error running the underlying tool') From 1b1f5a2abaa27107d2c611fc66b0c392d2ac0b49 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Wed, 29 May 2024 23:50:13 -0300 Subject: [PATCH 041/254] Replaced add_hook by a one functions for each hook --- pyfpga/project.py | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/pyfpga/project.py b/pyfpga/project.py index a33cb563..912a61e8 100644 --- a/pyfpga/project.py +++ b/pyfpga/project.py @@ -21,22 +21,11 @@ class Step(Enum): """Enumeration of supported Steps""" PRJ = 'prj' - ELB = 'elb' SYN = 'syn' PAR = 'par' BIT = 'bit' -class Hook(Enum): - """Enumeration of supported Hooks""" - PREFILE = 'prefile' - PROJECT = 'project' - PREFLOW = 'preflow' - POSTSYN = 'postsyn' - POSTPAR = 'postpar' - POSTBIT = 'postbit' - - class Project: """Base class to manage an FPGA project. @@ -127,7 +116,27 @@ def set_top(self, name): self.logger.debug('Executing set_top') self.data['top'] = name - def add_hook(self, hook, content): + def add_precfg_hook(self, content): + """Temp placeholder""" + raise NotImplementedError('Method is not implemented yet.') + + def add_postcfg_hook(self, content): + """Temp placeholder""" + raise NotImplementedError('Method is not implemented yet.') + + def add_presyn_hook(self, content): + """Temp placeholder""" + raise NotImplementedError('Method is not implemented yet.') + + def add_prepar_hook(self, content): + """Temp placeholder""" + raise NotImplementedError('Method is not implemented yet.') + + def add_prebit_hook(self, content): + """Temp placeholder""" + raise NotImplementedError('Method is not implemented yet.') + + def add_postbit_hook(self, content): """Temp placeholder""" raise NotImplementedError('Method is not implemented yet.') From 992cfab25705ace6051e0bb3e8e58b7b07378400 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Wed, 29 May 2024 23:50:40 -0300 Subject: [PATCH 042/254] docs: added hooks --- docs/hooks.rst | 27 +++++++++++++++++++++++++++ docs/index.rst | 1 + 2 files changed, 28 insertions(+) create mode 100644 docs/hooks.rst diff --git a/docs/hooks.rst b/docs/hooks.rst new file mode 100644 index 00000000..a9328332 --- /dev/null +++ b/docs/hooks.rst @@ -0,0 +1,27 @@ +Hooks +===== + +.. code-block:: + + create project + config project + part + precfg hook + params + defines + includes + files + arch + top + postcfg hook + close project + + open project + presyn hook + synthesis + prepar hook + place_and_route + prebit hook + bitstream + postbit hook + close project diff --git a/docs/index.rst b/docs/index.rst index dd21b211..69643350 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -9,4 +9,5 @@ PyFPGA's documentation .. toctree:: intro + hooks api From b9700cd6226a007337d5be8293d6989eaf0e96cb Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Thu, 30 May 2024 00:06:18 -0300 Subject: [PATCH 043/254] Updated the NOTICE about PyFPGA being rewritten --- README.md | 4 +-- docs/index.rst | 5 ++++ docs/wip/Makefile | 57 --------------------------------------- docs/wip/index.rst | 32 ---------------------- docs/wip/requirements.txt | 7 ----- 5 files changed, 7 insertions(+), 98 deletions(-) delete mode 100644 docs/wip/Makefile delete mode 100644 docs/wip/index.rst delete mode 100644 docs/wip/requirements.txt diff --git a/README.md b/README.md index 97fad22f..671fa6a1 100644 --- a/README.md +++ b/README.md @@ -6,8 +6,8 @@ ![ISE](https://img.shields.io/badge/ISE-14.7-blue.svg?style=flat-square) ![Openflow](https://img.shields.io/badge/Openflow-GHDL%20%7C%20Yosys%20%7C%20nextpnr%20%7C%20icestorm%20%7C%20prjtrellis-darkgreen.svg?style=flat-square) -> **WARNING:** (2022-05-15) PyFPGA is in the process of being strongly rewritten/simplified. -> Most changes are internal, but the API (`Project` class) will change. +> **WARNING:** (2024-05-20) PyFPGA is in the process of being strongly rewritten/simplified. +> Most changes are internal, but the API will also change. PyFPGA is a **Python Package** for **vendor-agnostic** FPGA development. It provides a **Class** which allows the programmatically execution of **synthesis**, diff --git a/docs/index.rst b/docs/index.rst index 69643350..96f4b3d9 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -6,6 +6,11 @@ PyFPGA's documentation :align: center :target: https://github.com/PyFPGA/pyfpga +.. ATTENTION:: + + (2024-05-20) PyFPGA is in the process of being strongly rewritten/simplified. + Most changes are internal, but the API will also change. + .. toctree:: intro diff --git a/docs/wip/Makefile b/docs/wip/Makefile deleted file mode 100644 index 247b174d..00000000 --- a/docs/wip/Makefile +++ /dev/null @@ -1,57 +0,0 @@ -CP=cp - -# Sphinx options. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -PAPER = -BUILDDIR = _build - -PAPEROPT_a4 = -D latex_paper_size=a4 -PAPEROPT_letter = -D latex_paper_size=letter -ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees -T -D language=en $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . - -all: pyfpga.info - -#--- - -man: - $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man - -#--- - -html: - PYTHONPATH=$(shell pwd)/.. $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html - -#--- - -latex: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - -#--- - -texi: pyfpga.texi -pyfpga.texi: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - $(CP) $(BUILDDIR)/texinfo/PyFPGA.texi $@ - -info: pyfpga.info -pyfpga.info: pyfpga.texi - makeinfo -o $@ $< - -dvi: pyfpga.dvi -pyfpga.dvi: pyfpga.texi - texi2dvi $< - -pyfpga.ps: pyfpga.dvi - dvips $< - -pdf: pyfpga.pdf -pyfpga.pdf: pyfpga.dvi - dvipdf $< - -#--- - -clean: - $(RM) *~ *.dvi *.info *.aux *.cp *.fn *.ky *.log - $(RM) *.pdf *.pg *.toc *.tp *.vr *.texi - $(RM) -rf _build diff --git a/docs/wip/index.rst b/docs/wip/index.rst deleted file mode 100644 index 8975c13b..00000000 --- a/docs/wip/index.rst +++ /dev/null @@ -1,32 +0,0 @@ -.. program:: pyfpga - -PyFPGA Documentation -#################### - -.. image:: images/logo.png - :width: 200 px - :align: center - :target: https://github.com/PyFPGA/pyfpga - -.. raw:: html - -

- -

- -
- -.. ATTENTION:: - - (2022-05-15) PyFPGA is in the process of being strongly rewritten/simplified. - Most changes are internal, but the API (`Project` class) will change. - -.. toctree:: - - intro - basic - advanced - tools - api - dev diff --git a/docs/wip/requirements.txt b/docs/wip/requirements.txt deleted file mode 100644 index 2406283f..00000000 --- a/docs/wip/requirements.txt +++ /dev/null @@ -1,7 +0,0 @@ -#-r ../requirements.txt -sphinx>=3.0.0 -recommonmark -python-dateutil -# sphinxcontrib-textstyle>=0.2.1 -# sphinxcontrib-spelling>=2.2.0 -# changelog>=0.3.5 \ No newline at end of file From 5c9f31ed96ca05bb543d747cb400ebb1c3d2fc1a Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Thu, 30 May 2024 18:08:33 -0300 Subject: [PATCH 044/254] Moved templates from fpga/tool to pyfpga/templates --- fpga/tool/template.sh => pyfpga/templates/openflow.jinja | 0 fpga/tool/template.tcl => pyfpga/templates/vivado.jinja | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename fpga/tool/template.sh => pyfpga/templates/openflow.jinja (100%) rename fpga/tool/template.tcl => pyfpga/templates/vivado.jinja (100%) diff --git a/fpga/tool/template.sh b/pyfpga/templates/openflow.jinja similarity index 100% rename from fpga/tool/template.sh rename to pyfpga/templates/openflow.jinja diff --git a/fpga/tool/template.tcl b/pyfpga/templates/vivado.jinja similarity index 100% rename from fpga/tool/template.tcl rename to pyfpga/templates/vivado.jinja From 93eeb269a2acb4c0f2961f06ea1b04141365fbb1 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Thu, 30 May 2024 18:51:53 -0300 Subject: [PATCH 045/254] Content of vivado.jinja was split into 4 templates --- pyfpga/templates/ise.jinja | 263 ++++++++++++++++++++++++++++ pyfpga/templates/libero.jinja | 294 ++++++++++++++++++++++++++++++++ pyfpga/templates/openflow.jinja | 19 +-- pyfpga/templates/quartus.jinja | 261 ++++++++++++++++++++++++++++ pyfpga/templates/vivado.jinja | 237 +------------------------ 5 files changed, 824 insertions(+), 250 deletions(-) create mode 100644 pyfpga/templates/ise.jinja create mode 100644 pyfpga/templates/libero.jinja create mode 100644 pyfpga/templates/quartus.jinja diff --git a/pyfpga/templates/ise.jinja b/pyfpga/templates/ise.jinja new file mode 100644 index 00000000..1830b97b --- /dev/null +++ b/pyfpga/templates/ise.jinja @@ -0,0 +1,263 @@ +# +# PyFPGA +# Copyright (C) 2015-2024 Rodrigo A. Melo +# +# SPDX-License-Identifier: GPL-3.0-or-later +# + +set TOOL #TOOL# +set PRESYNTH #PRESYNTH# +set PROJECT #PROJECT# +set PART #PART# +set FAMILY #FAMILY# +set DEVICE #DEVICE# +set PACKAGE #PACKAGE# +set SPEED #SPEED# +set TOP #TOP# +# TASKS = prj syn par bit +set TASKS [list #TASKS#] + +set PARAMS [list #PARAMS#] + +proc fpga_files {} { +#FILES# +} + +proc fpga_commands { PHASE } { + fpga_print "setting commands for the phase '$PHASE'" + switch $PHASE { + "prefile" { +#PREFILE_CMDS# + } + "project" { +#PROJECT_CMDS# + } + "preflow" { +#PREFLOW_CMDS# + } + "postsyn" { +#POSTSYN_CMDS# + } + "postpar" { +#POSTPAR_CMDS# + } + "postbit" { +#POSTBIT_CMDS# + } + } +} + +# +# Procedures +# + +proc fpga_print { MSG } { + global TOOL + puts ">>> PyFPGA ($TOOL): $MSG" +} + +proc fpga_create { PROJECT } { + global TOOL + fpga_print "creating the project '$PROJECT'" + switch $TOOL { + "ise" { + if { [ file exists $PROJECT.xise ] } { file delete $PROJECT.xise } + project new $PROJECT.xise + } + } +} + +proc fpga_open { PROJECT } { + global TOOL + fpga_print "opening the project '$PROJECT'" + switch $TOOL { + "ise" { project open $PROJECT.xise } + } +} + +proc fpga_close {} { + global TOOL + fpga_print "closing the project" + switch $TOOL { + "ise" { project close } + } +} + +proc fpga_part { PART } { + global TOOL FAMILY DEVICE PACKAGE SPEED + fpga_print "adding the part '$PART'" + switch $TOOL { + "ise" { + project set family $FAMILY + project set device $DEVICE + project set package $PACKAGE + project set speed $SPEED + } + } +} + +proc fpga_file {FILE {LIBRARY "work"}} { + global TOOL TOP + set message "adding the file '$FILE'" + if { $LIBRARY != "work" } { append message " (into the VHDL library '$LIBRARY')" } + fpga_print $message + regexp -nocase {\.(\w*)$} $FILE -> ext + if { $ext == "tcl" } { + source $FILE + return + } + switch $TOOL { + "ise" { + if {$ext == "xcf"} { + project set "Synthesis Constraints File" $FILE -process "Synthesize - XST" + } elseif { $LIBRARY != "work" } { + lib_vhdl new $LIBRARY + xfile add $FILE -lib_vhdl $LIBRARY + } else { + xfile add $FILE + } + } + } +} + +proc fpga_include {PATH} { + global TOOL INCLUDED + lappend INCLUDED $PATH + fpga_print "setting '$PATH' as a search location" + switch $TOOL { + "ise" { + # Verilog Included Files are NOT added + project set "Verilog Include Directories" \ + [join $INCLUDED "|"] -process "Synthesize - XST" + } + } +} + +proc fpga_top { TOP } { + global TOOL + fpga_print "specifying the top level '$TOP'" + switch $TOOL { + "ise" { + project set top $TOP + } + } +} + +proc fpga_params {} { + global TOOL PARAMS + if { [llength $PARAMS] == 0 } { return } + fpga_print "setting generics/parameters" + switch $TOOL { + "ise" { + set assigns [list] + foreach PARAM $PARAMS { lappend assigns [join $PARAM "="] } + project set "Generics, Parameters" "[join $assigns]" -process "Synthesize - XST" + } + } +} + +proc fpga_run_syn {} { + global TOOL PRESYNTH + fpga_print "running 'synthesis'" + switch $TOOL { + "ise" { + if { $PRESYNTH == "True" } { + project set top_level_module_type "EDIF" + } else { + project clean + process run "Synthesize" + if { [process get "Synthesize" status] == "errors" } { exit 2 } + } + } + } +} + +proc fpga_run_par {} { + global TOOL PRESYNTH + fpga_print "running 'place and route'" + switch $TOOL { + "ise" { + process run "Translate" + if { [process get "Translate" status] == "errors" } { exit 2 } + process run "Map" + if { [process get "Map" status] == "errors" } { exit 2 } + process run "Place & Route" + if { [process get "Place & Route" status] == "errors" } { exit 2 } + } + } +} + +proc fpga_run_bit {} { + global TOOL PROJECT TOP + fpga_print "running 'bitstream generation'" + switch $TOOL { + "ise" { + process run "Generate Programming File" + if { [process get "Generate Programming File" status] == "errors" } { exit 2 } + catch { file rename -force $TOP.bit $PROJECT.bit } + } + } +} + +# +# Start of the script +# + +fpga_print "start of the Tcl script (interpreter $tcl_version)" + +# +# Project Creation +# + +if { [lsearch -exact $TASKS "prj"] >= 0 } { + fpga_print "running the Project Creation" + if { [catch { + fpga_create $PROJECT + fpga_part $PART + fpga_commands "prefile" + fpga_files + fpga_top $TOP + fpga_params + fpga_commands "project" + fpga_close + } ERRMSG]} { + puts "ERROR: there was a problem creating a New Project.\n" + puts $ERRMSG + exit 1 + } +} + +# +# Design Flow +# + +if { [lsearch -regexp $TASKS "syn|par|bit"] >= 0 } { + fpga_print "running the Design Flow" + if { [catch { + fpga_open $PROJECT + fpga_commands "preflow" + if { [lsearch -exact $TASKS "syn"] >= 0 } { + fpga_run_syn + fpga_commands "postsyn" + } + if { [lsearch -exact $TASKS "par"] >= 0 } { + fpga_run_par + fpga_commands "postpar" + } + if { [lsearch -exact $TASKS "bit"] >= 0 } { + fpga_run_bit + fpga_commands "postbit" + } + fpga_close + } ERRMSG]} { + puts "ERROR: there was a problem running the Design Flow.\n" + puts $ERRMSG + exit 2 + } +} + +# +# End of the script +# + +fpga_print "end of the Tcl script" diff --git a/pyfpga/templates/libero.jinja b/pyfpga/templates/libero.jinja new file mode 100644 index 00000000..39919f94 --- /dev/null +++ b/pyfpga/templates/libero.jinja @@ -0,0 +1,294 @@ +# +# PyFPGA +# Copyright (C) 2015-2024 Rodrigo A. Melo +# +# SPDX-License-Identifier: GPL-3.0-or-later +# + +set TOOL #TOOL# +set PRESYNTH #PRESYNTH# +set PROJECT #PROJECT# +set PART #PART# +set FAMILY #FAMILY# +set DEVICE #DEVICE# +set PACKAGE #PACKAGE# +set SPEED #SPEED# +set TOP #TOP# +# TASKS = prj syn par bit +set TASKS [list #TASKS#] + +set PARAMS [list #PARAMS#] + +proc fpga_files {} { +#FILES# +} + +proc fpga_commands { PHASE } { + fpga_print "setting commands for the phase '$PHASE'" + switch $PHASE { + "prefile" { +#PREFILE_CMDS# + } + "project" { +#PROJECT_CMDS# + } + "preflow" { +#PREFLOW_CMDS# + } + "postsyn" { +#POSTSYN_CMDS# + } + "postpar" { +#POSTPAR_CMDS# + } + "postbit" { +#POSTBIT_CMDS# + } + } +} + +# +# Procedures +# + +proc fpga_print { MSG } { + global TOOL + puts ">>> PyFPGA ($TOOL): $MSG" +} + +proc fpga_create { PROJECT } { + global TOOL + fpga_print "creating the project '$PROJECT'" + switch $TOOL { + "libero" { + if { [ file exists $PROJECT ] } { file delete -force -- $PROJECT } + new_project -name $PROJECT -location $PROJECT -hdl {VHDL} -family {SmartFusion2} + } + } +} + +proc fpga_open { PROJECT } { + global TOOL + fpga_print "opening the project '$PROJECT'" + switch $TOOL { + "libero" { + open_project $PROJECT/$PROJECT.prjx + } + } +} + +proc fpga_close {} { + global TOOL + fpga_print "closing the project" + switch $TOOL { + "libero" { close_project } + } +} + +proc fpga_part { PART } { + global TOOL FAMILY DEVICE PACKAGE SPEED + fpga_print "adding the part '$PART'" + switch $TOOL { + "libero" { + set_device -family $FAMILY -die $DEVICE -package $PACKAGE -speed $SPEED + } + } +} + +proc fpga_file {FILE {LIBRARY "work"}} { + global TOOL TOP + set message "adding the file '$FILE'" + if { $LIBRARY != "work" } { append message " (into the VHDL library '$LIBRARY')" } + fpga_print $message + regexp -nocase {\.(\w*)$} $FILE -> ext + if { $ext == "tcl" } { + source $FILE + return + } + switch $TOOL { + "libero" { + global LIBERO_PLACE_CONSTRAINTS + global LIBERO_OTHER_CONSTRAINTS + if {$ext == "pdc"} { + create_links -io_pdc $FILE + append LIBERO_PLACE_CONSTRAINTS "-file $FILE " + } elseif {$ext == "sdc"} { + create_links -sdc $FILE + append LIBERO_PLACE_CONSTRAINTS "-file $FILE " + append LIBERO_OTHER_CONSTRAINTS "-file $FILE " + } else { + create_links -library $LIBRARY -hdl_source $FILE + build_design_hierarchy + } + } + } +} + +proc fpga_include {PATH} { + global TOOL INCLUDED + lappend INCLUDED $PATH + fpga_print "setting '$PATH' as a search location" + switch $TOOL { + "libero" { + # Verilog Included Files are ALSO added + # They must be specified after set_root (see fpga_top) + foreach FILE [glob -nocomplain $PATH/*.vh] { + create_links -hdl_source $FILE + } + build_design_hierarchy + } + } +} + +proc fpga_top { TOP } { + global TOOL + fpga_print "specifying the top level '$TOP'" + switch $TOOL { + "libero" { + set_root $TOP + # Verilog Included files + global INCLUDED PARAMS + set cmd "configure_tool -name {SYNTHESIZE} -params {SYNPLIFY_OPTIONS:" + if { [info exists INCLUDED] && [llength $INCLUDED] > 0 } { + # See /poc/include/libero.tcl for details + set PATHS "../../" + append PATHS [join $INCLUDED ";../../"] + append cmd "set_option -include_path \"$PATHS\"" + append cmd "\n" + } + foreach PARAM $PARAMS { + set assign [join $PARAM] + append cmd "set_option -hdl_param -set \"$assign\"" + append cmd "\n" + } + append cmd "}" + eval $cmd + # Constraints + # PDC is only used for PLACEROUTE. + # SDC is used by ALL (SYNTHESIZE, PLACEROUTE and VERIFYTIMING). + global LIBERO_PLACE_CONSTRAINTS + global LIBERO_OTHER_CONSTRAINTS + if { [info exists LIBERO_OTHER_CONSTRAINTS] } { + set cmd "organize_tool_files -tool {SYNTHESIZE} " + append cmd $LIBERO_OTHER_CONSTRAINTS + append cmd "-module $TOP -input_type {constraint}" + eval $cmd + set cmd "organize_tool_files -tool {VERIFYTIMING} " + append cmd $LIBERO_OTHER_CONSTRAINTS + append cmd "-module $TOP -input_type {constraint}" + eval $cmd + } + if { [info exists LIBERO_PLACE_CONSTRAINTS] } { + set cmd "organize_tool_files -tool {PLACEROUTE} " + append cmd $LIBERO_PLACE_CONSTRAINTS + append cmd "-module $TOP -input_type {constraint}" + eval $cmd + } + } + } +} + +proc fpga_params {} { + global TOOL PARAMS + if { [llength $PARAMS] == 0 } { return } + fpga_print "setting generics/parameters" + switch $TOOL { + "libero" { + # They must be specified after set_root (see fpga_top) + } + } +} + +proc fpga_run_syn {} { + global TOOL PRESYNTH + fpga_print "running 'synthesis'" + switch $TOOL { + "libero" { + run_tool -name {SYNTHESIZE} + } + } +} + +proc fpga_run_par {} { + global TOOL PRESYNTH + fpga_print "running 'place and route'" + switch $TOOL { + "libero" { + run_tool -name {PLACEROUTE} + run_tool -name {VERIFYTIMING} + } + } +} + +proc fpga_run_bit {} { + global TOOL PROJECT TOP + fpga_print "running 'bitstream generation'" + switch $TOOL { + "libero" { + run_tool -name {GENERATEPROGRAMMINGFILE} + } + } +} + +# +# Start of the script +# + +fpga_print "start of the Tcl script (interpreter $tcl_version)" + +# +# Project Creation +# + +if { [lsearch -exact $TASKS "prj"] >= 0 } { + fpga_print "running the Project Creation" + if { [catch { + fpga_create $PROJECT + fpga_part $PART + fpga_commands "prefile" + fpga_files + fpga_top $TOP + fpga_params + fpga_commands "project" + fpga_close + } ERRMSG]} { + puts "ERROR: there was a problem creating a New Project.\n" + puts $ERRMSG + exit 1 + } +} + +# +# Design Flow +# + +if { [lsearch -regexp $TASKS "syn|par|bit"] >= 0 } { + fpga_print "running the Design Flow" + if { [catch { + fpga_open $PROJECT + fpga_commands "preflow" + if { [lsearch -exact $TASKS "syn"] >= 0 } { + fpga_run_syn + fpga_commands "postsyn" + } + if { [lsearch -exact $TASKS "par"] >= 0 } { + fpga_run_par + fpga_commands "postpar" + } + if { [lsearch -exact $TASKS "bit"] >= 0 } { + fpga_run_bit + fpga_commands "postbit" + } + fpga_close + } ERRMSG]} { + puts "ERROR: there was a problem running the Design Flow.\n" + puts $ERRMSG + exit 2 + } +} + +# +# End of the script +# + +fpga_print "end of the Tcl script" diff --git a/pyfpga/templates/openflow.jinja b/pyfpga/templates/openflow.jinja index f2b271eb..f50b0786 100644 --- a/pyfpga/templates/openflow.jinja +++ b/pyfpga/templates/openflow.jinja @@ -1,22 +1,9 @@ #!/bin/bash # -# Copyright (C) 2020 Rodrigo A. Melo +# PyFPGA +# Copyright (C) 2020-2024 Rodrigo A. Melo # -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# -# This file implements an open-source flow based on ghdl, ghdl-yosys-plugin, -# yosys, nextpnr, icestorm and prjtrellis. +# SPDX-License-Identifier: GPL-3.0-or-later # set -e diff --git a/pyfpga/templates/quartus.jinja b/pyfpga/templates/quartus.jinja new file mode 100644 index 00000000..8e54e4ea --- /dev/null +++ b/pyfpga/templates/quartus.jinja @@ -0,0 +1,261 @@ +# +# PyFPGA +# Copyright (C) 2015-2024 Rodrigo A. Melo +# +# SPDX-License-Identifier: GPL-3.0-or-later +# + +set TOOL #TOOL# +set PRESYNTH #PRESYNTH# +set PROJECT #PROJECT# +set PART #PART# +set FAMILY #FAMILY# +set DEVICE #DEVICE# +set PACKAGE #PACKAGE# +set SPEED #SPEED# +set TOP #TOP# +# TASKS = prj syn par bit +set TASKS [list #TASKS#] + +set PARAMS [list #PARAMS#] + +proc fpga_files {} { +#FILES# +} + +proc fpga_commands { PHASE } { + fpga_print "setting commands for the phase '$PHASE'" + switch $PHASE { + "prefile" { +#PREFILE_CMDS# + } + "project" { +#PROJECT_CMDS# + } + "preflow" { +#PREFLOW_CMDS# + } + "postsyn" { +#POSTSYN_CMDS# + } + "postpar" { +#POSTPAR_CMDS# + } + "postbit" { +#POSTBIT_CMDS# + } + } +} + +# +# Procedures +# + +proc fpga_print { MSG } { + global TOOL + puts ">>> PyFPGA ($TOOL): $MSG" +} + +proc fpga_create { PROJECT } { + global TOOL + fpga_print "creating the project '$PROJECT'" + switch $TOOL { + "quartus" { + package require ::quartus::project + project_new $PROJECT -overwrite + set_global_assignment -name NUM_PARALLEL_PROCESSORS ALL + } + } +} + +proc fpga_open { PROJECT } { + global TOOL + fpga_print "opening the project '$PROJECT'" + switch $TOOL { + "quartus" { + package require ::quartus::flow + project_open -force $PROJECT.qpf + } + } +} + +proc fpga_close {} { + global TOOL + fpga_print "closing the project" + switch $TOOL { + "quartus" { project_close } + } +} + +proc fpga_part { PART } { + global TOOL FAMILY DEVICE PACKAGE SPEED + fpga_print "adding the part '$PART'" + switch $TOOL { + "quartus" { + set_global_assignment -name DEVICE $PART + } + } +} + +proc fpga_file {FILE {LIBRARY "work"}} { + global TOOL TOP + set message "adding the file '$FILE'" + if { $LIBRARY != "work" } { append message " (into the VHDL library '$LIBRARY')" } + fpga_print $message + regexp -nocase {\.(\w*)$} $FILE -> ext + if { $ext == "tcl" } { + source $FILE + return + } + switch $TOOL { + "quartus" { + if {$ext == "v"} { + set TYPE VERILOG_FILE + } elseif {$ext == "sv"} { + set TYPE SYSTEMVERILOG_FILE + } elseif {$ext == "vhdl" || $ext == "vhd"} { + set TYPE VHDL_FILE + } elseif {$ext == "sdc"} { + set TYPE SDC_FILE + } else { + set TYPE SOURCE_FILE + } + if { $LIBRARY != "work" } { + set_global_assignment -name $TYPE $FILE -library $LIBRARY + } else { + set_global_assignment -name $TYPE $FILE + } + } + } +} + +proc fpga_include {PATH} { + global TOOL INCLUDED + lappend INCLUDED $PATH + fpga_print "setting '$PATH' as a search location" + switch $TOOL { + "quartus" { + # Verilog Included Files are NOT added + foreach INCLUDE $INCLUDED { + set_global_assignment -name SEARCH_PATH $INCLUDE + } + } + } +} + +proc fpga_top { TOP } { + global TOOL + fpga_print "specifying the top level '$TOP'" + switch $TOOL { + "quartus" { + set_global_assignment -name TOP_LEVEL_ENTITY $TOP + } + } +} + +proc fpga_params {} { + global TOOL PARAMS + if { [llength $PARAMS] == 0 } { return } + fpga_print "setting generics/parameters" + switch $TOOL { + "quartus" { + foreach PARAM $PARAMS { + eval "set_parameter -name $PARAM" + } + } + } +} + +proc fpga_run_syn {} { + global TOOL PRESYNTH + fpga_print "running 'synthesis'" + switch $TOOL { + "quartus" { + execute_module -tool map + } + } +} + +proc fpga_run_par {} { + global TOOL PRESYNTH + fpga_print "running 'place and route'" + switch $TOOL { + "quartus" { + execute_module -tool fit + execute_module -tool sta + } + } +} + +proc fpga_run_bit {} { + global TOOL PROJECT TOP + fpga_print "running 'bitstream generation'" + switch $TOOL { + "quartus" { + execute_module -tool asm + } + } +} + +# +# Start of the script +# + +fpga_print "start of the Tcl script (interpreter $tcl_version)" + +# +# Project Creation +# + +if { [lsearch -exact $TASKS "prj"] >= 0 } { + fpga_print "running the Project Creation" + if { [catch { + fpga_create $PROJECT + fpga_part $PART + fpga_commands "prefile" + fpga_files + fpga_top $TOP + fpga_params + fpga_commands "project" + fpga_close + } ERRMSG]} { + puts "ERROR: there was a problem creating a New Project.\n" + puts $ERRMSG + exit 1 + } +} + +# +# Design Flow +# + +if { [lsearch -regexp $TASKS "syn|par|bit"] >= 0 } { + fpga_print "running the Design Flow" + if { [catch { + fpga_open $PROJECT + fpga_commands "preflow" + if { [lsearch -exact $TASKS "syn"] >= 0 } { + fpga_run_syn + fpga_commands "postsyn" + } + if { [lsearch -exact $TASKS "par"] >= 0 } { + fpga_run_par + fpga_commands "postpar" + } + if { [lsearch -exact $TASKS "bit"] >= 0 } { + fpga_run_bit + fpga_commands "postbit" + } + fpga_close + } ERRMSG]} { + puts "ERROR: there was a problem running the Design Flow.\n" + puts $ERRMSG + exit 2 + } +} + +# +# End of the script +# + +fpga_print "end of the Tcl script" diff --git a/pyfpga/templates/vivado.jinja b/pyfpga/templates/vivado.jinja index 7466d534..5758f1c7 100644 --- a/pyfpga/templates/vivado.jinja +++ b/pyfpga/templates/vivado.jinja @@ -1,35 +1,8 @@ # -# PyFPGA Master Tcl +# PyFPGA +# Copyright (C) 2015-2024 Rodrigo A. Melo # -# Copyright (C) 2015-2020 INTI -# Copyright (C) 2015-2020 Rodrigo A. Melo -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# -# Description: Tcl script to create a new project and performs synthesis, -# place and route, and bitstream generation. -# -# Supported TOOLs: ise, libero, quartus, vivado -# -# Notes: -# * fpga_ is used to avoid name collisions. -# * The 'in' operator was introduced by Tcl 8.5, but some Tools uses 8.4, -# so 'lsearch' is used to test if a value is in a list. -# - -# -# Things to tuneup (#SOMETHING#) for each project +# SPDX-License-Identifier: GPL-3.0-or-later # set TOOL #TOOL# @@ -87,19 +60,6 @@ proc fpga_create { PROJECT } { global TOOL fpga_print "creating the project '$PROJECT'" switch $TOOL { - "ise" { - if { [ file exists $PROJECT.xise ] } { file delete $PROJECT.xise } - project new $PROJECT.xise - } - "libero" { - if { [ file exists $PROJECT ] } { file delete -force -- $PROJECT } - new_project -name $PROJECT -location $PROJECT -hdl {VHDL} -family {SmartFusion2} - } - "quartus" { - package require ::quartus::project - project_new $PROJECT -overwrite - set_global_assignment -name NUM_PARALLEL_PROCESSORS ALL - } "vivado" { create_project -force $PROJECT } } } @@ -108,14 +68,6 @@ proc fpga_open { PROJECT } { global TOOL fpga_print "opening the project '$PROJECT'" switch $TOOL { - "ise" { project open $PROJECT.xise } - "libero" { - open_project $PROJECT/$PROJECT.prjx - } - "quartus" { - package require ::quartus::flow - project_open -force $PROJECT.qpf - } "vivado" { open_project $PROJECT } } } @@ -124,9 +76,6 @@ proc fpga_close {} { global TOOL fpga_print "closing the project" switch $TOOL { - "ise" { project close } - "libero" { close_project } - "quartus" { project_close } "vivado" { close_project } } } @@ -135,18 +84,6 @@ proc fpga_part { PART } { global TOOL FAMILY DEVICE PACKAGE SPEED fpga_print "adding the part '$PART'" switch $TOOL { - "ise" { - project set family $FAMILY - project set device $DEVICE - project set package $PACKAGE - project set speed $SPEED - } - "libero" { - set_device -family $FAMILY -die $DEVICE -package $PACKAGE -speed $SPEED - } - "quartus" { - set_global_assignment -name DEVICE $PART - } "vivado" { set_property "part" $PART [current_project] } @@ -164,49 +101,6 @@ proc fpga_file {FILE {LIBRARY "work"}} { return } switch $TOOL { - "ise" { - if {$ext == "xcf"} { - project set "Synthesis Constraints File" $FILE -process "Synthesize - XST" - } elseif { $LIBRARY != "work" } { - lib_vhdl new $LIBRARY - xfile add $FILE -lib_vhdl $LIBRARY - } else { - xfile add $FILE - } - } - "libero" { - global LIBERO_PLACE_CONSTRAINTS - global LIBERO_OTHER_CONSTRAINTS - if {$ext == "pdc"} { - create_links -io_pdc $FILE - append LIBERO_PLACE_CONSTRAINTS "-file $FILE " - } elseif {$ext == "sdc"} { - create_links -sdc $FILE - append LIBERO_PLACE_CONSTRAINTS "-file $FILE " - append LIBERO_OTHER_CONSTRAINTS "-file $FILE " - } else { - create_links -library $LIBRARY -hdl_source $FILE - build_design_hierarchy - } - } - "quartus" { - if {$ext == "v"} { - set TYPE VERILOG_FILE - } elseif {$ext == "sv"} { - set TYPE SYSTEMVERILOG_FILE - } elseif {$ext == "vhdl" || $ext == "vhd"} { - set TYPE VHDL_FILE - } elseif {$ext == "sdc"} { - set TYPE SDC_FILE - } else { - set TYPE SOURCE_FILE - } - if { $LIBRARY != "work" } { - set_global_assignment -name $TYPE $FILE -library $LIBRARY - } else { - set_global_assignment -name $TYPE $FILE - } - } "vivado" { if { $LIBRARY != "work" } { add_files $FILE @@ -223,25 +117,6 @@ proc fpga_include {PATH} { lappend INCLUDED $PATH fpga_print "setting '$PATH' as a search location" switch $TOOL { - "ise" { - # Verilog Included Files are NOT added - project set "Verilog Include Directories" \ - [join $INCLUDED "|"] -process "Synthesize - XST" - } - "libero" { - # Verilog Included Files are ALSO added - # They must be specified after set_root (see fpga_top) - foreach FILE [glob -nocomplain $PATH/*.vh] { - create_links -hdl_source $FILE - } - build_design_hierarchy - } - "quartus" { - # Verilog Included Files are NOT added - foreach INCLUDE $INCLUDED { - set_global_assignment -name SEARCH_PATH $INCLUDE - } - } "vivado" { # Verilog Included Files are NOT added set_property "include_dirs" $INCLUDED [current_fileset] @@ -264,7 +139,6 @@ proc fpga_design {FILE} { set TOP design_1_wrapper } } - default { puts "UNSUPPORTED by '$TOOL'" } } } @@ -272,53 +146,6 @@ proc fpga_top { TOP } { global TOOL fpga_print "specifying the top level '$TOP'" switch $TOOL { - "ise" { - project set top $TOP - } - "libero" { - set_root $TOP - # Verilog Included files - global INCLUDED PARAMS - set cmd "configure_tool -name {SYNTHESIZE} -params {SYNPLIFY_OPTIONS:" - if { [info exists INCLUDED] && [llength $INCLUDED] > 0 } { - # See /poc/include/libero.tcl for details - set PATHS "../../" - append PATHS [join $INCLUDED ";../../"] - append cmd "set_option -include_path \"$PATHS\"" - append cmd "\n" - } - foreach PARAM $PARAMS { - set assign [join $PARAM] - append cmd "set_option -hdl_param -set \"$assign\"" - append cmd "\n" - } - append cmd "}" - eval $cmd - # Constraints - # PDC is only used for PLACEROUTE. - # SDC is used by ALL (SYNTHESIZE, PLACEROUTE and VERIFYTIMING). - global LIBERO_PLACE_CONSTRAINTS - global LIBERO_OTHER_CONSTRAINTS - if { [info exists LIBERO_OTHER_CONSTRAINTS] } { - set cmd "organize_tool_files -tool {SYNTHESIZE} " - append cmd $LIBERO_OTHER_CONSTRAINTS - append cmd "-module $TOP -input_type {constraint}" - eval $cmd - set cmd "organize_tool_files -tool {VERIFYTIMING} " - append cmd $LIBERO_OTHER_CONSTRAINTS - append cmd "-module $TOP -input_type {constraint}" - eval $cmd - } - if { [info exists LIBERO_PLACE_CONSTRAINTS] } { - set cmd "organize_tool_files -tool {PLACEROUTE} " - append cmd $LIBERO_PLACE_CONSTRAINTS - append cmd "-module $TOP -input_type {constraint}" - eval $cmd - } - } - "quartus" { - set_global_assignment -name TOP_LEVEL_ENTITY $TOP - } "vivado" { set_property top $TOP [current_fileset] } @@ -330,19 +157,6 @@ proc fpga_params {} { if { [llength $PARAMS] == 0 } { return } fpga_print "setting generics/parameters" switch $TOOL { - "ise" { - set assigns [list] - foreach PARAM $PARAMS { lappend assigns [join $PARAM "="] } - project set "Generics, Parameters" "[join $assigns]" -process "Synthesize - XST" - } - "libero" { - # They must be specified after set_root (see fpga_top) - } - "quartus" { - foreach PARAM $PARAMS { - eval "set_parameter -name $PARAM" - } - } "vivado" { set assigns [list] foreach PARAM $PARAMS { lappend assigns [join $PARAM "="] } @@ -356,21 +170,6 @@ proc fpga_run_syn {} { global TOOL PRESYNTH fpga_print "running 'synthesis'" switch $TOOL { - "ise" { - if { $PRESYNTH == "True" } { - project set top_level_module_type "EDIF" - } else { - project clean - process run "Synthesize" - if { [process get "Synthesize" status] == "errors" } { exit 2 } - } - } - "libero" { - run_tool -name {SYNTHESIZE} - } - "quartus" { - execute_module -tool map - } "vivado" { if { $PRESYNTH == "True" } { set_property design_mode GateLvl [current_fileset] @@ -380,7 +179,6 @@ proc fpga_run_syn {} { wait_on_run synth_1 } } - default { puts "UNSUPPORTED by '$TOOL'" } } } @@ -388,22 +186,6 @@ proc fpga_run_par {} { global TOOL PRESYNTH fpga_print "running 'place and route'" switch $TOOL { - "ise" { - process run "Translate" - if { [process get "Translate" status] == "errors" } { exit 2 } - process run "Map" - if { [process get "Map" status] == "errors" } { exit 2 } - process run "Place & Route" - if { [process get "Place & Route" status] == "errors" } { exit 2 } - } - "libero" { - run_tool -name {PLACEROUTE} - run_tool -name {VERIFYTIMING} - } - "quartus" { - execute_module -tool fit - execute_module -tool sta - } "vivado" { if {$PRESYNTH == "False"} { open_run synth_1 @@ -411,7 +193,6 @@ proc fpga_run_par {} { launch_runs impl_1 wait_on_run impl_1 } - default { puts "UNSUPPORTED by '$TOOL'" } } } @@ -419,22 +200,10 @@ proc fpga_run_bit {} { global TOOL PROJECT TOP fpga_print "running 'bitstream generation'" switch $TOOL { - "ise" { - process run "Generate Programming File" - if { [process get "Generate Programming File" status] == "errors" } { exit 2 } - catch { file rename -force $TOP.bit $PROJECT.bit } - } - "libero" { - run_tool -name {GENERATEPROGRAMMINGFILE} - } - "quartus" { - execute_module -tool asm - } "vivado" { open_run impl_1 write_bitstream -force $PROJECT } - default { puts "UNSUPPORTED by '$TOOL'" } } } From 5c2ad6ffe1084cdbae147569ed04d7e66fecbf59 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Thu, 30 May 2024 19:28:49 -0300 Subject: [PATCH 046/254] Templates clean-up --- pyfpga/templates/ise.jinja | 248 +++++++-------------------- pyfpga/templates/libero.jinja | 305 ++++++++++----------------------- pyfpga/templates/quartus.jinja | 240 ++++++-------------------- pyfpga/templates/vivado.jinja | 251 +++++++-------------------- 4 files changed, 258 insertions(+), 786 deletions(-) diff --git a/pyfpga/templates/ise.jinja b/pyfpga/templates/ise.jinja index 1830b97b..2aa7a6b1 100644 --- a/pyfpga/templates/ise.jinja +++ b/pyfpga/templates/ise.jinja @@ -5,7 +5,6 @@ # SPDX-License-Identifier: GPL-3.0-or-later # -set TOOL #TOOL# set PRESYNTH #PRESYNTH# set PROJECT #PROJECT# set PART #PART# @@ -23,241 +22,112 @@ proc fpga_files {} { #FILES# } -proc fpga_commands { PHASE } { - fpga_print "setting commands for the phase '$PHASE'" - switch $PHASE { - "prefile" { -#PREFILE_CMDS# - } - "project" { -#PROJECT_CMDS# - } - "preflow" { -#PREFLOW_CMDS# - } - "postsyn" { -#POSTSYN_CMDS# - } - "postpar" { -#POSTPAR_CMDS# - } - "postbit" { -#POSTBIT_CMDS# - } - } -} - -# -# Procedures -# - -proc fpga_print { MSG } { - global TOOL - puts ">>> PyFPGA ($TOOL): $MSG" -} - proc fpga_create { PROJECT } { - global TOOL - fpga_print "creating the project '$PROJECT'" - switch $TOOL { - "ise" { - if { [ file exists $PROJECT.xise ] } { file delete $PROJECT.xise } - project new $PROJECT.xise - } - } + if { [ file exists $PROJECT.xise ] } { file delete $PROJECT.xise } + project new $PROJECT.xise } proc fpga_open { PROJECT } { - global TOOL - fpga_print "opening the project '$PROJECT'" - switch $TOOL { - "ise" { project open $PROJECT.xise } - } + project open $PROJECT.xise } proc fpga_close {} { - global TOOL - fpga_print "closing the project" - switch $TOOL { - "ise" { project close } - } + project close } proc fpga_part { PART } { - global TOOL FAMILY DEVICE PACKAGE SPEED - fpga_print "adding the part '$PART'" - switch $TOOL { - "ise" { - project set family $FAMILY - project set device $DEVICE - project set package $PACKAGE - project set speed $SPEED - } - } + project set family $FAMILY + project set device $DEVICE + project set package $PACKAGE + project set speed $SPEED } proc fpga_file {FILE {LIBRARY "work"}} { - global TOOL TOP set message "adding the file '$FILE'" if { $LIBRARY != "work" } { append message " (into the VHDL library '$LIBRARY')" } - fpga_print $message regexp -nocase {\.(\w*)$} $FILE -> ext if { $ext == "tcl" } { source $FILE return } - switch $TOOL { - "ise" { - if {$ext == "xcf"} { - project set "Synthesis Constraints File" $FILE -process "Synthesize - XST" - } elseif { $LIBRARY != "work" } { - lib_vhdl new $LIBRARY - xfile add $FILE -lib_vhdl $LIBRARY - } else { - xfile add $FILE - } - } + if {$ext == "xcf"} { + project set "Synthesis Constraints File" $FILE -process "Synthesize - XST" + } elseif { $LIBRARY != "work" } { + lib_vhdl new $LIBRARY + xfile add $FILE -lib_vhdl $LIBRARY + } else { + xfile add $FILE } } proc fpga_include {PATH} { - global TOOL INCLUDED lappend INCLUDED $PATH - fpga_print "setting '$PATH' as a search location" - switch $TOOL { - "ise" { - # Verilog Included Files are NOT added - project set "Verilog Include Directories" \ - [join $INCLUDED "|"] -process "Synthesize - XST" - } - } + # Verilog Included Files are NOT added + project set "Verilog Include Directories" \ + [join $INCLUDED "|"] -process "Synthesize - XST" } proc fpga_top { TOP } { - global TOOL - fpga_print "specifying the top level '$TOP'" - switch $TOOL { - "ise" { - project set top $TOP - } - } + project set top $TOP } proc fpga_params {} { - global TOOL PARAMS if { [llength $PARAMS] == 0 } { return } - fpga_print "setting generics/parameters" - switch $TOOL { - "ise" { - set assigns [list] - foreach PARAM $PARAMS { lappend assigns [join $PARAM "="] } - project set "Generics, Parameters" "[join $assigns]" -process "Synthesize - XST" - } - } + set assigns [list] + foreach PARAM $PARAMS { lappend assigns [join $PARAM "="] } + project set "Generics, Parameters" "[join $assigns]" -process "Synthesize - XST" } proc fpga_run_syn {} { - global TOOL PRESYNTH - fpga_print "running 'synthesis'" - switch $TOOL { - "ise" { - if { $PRESYNTH == "True" } { - project set top_level_module_type "EDIF" - } else { - project clean - process run "Synthesize" - if { [process get "Synthesize" status] == "errors" } { exit 2 } - } - } + if { $PRESYNTH == "True" } { + project set top_level_module_type "EDIF" + } else { + project clean + process run "Synthesize" + if { [process get "Synthesize" status] == "errors" } { exit 2 } } } proc fpga_run_par {} { - global TOOL PRESYNTH - fpga_print "running 'place and route'" - switch $TOOL { - "ise" { - process run "Translate" - if { [process get "Translate" status] == "errors" } { exit 2 } - process run "Map" - if { [process get "Map" status] == "errors" } { exit 2 } - process run "Place & Route" - if { [process get "Place & Route" status] == "errors" } { exit 2 } - } - } + process run "Translate" + if { [process get "Translate" status] == "errors" } { exit 2 } + process run "Map" + if { [process get "Map" status] == "errors" } { exit 2 } + process run "Place & Route" + if { [process get "Place & Route" status] == "errors" } { exit 2 } } proc fpga_run_bit {} { - global TOOL PROJECT TOP - fpga_print "running 'bitstream generation'" - switch $TOOL { - "ise" { - process run "Generate Programming File" - if { [process get "Generate Programming File" status] == "errors" } { exit 2 } - catch { file rename -force $TOP.bit $PROJECT.bit } - } - } + process run "Generate Programming File" + if { [process get "Generate Programming File" status] == "errors" } { exit 2 } + catch { file rename -force $TOP.bit $PROJECT.bit } } -# -# Start of the script -# - -fpga_print "start of the Tcl script (interpreter $tcl_version)" - -# -# Project Creation -# - if { [lsearch -exact $TASKS "prj"] >= 0 } { - fpga_print "running the Project Creation" - if { [catch { - fpga_create $PROJECT - fpga_part $PART - fpga_commands "prefile" - fpga_files - fpga_top $TOP - fpga_params - fpga_commands "project" - fpga_close - } ERRMSG]} { - puts "ERROR: there was a problem creating a New Project.\n" - puts $ERRMSG - exit 1 - } + fpga_create $PROJECT + fpga_part $PART + {{ PRECFG }} + fpga_files + fpga_top $TOP + fpga_params + {{ POSTCFG }} + fpga_close } -# -# Design Flow -# - if { [lsearch -regexp $TASKS "syn|par|bit"] >= 0 } { - fpga_print "running the Design Flow" - if { [catch { - fpga_open $PROJECT - fpga_commands "preflow" - if { [lsearch -exact $TASKS "syn"] >= 0 } { - fpga_run_syn - fpga_commands "postsyn" - } - if { [lsearch -exact $TASKS "par"] >= 0 } { - fpga_run_par - fpga_commands "postpar" - } - if { [lsearch -exact $TASKS "bit"] >= 0 } { - fpga_run_bit - fpga_commands "postbit" - } - fpga_close - } ERRMSG]} { - puts "ERROR: there was a problem running the Design Flow.\n" - puts $ERRMSG - exit 2 + fpga_open $PROJECT + if { [lsearch -exact $TASKS "syn"] >= 0 } { + {{ PRESYN }} + fpga_run_syn + } + if { [lsearch -exact $TASKS "par"] >= 0 } { + {{ PREPAR }} + fpga_run_par + } + if { [lsearch -exact $TASKS "bit"] >= 0 } { + {{ PREBIT }} + fpga_run_bit + {{ POSTBIT }} } + fpga_close } - -# -# End of the script -# - -fpga_print "end of the Tcl script" diff --git a/pyfpga/templates/libero.jinja b/pyfpga/templates/libero.jinja index 39919f94..7cdcfd33 100644 --- a/pyfpga/templates/libero.jinja +++ b/pyfpga/templates/libero.jinja @@ -5,7 +5,6 @@ # SPDX-License-Identifier: GPL-3.0-or-later # -set TOOL #TOOL# set PRESYNTH #PRESYNTH# set PROJECT #PROJECT# set PART #PART# @@ -23,272 +22,140 @@ proc fpga_files {} { #FILES# } -proc fpga_commands { PHASE } { - fpga_print "setting commands for the phase '$PHASE'" - switch $PHASE { - "prefile" { -#PREFILE_CMDS# - } - "project" { -#PROJECT_CMDS# - } - "preflow" { -#PREFLOW_CMDS# - } - "postsyn" { -#POSTSYN_CMDS# - } - "postpar" { -#POSTPAR_CMDS# - } - "postbit" { -#POSTBIT_CMDS# - } - } -} - -# -# Procedures -# - -proc fpga_print { MSG } { - global TOOL - puts ">>> PyFPGA ($TOOL): $MSG" -} - proc fpga_create { PROJECT } { - global TOOL - fpga_print "creating the project '$PROJECT'" - switch $TOOL { - "libero" { - if { [ file exists $PROJECT ] } { file delete -force -- $PROJECT } - new_project -name $PROJECT -location $PROJECT -hdl {VHDL} -family {SmartFusion2} - } - } + if { [ file exists $PROJECT ] } { file delete -force -- $PROJECT } + new_project -name $PROJECT -location $PROJECT -hdl {VHDL} -family {SmartFusion2} } proc fpga_open { PROJECT } { - global TOOL - fpga_print "opening the project '$PROJECT'" - switch $TOOL { - "libero" { - open_project $PROJECT/$PROJECT.prjx - } - } + open_project $PROJECT/$PROJECT.prjx } proc fpga_close {} { - global TOOL - fpga_print "closing the project" - switch $TOOL { - "libero" { close_project } - } + close_project } proc fpga_part { PART } { - global TOOL FAMILY DEVICE PACKAGE SPEED - fpga_print "adding the part '$PART'" - switch $TOOL { - "libero" { - set_device -family $FAMILY -die $DEVICE -package $PACKAGE -speed $SPEED - } - } + set_device -family $FAMILY -die $DEVICE -package $PACKAGE -speed $SPEED } proc fpga_file {FILE {LIBRARY "work"}} { - global TOOL TOP set message "adding the file '$FILE'" if { $LIBRARY != "work" } { append message " (into the VHDL library '$LIBRARY')" } - fpga_print $message regexp -nocase {\.(\w*)$} $FILE -> ext if { $ext == "tcl" } { source $FILE return } - switch $TOOL { - "libero" { - global LIBERO_PLACE_CONSTRAINTS - global LIBERO_OTHER_CONSTRAINTS - if {$ext == "pdc"} { - create_links -io_pdc $FILE - append LIBERO_PLACE_CONSTRAINTS "-file $FILE " - } elseif {$ext == "sdc"} { - create_links -sdc $FILE - append LIBERO_PLACE_CONSTRAINTS "-file $FILE " - append LIBERO_OTHER_CONSTRAINTS "-file $FILE " - } else { - create_links -library $LIBRARY -hdl_source $FILE - build_design_hierarchy - } - } + global LIBERO_PLACE_CONSTRAINTS + global LIBERO_OTHER_CONSTRAINTS + if {$ext == "pdc"} { + create_links -io_pdc $FILE + append LIBERO_PLACE_CONSTRAINTS "-file $FILE " + } elseif {$ext == "sdc"} { + create_links -sdc $FILE + append LIBERO_PLACE_CONSTRAINTS "-file $FILE " + append LIBERO_OTHER_CONSTRAINTS "-file $FILE " + } else { + create_links -library $LIBRARY -hdl_source $FILE + build_design_hierarchy } } proc fpga_include {PATH} { - global TOOL INCLUDED lappend INCLUDED $PATH - fpga_print "setting '$PATH' as a search location" - switch $TOOL { - "libero" { - # Verilog Included Files are ALSO added - # They must be specified after set_root (see fpga_top) - foreach FILE [glob -nocomplain $PATH/*.vh] { - create_links -hdl_source $FILE - } - build_design_hierarchy - } + # Verilog Included Files are ALSO added + # They must be specified after set_root (see fpga_top) + foreach FILE [glob -nocomplain $PATH/*.vh] { + create_links -hdl_source $FILE } + build_design_hierarchy } proc fpga_top { TOP } { - global TOOL - fpga_print "specifying the top level '$TOP'" - switch $TOOL { - "libero" { - set_root $TOP - # Verilog Included files - global INCLUDED PARAMS - set cmd "configure_tool -name {SYNTHESIZE} -params {SYNPLIFY_OPTIONS:" - if { [info exists INCLUDED] && [llength $INCLUDED] > 0 } { - # See /poc/include/libero.tcl for details - set PATHS "../../" - append PATHS [join $INCLUDED ";../../"] - append cmd "set_option -include_path \"$PATHS\"" - append cmd "\n" - } - foreach PARAM $PARAMS { - set assign [join $PARAM] - append cmd "set_option -hdl_param -set \"$assign\"" - append cmd "\n" - } - append cmd "}" - eval $cmd - # Constraints - # PDC is only used for PLACEROUTE. - # SDC is used by ALL (SYNTHESIZE, PLACEROUTE and VERIFYTIMING). - global LIBERO_PLACE_CONSTRAINTS - global LIBERO_OTHER_CONSTRAINTS - if { [info exists LIBERO_OTHER_CONSTRAINTS] } { - set cmd "organize_tool_files -tool {SYNTHESIZE} " - append cmd $LIBERO_OTHER_CONSTRAINTS - append cmd "-module $TOP -input_type {constraint}" - eval $cmd - set cmd "organize_tool_files -tool {VERIFYTIMING} " - append cmd $LIBERO_OTHER_CONSTRAINTS - append cmd "-module $TOP -input_type {constraint}" - eval $cmd - } - if { [info exists LIBERO_PLACE_CONSTRAINTS] } { - set cmd "organize_tool_files -tool {PLACEROUTE} " - append cmd $LIBERO_PLACE_CONSTRAINTS - append cmd "-module $TOP -input_type {constraint}" - eval $cmd - } - } + set_root $TOP + # Verilog Included files + set cmd "configure_tool -name {SYNTHESIZE} -params {SYNPLIFY_OPTIONS:" + if { [info exists INCLUDED] && [llength $INCLUDED] > 0 } { + # See /poc/include/libero.tcl for details + set PATHS "../../" + append PATHS [join $INCLUDED ";../../"] + append cmd "set_option -include_path \"$PATHS\"" + append cmd "\n" + } + foreach PARAM $PARAMS { + set assign [join $PARAM] + append cmd "set_option -hdl_param -set \"$assign\"" + append cmd "\n" + } + append cmd "}" + eval $cmd + # Constraints + # PDC is only used for PLACEROUTE. + # SDC is used by ALL (SYNTHESIZE, PLACEROUTE and VERIFYTIMING). + global LIBERO_PLACE_CONSTRAINTS + global LIBERO_OTHER_CONSTRAINTS + if { [info exists LIBERO_OTHER_CONSTRAINTS] } { + set cmd "organize_tool_files -tool {SYNTHESIZE} " + append cmd $LIBERO_OTHER_CONSTRAINTS + append cmd "-module $TOP -input_type {constraint}" + eval $cmd + set cmd "organize_tool_files -tool {VERIFYTIMING} " + append cmd $LIBERO_OTHER_CONSTRAINTS + append cmd "-module $TOP -input_type {constraint}" + eval $cmd + } + if { [info exists LIBERO_PLACE_CONSTRAINTS] } { + set cmd "organize_tool_files -tool {PLACEROUTE} " + append cmd $LIBERO_PLACE_CONSTRAINTS + append cmd "-module $TOP -input_type {constraint}" + eval $cmd } } proc fpga_params {} { - global TOOL PARAMS if { [llength $PARAMS] == 0 } { return } - fpga_print "setting generics/parameters" - switch $TOOL { - "libero" { - # They must be specified after set_root (see fpga_top) - } - } + # They must be specified after set_root (see fpga_top) } proc fpga_run_syn {} { - global TOOL PRESYNTH - fpga_print "running 'synthesis'" - switch $TOOL { - "libero" { - run_tool -name {SYNTHESIZE} - } - } + run_tool -name {SYNTHESIZE} } proc fpga_run_par {} { - global TOOL PRESYNTH - fpga_print "running 'place and route'" - switch $TOOL { - "libero" { - run_tool -name {PLACEROUTE} - run_tool -name {VERIFYTIMING} - } - } + run_tool -name {PLACEROUTE} + run_tool -name {VERIFYTIMING} } proc fpga_run_bit {} { - global TOOL PROJECT TOP - fpga_print "running 'bitstream generation'" - switch $TOOL { - "libero" { - run_tool -name {GENERATEPROGRAMMINGFILE} - } - } + run_tool -name {GENERATEPROGRAMMINGFILE} } -# -# Start of the script -# - -fpga_print "start of the Tcl script (interpreter $tcl_version)" - -# -# Project Creation -# - if { [lsearch -exact $TASKS "prj"] >= 0 } { - fpga_print "running the Project Creation" - if { [catch { - fpga_create $PROJECT - fpga_part $PART - fpga_commands "prefile" - fpga_files - fpga_top $TOP - fpga_params - fpga_commands "project" - fpga_close - } ERRMSG]} { - puts "ERROR: there was a problem creating a New Project.\n" - puts $ERRMSG - exit 1 - } + fpga_create $PROJECT + fpga_part $PART + {{ PRECFG }} + fpga_files + fpga_top $TOP + fpga_params + {{ POSTCFG }} + fpga_close } -# -# Design Flow -# - if { [lsearch -regexp $TASKS "syn|par|bit"] >= 0 } { - fpga_print "running the Design Flow" - if { [catch { - fpga_open $PROJECT - fpga_commands "preflow" - if { [lsearch -exact $TASKS "syn"] >= 0 } { - fpga_run_syn - fpga_commands "postsyn" - } - if { [lsearch -exact $TASKS "par"] >= 0 } { - fpga_run_par - fpga_commands "postpar" - } - if { [lsearch -exact $TASKS "bit"] >= 0 } { - fpga_run_bit - fpga_commands "postbit" - } - fpga_close - } ERRMSG]} { - puts "ERROR: there was a problem running the Design Flow.\n" - puts $ERRMSG - exit 2 + fpga_open $PROJECT + if { [lsearch -exact $TASKS "syn"] >= 0 } { + {{ PRESYN }} + fpga_run_syn + } + if { [lsearch -exact $TASKS "par"] >= 0 } { + {{ PREPAR }} + fpga_run_par + } + if { [lsearch -exact $TASKS "bit"] >= 0 } { + {{ PREBIT }} + fpga_run_bit + {{ POSTBIT }} } + fpga_close } - -# -# End of the script -# - -fpga_print "end of the Tcl script" diff --git a/pyfpga/templates/quartus.jinja b/pyfpga/templates/quartus.jinja index 8e54e4ea..4a46bce8 100644 --- a/pyfpga/templates/quartus.jinja +++ b/pyfpga/templates/quartus.jinja @@ -5,7 +5,6 @@ # SPDX-License-Identifier: GPL-3.0-or-later # -set TOOL #TOOL# set PRESYNTH #PRESYNTH# set PROJECT #PROJECT# set PART #PART# @@ -23,239 +22,108 @@ proc fpga_files {} { #FILES# } -proc fpga_commands { PHASE } { - fpga_print "setting commands for the phase '$PHASE'" - switch $PHASE { - "prefile" { -#PREFILE_CMDS# - } - "project" { -#PROJECT_CMDS# - } - "preflow" { -#PREFLOW_CMDS# - } - "postsyn" { -#POSTSYN_CMDS# - } - "postpar" { -#POSTPAR_CMDS# - } - "postbit" { -#POSTBIT_CMDS# - } - } -} - -# -# Procedures -# - -proc fpga_print { MSG } { - global TOOL - puts ">>> PyFPGA ($TOOL): $MSG" -} - proc fpga_create { PROJECT } { - global TOOL - fpga_print "creating the project '$PROJECT'" - switch $TOOL { - "quartus" { - package require ::quartus::project - project_new $PROJECT -overwrite - set_global_assignment -name NUM_PARALLEL_PROCESSORS ALL - } - } + package require ::quartus::project + project_new $PROJECT -overwrite + set_global_assignment -name NUM_PARALLEL_PROCESSORS ALL } proc fpga_open { PROJECT } { - global TOOL - fpga_print "opening the project '$PROJECT'" - switch $TOOL { - "quartus" { - package require ::quartus::flow - project_open -force $PROJECT.qpf - } - } + package require ::quartus::flow + project_open -force $PROJECT.qpf } proc fpga_close {} { - global TOOL - fpga_print "closing the project" - switch $TOOL { - "quartus" { project_close } - } + project_close } proc fpga_part { PART } { - global TOOL FAMILY DEVICE PACKAGE SPEED - fpga_print "adding the part '$PART'" - switch $TOOL { - "quartus" { - set_global_assignment -name DEVICE $PART - } - } + set_global_assignment -name DEVICE $PART } proc fpga_file {FILE {LIBRARY "work"}} { - global TOOL TOP set message "adding the file '$FILE'" if { $LIBRARY != "work" } { append message " (into the VHDL library '$LIBRARY')" } - fpga_print $message regexp -nocase {\.(\w*)$} $FILE -> ext if { $ext == "tcl" } { source $FILE return } - switch $TOOL { - "quartus" { - if {$ext == "v"} { - set TYPE VERILOG_FILE - } elseif {$ext == "sv"} { - set TYPE SYSTEMVERILOG_FILE - } elseif {$ext == "vhdl" || $ext == "vhd"} { - set TYPE VHDL_FILE - } elseif {$ext == "sdc"} { - set TYPE SDC_FILE - } else { - set TYPE SOURCE_FILE - } - if { $LIBRARY != "work" } { - set_global_assignment -name $TYPE $FILE -library $LIBRARY - } else { - set_global_assignment -name $TYPE $FILE - } - } + if {$ext == "v"} { + set TYPE VERILOG_FILE + } elseif {$ext == "sv"} { + set TYPE SYSTEMVERILOG_FILE + } elseif {$ext == "vhdl" || $ext == "vhd"} { + set TYPE VHDL_FILE + } elseif {$ext == "sdc"} { + set TYPE SDC_FILE + } else { + set TYPE SOURCE_FILE + } + if { $LIBRARY != "work" } { + set_global_assignment -name $TYPE $FILE -library $LIBRARY + } else { + set_global_assignment -name $TYPE $FILE } } proc fpga_include {PATH} { - global TOOL INCLUDED lappend INCLUDED $PATH - fpga_print "setting '$PATH' as a search location" - switch $TOOL { - "quartus" { - # Verilog Included Files are NOT added - foreach INCLUDE $INCLUDED { - set_global_assignment -name SEARCH_PATH $INCLUDE - } - } + # Verilog Included Files are NOT added + foreach INCLUDE $INCLUDED { + set_global_assignment -name SEARCH_PATH $INCLUDE } } proc fpga_top { TOP } { - global TOOL - fpga_print "specifying the top level '$TOP'" - switch $TOOL { - "quartus" { - set_global_assignment -name TOP_LEVEL_ENTITY $TOP - } - } + set_global_assignment -name TOP_LEVEL_ENTITY $TOP } proc fpga_params {} { - global TOOL PARAMS if { [llength $PARAMS] == 0 } { return } - fpga_print "setting generics/parameters" - switch $TOOL { - "quartus" { - foreach PARAM $PARAMS { - eval "set_parameter -name $PARAM" - } - } + foreach PARAM $PARAMS { + eval "set_parameter -name $PARAM" } } proc fpga_run_syn {} { - global TOOL PRESYNTH - fpga_print "running 'synthesis'" - switch $TOOL { - "quartus" { - execute_module -tool map - } - } + execute_module -tool map } proc fpga_run_par {} { - global TOOL PRESYNTH - fpga_print "running 'place and route'" - switch $TOOL { - "quartus" { - execute_module -tool fit - execute_module -tool sta - } - } + execute_module -tool fit + execute_module -tool sta } proc fpga_run_bit {} { - global TOOL PROJECT TOP - fpga_print "running 'bitstream generation'" - switch $TOOL { - "quartus" { - execute_module -tool asm - } - } + execute_module -tool asm } -# -# Start of the script -# - -fpga_print "start of the Tcl script (interpreter $tcl_version)" - -# -# Project Creation -# - if { [lsearch -exact $TASKS "prj"] >= 0 } { - fpga_print "running the Project Creation" - if { [catch { - fpga_create $PROJECT - fpga_part $PART - fpga_commands "prefile" - fpga_files - fpga_top $TOP - fpga_params - fpga_commands "project" - fpga_close - } ERRMSG]} { - puts "ERROR: there was a problem creating a New Project.\n" - puts $ERRMSG - exit 1 - } + fpga_create $PROJECT + fpga_part $PART + {{ PRECFG }} + fpga_files + fpga_top $TOP + fpga_params + {{ POSTCFG }} + fpga_close } -# -# Design Flow -# - if { [lsearch -regexp $TASKS "syn|par|bit"] >= 0 } { - fpga_print "running the Design Flow" - if { [catch { - fpga_open $PROJECT - fpga_commands "preflow" - if { [lsearch -exact $TASKS "syn"] >= 0 } { - fpga_run_syn - fpga_commands "postsyn" - } - if { [lsearch -exact $TASKS "par"] >= 0 } { - fpga_run_par - fpga_commands "postpar" - } - if { [lsearch -exact $TASKS "bit"] >= 0 } { - fpga_run_bit - fpga_commands "postbit" - } - fpga_close - } ERRMSG]} { - puts "ERROR: there was a problem running the Design Flow.\n" - puts $ERRMSG - exit 2 + fpga_open $PROJECT + if { [lsearch -exact $TASKS "syn"] >= 0 } { + {{ PRESYN }} + fpga_run_syn + } + if { [lsearch -exact $TASKS "par"] >= 0 } { + {{ PREPAR }} + fpga_run_par + } + if { [lsearch -exact $TASKS "bit"] >= 0 } { + {{ PREBIT }} + fpga_run_bit + {{ POSTBIT }} } + fpga_close } - -# -# End of the script -# - -fpga_print "end of the Tcl script" diff --git a/pyfpga/templates/vivado.jinja b/pyfpga/templates/vivado.jinja index 5758f1c7..a8fac10d 100644 --- a/pyfpga/templates/vivado.jinja +++ b/pyfpga/templates/vivado.jinja @@ -5,7 +5,6 @@ # SPDX-License-Identifier: GPL-3.0-or-later # -set TOOL #TOOL# set PRESYNTH #PRESYNTH# set PROJECT #PROJECT# set PART #PART# @@ -23,249 +22,117 @@ proc fpga_files {} { #FILES# } -proc fpga_commands { PHASE } { - fpga_print "setting commands for the phase '$PHASE'" - switch $PHASE { - "prefile" { -#PREFILE_CMDS# - } - "project" { -#PROJECT_CMDS# - } - "preflow" { -#PREFLOW_CMDS# - } - "postsyn" { -#POSTSYN_CMDS# - } - "postpar" { -#POSTPAR_CMDS# - } - "postbit" { -#POSTBIT_CMDS# - } - } -} - -# -# Procedures -# - -proc fpga_print { MSG } { - global TOOL - puts ">>> PyFPGA ($TOOL): $MSG" -} - proc fpga_create { PROJECT } { - global TOOL - fpga_print "creating the project '$PROJECT'" - switch $TOOL { - "vivado" { create_project -force $PROJECT } - } + create_project -force $PROJECT } proc fpga_open { PROJECT } { - global TOOL - fpga_print "opening the project '$PROJECT'" - switch $TOOL { - "vivado" { open_project $PROJECT } - } + open_project $PROJECT } proc fpga_close {} { - global TOOL - fpga_print "closing the project" - switch $TOOL { - "vivado" { close_project } - } + close_project } proc fpga_part { PART } { - global TOOL FAMILY DEVICE PACKAGE SPEED - fpga_print "adding the part '$PART'" - switch $TOOL { - "vivado" { - set_property "part" $PART [current_project] - } - } -} + set_property "part" $PART [current_project] + } proc fpga_file {FILE {LIBRARY "work"}} { - global TOOL TOP set message "adding the file '$FILE'" if { $LIBRARY != "work" } { append message " (into the VHDL library '$LIBRARY')" } - fpga_print $message regexp -nocase {\.(\w*)$} $FILE -> ext if { $ext == "tcl" } { source $FILE return } - switch $TOOL { - "vivado" { - if { $LIBRARY != "work" } { - add_files $FILE - set_property library $LIBRARY [get_files $FILE] - } else { - add_files $FILE - } - } + if { $LIBRARY != "work" } { + add_files $FILE + set_property library $LIBRARY [get_files $FILE] + } else { + add_files $FILE } } proc fpga_include {PATH} { - global TOOL INCLUDED lappend INCLUDED $PATH - fpga_print "setting '$PATH' as a search location" - switch $TOOL { - "vivado" { - # Verilog Included Files are NOT added - set_property "include_dirs" $INCLUDED [current_fileset] - } - } + # Verilog Included Files are NOT added + set_property "include_dirs" $INCLUDED [current_fileset] } proc fpga_design {FILE} { - global TOOL TOP INCLUDED fpga_print "including the block design '$FILE'" - switch $TOOL { - "vivado" { - if { [info exists INCLUDED] && [llength $INCLUDED] > 0 } { - set_property "ip_repo_paths" $INCLUDED [get_filesets sources_1] - update_ip_catalog -rebuild - } - source $FILE - make_wrapper -force -files [get_files design_1.bd] -top -import - if { $TOP == "UNDEFINED"} { - set TOP design_1_wrapper - } - } + if { [info exists INCLUDED] && [llength $INCLUDED] > 0 } { + set_property "ip_repo_paths" $INCLUDED [get_filesets sources_1] + update_ip_catalog -rebuild + } + source $FILE + make_wrapper -force -files [get_files design_1.bd] -top -import + if { $TOP == "UNDEFINED"} { + set TOP design_1_wrapper } } proc fpga_top { TOP } { - global TOOL - fpga_print "specifying the top level '$TOP'" - switch $TOOL { - "vivado" { - set_property top $TOP [current_fileset] - } - } + set_property top $TOP [current_fileset] } proc fpga_params {} { - global TOOL PARAMS if { [llength $PARAMS] == 0 } { return } - fpga_print "setting generics/parameters" - switch $TOOL { - "vivado" { - set assigns [list] - foreach PARAM $PARAMS { lappend assigns [join $PARAM "="] } - set obj [get_filesets sources_1] - set_property "generic" "[join $assigns]" -objects $obj - } - } + set assigns [list] + foreach PARAM $PARAMS { lappend assigns [join $PARAM "="] } + set obj [get_filesets sources_1] + set_property "generic" "[join $assigns]" -objects $obj } proc fpga_run_syn {} { - global TOOL PRESYNTH - fpga_print "running 'synthesis'" - switch $TOOL { - "vivado" { - if { $PRESYNTH == "True" } { - set_property design_mode GateLvl [current_fileset] - } else { - reset_run synth_1 - launch_runs synth_1 - wait_on_run synth_1 - } - } + if { $PRESYNTH == "True" } { + set_property design_mode GateLvl [current_fileset] + } else { + reset_run synth_1 + launch_runs synth_1 + wait_on_run synth_1 } } proc fpga_run_par {} { - global TOOL PRESYNTH - fpga_print "running 'place and route'" - switch $TOOL { - "vivado" { - if {$PRESYNTH == "False"} { - open_run synth_1 - } - launch_runs impl_1 - wait_on_run impl_1 - } + if {$PRESYNTH == "False"} { + open_run synth_1 } + launch_runs impl_1 + wait_on_run impl_1 } proc fpga_run_bit {} { - global TOOL PROJECT TOP - fpga_print "running 'bitstream generation'" - switch $TOOL { - "vivado" { - open_run impl_1 - write_bitstream -force $PROJECT - } - } + open_run impl_1 + write_bitstream -force $PROJECT } -# -# Start of the script -# - -fpga_print "start of the Tcl script (interpreter $tcl_version)" - -# -# Project Creation -# - if { [lsearch -exact $TASKS "prj"] >= 0 } { - fpga_print "running the Project Creation" - if { [catch { - fpga_create $PROJECT - fpga_part $PART - fpga_commands "prefile" - fpga_files - fpga_top $TOP - fpga_params - fpga_commands "project" - fpga_close - } ERRMSG]} { - puts "ERROR: there was a problem creating a New Project.\n" - puts $ERRMSG - exit 1 - } + fpga_create $PROJECT + fpga_part $PART + {{ PRECFG }} + fpga_files + fpga_top $TOP + fpga_params + {{ POSTCFG }} + fpga_close } -# -# Design Flow -# - if { [lsearch -regexp $TASKS "syn|par|bit"] >= 0 } { - fpga_print "running the Design Flow" - if { [catch { - fpga_open $PROJECT - fpga_commands "preflow" - if { [lsearch -exact $TASKS "syn"] >= 0 } { - fpga_run_syn - fpga_commands "postsyn" - } - if { [lsearch -exact $TASKS "par"] >= 0 } { - fpga_run_par - fpga_commands "postpar" - } - if { [lsearch -exact $TASKS "bit"] >= 0 } { - fpga_run_bit - fpga_commands "postbit" - } - fpga_close - } ERRMSG]} { - puts "ERROR: there was a problem running the Design Flow.\n" - puts $ERRMSG - exit 2 + fpga_open $PROJECT + if { [lsearch -exact $TASKS "syn"] >= 0 } { + {{ PRESYN }} + fpga_run_syn + } + if { [lsearch -exact $TASKS "par"] >= 0 } { + {{ PREPAR }} + fpga_run_par + } + if { [lsearch -exact $TASKS "bit"] >= 0 } { + {{ PREBIT }} + fpga_run_bit + {{ POSTBIT }} } + fpga_close } - -# -# End of the script -# - -fpga_print "end of the Tcl script" From b20db612652d8d4c85b8c16e99e95722d6133ca9 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Fri, 31 May 2024 19:34:40 -0300 Subject: [PATCH 047/254] Added two hook stages, returned to the single function version --- docs/hooks.rst | 2 ++ pyfpga/project.py | 47 +++++++++++++++-------------------------------- 2 files changed, 17 insertions(+), 32 deletions(-) diff --git a/docs/hooks.rst b/docs/hooks.rst index a9328332..498aceed 100644 --- a/docs/hooks.rst +++ b/docs/hooks.rst @@ -19,8 +19,10 @@ Hooks open project presyn hook synthesis + postsyn hook prepar hook place_and_route + postpar hook prebit hook bitstream postbit hook diff --git a/pyfpga/project.py b/pyfpga/project.py index 912a61e8..d700e8df 100644 --- a/pyfpga/project.py +++ b/pyfpga/project.py @@ -12,20 +12,11 @@ import os import subprocess -from enum import Enum from datetime import datetime from pathlib import Path from time import time -class Step(Enum): - """Enumeration of supported Steps""" - PRJ = 'prj' - SYN = 'syn' - PAR = 'par' - BIT = 'bit' - - class Project: """Base class to manage an FPGA project. @@ -116,32 +107,24 @@ def set_top(self, name): self.logger.debug('Executing set_top') self.data['top'] = name - def add_precfg_hook(self, content): - """Temp placeholder""" - raise NotImplementedError('Method is not implemented yet.') - - def add_postcfg_hook(self, content): - """Temp placeholder""" - raise NotImplementedError('Method is not implemented yet.') - - def add_presyn_hook(self, content): - """Temp placeholder""" - raise NotImplementedError('Method is not implemented yet.') - - def add_prepar_hook(self, content): - """Temp placeholder""" - raise NotImplementedError('Method is not implemented yet.') - - def add_prebit_hook(self, content): - """Temp placeholder""" - raise NotImplementedError('Method is not implemented yet.') - - def add_postbit_hook(self, content): - """Temp placeholder""" + def add_hook(self, stage, hook): + """Add hook for a specific stage.""" + stages = [ + 'precfg', 'postcfg', 'presyn', 'postsyn', + 'prepar', 'postpar', 'prebit', 'postbit' + ] + if stage not in stages: + raise ValueError('Invalid stage.') + _ = self + _ = hook raise NotImplementedError('Method is not implemented yet.') - def make(self, end=Step.BIT, start=Step.PRJ, capture=False): + def make(self, end='bit', start='prj'): """Temp placeholder""" + steps = ['prj', 'syn', 'par', 'bit'] + if end not in steps or start not in steps: + raise ValueError('Invalid steps.') + _ = self raise NotImplementedError('Method is not implemented yet.') def prog(self, position=1, bitstream=None): From b48ad68cda44f6026ecc69b052dca7e635a9f2b7 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Fri, 31 May 2024 21:09:02 -0300 Subject: [PATCH 048/254] Templates simplified, still WIP --- pyfpga/templates/ise.jinja | 157 ++++++++++++------------- pyfpga/templates/libero.jinja | 201 +++++++++++++++------------------ pyfpga/templates/quartus.jinja | 133 ++++++++++------------ pyfpga/templates/vivado.jinja | 156 +++++++++++-------------- 4 files changed, 291 insertions(+), 356 deletions(-) diff --git a/pyfpga/templates/ise.jinja b/pyfpga/templates/ise.jinja index 2aa7a6b1..a5f31223 100644 --- a/pyfpga/templates/ise.jinja +++ b/pyfpga/templates/ise.jinja @@ -5,41 +5,19 @@ # SPDX-License-Identifier: GPL-3.0-or-later # -set PRESYNTH #PRESYNTH# -set PROJECT #PROJECT# -set PART #PART# -set FAMILY #FAMILY# -set DEVICE #DEVICE# -set PACKAGE #PACKAGE# -set SPEED #SPEED# -set TOP #TOP# -# TASKS = prj syn par bit -set TASKS [list #TASKS#] - -set PARAMS [list #PARAMS#] +set PRESYNTH {{ PRESYNTH }} +set PROJECT {{ PROJECT }} +set PART {{ PART }} +set FAMILY {{ FAMILY }} +set DEVICE {{ DEVICE }} +set PACKAGE {{ PACKAGE }} +set SPEED {{ SPEED }} +set TOP {{ TOP }} -proc fpga_files {} { -#FILES# -} +set PARAMS [list {{ PARAMS }}] -proc fpga_create { PROJECT } { - if { [ file exists $PROJECT.xise ] } { file delete $PROJECT.xise } - project new $PROJECT.xise -} - -proc fpga_open { PROJECT } { - project open $PROJECT.xise -} - -proc fpga_close {} { - project close -} - -proc fpga_part { PART } { - project set family $FAMILY - project set device $DEVICE - project set package $PACKAGE - project set speed $SPEED +proc fpga_files {} { +{{ FILES }} } proc fpga_file {FILE {LIBRARY "work"}} { @@ -67,10 +45,6 @@ proc fpga_include {PATH} { [join $INCLUDED "|"] -process "Synthesize - XST" } -proc fpga_top { TOP } { - project set top $TOP -} - proc fpga_params {} { if { [llength $PARAMS] == 0 } { return } set assigns [list] @@ -78,56 +52,69 @@ proc fpga_params {} { project set "Generics, Parameters" "[join $assigns]" -process "Synthesize - XST" } -proc fpga_run_syn {} { - if { $PRESYNTH == "True" } { - project set top_level_module_type "EDIF" - } else { - project clean - process run "Synthesize" - if { [process get "Synthesize" status] == "errors" } { exit 2 } - } -} +#------------------------------------------------------------------------------ -proc fpga_run_par {} { - process run "Translate" - if { [process get "Translate" status] == "errors" } { exit 2 } - process run "Map" - if { [process get "Map" status] == "errors" } { exit 2 } - process run "Place & Route" - if { [process get "Place & Route" status] == "errors" } { exit 2 } -} +{% if PRJ %} +if { [ file exists $PROJECT.xise ] } { file delete $PROJECT.xise } +project new $PROJECT.xise -proc fpga_run_bit {} { - process run "Generate Programming File" - if { [process get "Generate Programming File" status] == "errors" } { exit 2 } - catch { file rename -force $TOP.bit $PROJECT.bit } -} +project set family $FAMILY +project set device $DEVICE +project set package $PACKAGE +project set speed $SPEED -if { [lsearch -exact $TASKS "prj"] >= 0 } { - fpga_create $PROJECT - fpga_part $PART - {{ PRECFG }} - fpga_files - fpga_top $TOP - fpga_params - {{ POSTCFG }} - fpga_close -} +{{ PRECFG }} -if { [lsearch -regexp $TASKS "syn|par|bit"] >= 0 } { - fpga_open $PROJECT - if { [lsearch -exact $TASKS "syn"] >= 0 } { - {{ PRESYN }} - fpga_run_syn - } - if { [lsearch -exact $TASKS "par"] >= 0 } { - {{ PREPAR }} - fpga_run_par - } - if { [lsearch -exact $TASKS "bit"] >= 0 } { - {{ PREBIT }} - fpga_run_bit - {{ POSTBIT }} - } - fpga_close +fpga_files + +project set top $TOP + +fpga_params + +{{ POSTCFG }} + +project close +{% endif %} + +{% if SYN or PAR or BIT %} +project open $PROJECT.xise + +{% if SYN %} +{{ PRESYN }} + +if { $PRESYNTH == "True" } { + project set top_level_module_type "EDIF" +} else { + project clean + process run "Synthesize" + if { [process get "Synthesize" status] == "errors" } { exit 2 } } + +{{ POSTSYN }} +{% endif %} + +{% if PAR %} +{{ PREPAR }} + +process run "Translate" +if { [process get "Translate" status] == "errors" } { exit 2 } +process run "Map" +if { [process get "Map" status] == "errors" } { exit 2 } +process run "Place & Route" +if { [process get "Place & Route" status] == "errors" } { exit 2 } + +{{ POSTPAR }} +{% endif %} + +{% if BIT %} +{{ PREBIT }} + +process run "Generate Programming File" +if { [process get "Generate Programming File" status] == "errors" } { exit 2 } +catch { file rename -force $TOP.bit $PROJECT.bit } + +{{ POSTBIT }} +{% endif %} + +project close +{% endif %} diff --git a/pyfpga/templates/libero.jinja b/pyfpga/templates/libero.jinja index 7cdcfd33..8e703ed3 100644 --- a/pyfpga/templates/libero.jinja +++ b/pyfpga/templates/libero.jinja @@ -5,38 +5,19 @@ # SPDX-License-Identifier: GPL-3.0-or-later # -set PRESYNTH #PRESYNTH# -set PROJECT #PROJECT# -set PART #PART# -set FAMILY #FAMILY# -set DEVICE #DEVICE# -set PACKAGE #PACKAGE# -set SPEED #SPEED# -set TOP #TOP# -# TASKS = prj syn par bit -set TASKS [list #TASKS#] - -set PARAMS [list #PARAMS#] +set PRESYNTH {{ PRESYNTH }} +set PROJECT {{ PROJECT }} +set PART {{ PART }} +set FAMILY {{ FAMILY }} +set DEVICE {{ DEVICE }} +set PACKAGE {{ PACKAGE }} +set SPEED {{ SPEED }} +set TOP {{ TOP }} -proc fpga_files {} { -#FILES# -} - -proc fpga_create { PROJECT } { - if { [ file exists $PROJECT ] } { file delete -force -- $PROJECT } - new_project -name $PROJECT -location $PROJECT -hdl {VHDL} -family {SmartFusion2} -} - -proc fpga_open { PROJECT } { - open_project $PROJECT/$PROJECT.prjx -} +set PARAMS [list {{ PARAMS }}] -proc fpga_close {} { - close_project -} - -proc fpga_part { PART } { - set_device -family $FAMILY -die $DEVICE -package $PACKAGE -speed $SPEED +proc fpga_files {} { +{{ FILES }} } proc fpga_file {FILE {LIBRARY "work"}} { @@ -72,90 +53,96 @@ proc fpga_include {PATH} { build_design_hierarchy } -proc fpga_top { TOP } { - set_root $TOP - # Verilog Included files - set cmd "configure_tool -name {SYNTHESIZE} -params {SYNPLIFY_OPTIONS:" - if { [info exists INCLUDED] && [llength $INCLUDED] > 0 } { - # See /poc/include/libero.tcl for details - set PATHS "../../" - append PATHS [join $INCLUDED ";../../"] - append cmd "set_option -include_path \"$PATHS\"" - append cmd "\n" - } - foreach PARAM $PARAMS { - set assign [join $PARAM] - append cmd "set_option -hdl_param -set \"$assign\"" - append cmd "\n" - } - append cmd "}" - eval $cmd - # Constraints - # PDC is only used for PLACEROUTE. - # SDC is used by ALL (SYNTHESIZE, PLACEROUTE and VERIFYTIMING). - global LIBERO_PLACE_CONSTRAINTS - global LIBERO_OTHER_CONSTRAINTS - if { [info exists LIBERO_OTHER_CONSTRAINTS] } { - set cmd "organize_tool_files -tool {SYNTHESIZE} " - append cmd $LIBERO_OTHER_CONSTRAINTS - append cmd "-module $TOP -input_type {constraint}" - eval $cmd - set cmd "organize_tool_files -tool {VERIFYTIMING} " - append cmd $LIBERO_OTHER_CONSTRAINTS - append cmd "-module $TOP -input_type {constraint}" - eval $cmd - } - if { [info exists LIBERO_PLACE_CONSTRAINTS] } { - set cmd "organize_tool_files -tool {PLACEROUTE} " - append cmd $LIBERO_PLACE_CONSTRAINTS - append cmd "-module $TOP -input_type {constraint}" - eval $cmd - } -} - proc fpga_params {} { if { [llength $PARAMS] == 0 } { return } # They must be specified after set_root (see fpga_top) } -proc fpga_run_syn {} { - run_tool -name {SYNTHESIZE} -} +#------------------------------------------------------------------------------ -proc fpga_run_par {} { - run_tool -name {PLACEROUTE} - run_tool -name {VERIFYTIMING} -} +{% if PRJ %} +if { [ file exists $PROJECT ] } { file delete -force -- $PROJECT } +new_project -name $PROJECT -location $PROJECT -hdl {VHDL} -family {SmartFusion2} -proc fpga_run_bit {} { - run_tool -name {GENERATEPROGRAMMINGFILE} -} +set_device -family $FAMILY -die $DEVICE -package $PACKAGE -speed $SPEED -if { [lsearch -exact $TASKS "prj"] >= 0 } { - fpga_create $PROJECT - fpga_part $PART - {{ PRECFG }} - fpga_files - fpga_top $TOP - fpga_params - {{ POSTCFG }} - fpga_close -} +{{ PRECFG }} -if { [lsearch -regexp $TASKS "syn|par|bit"] >= 0 } { - fpga_open $PROJECT - if { [lsearch -exact $TASKS "syn"] >= 0 } { - {{ PRESYN }} - fpga_run_syn - } - if { [lsearch -exact $TASKS "par"] >= 0 } { - {{ PREPAR }} - fpga_run_par - } - if { [lsearch -exact $TASKS "bit"] >= 0 } { - {{ PREBIT }} - fpga_run_bit - {{ POSTBIT }} - } - fpga_close +fpga_files + +set_root $TOP +# Verilog Included files +set cmd "configure_tool -name {SYNTHESIZE} -params {SYNPLIFY_OPTIONS:" +if { [info exists INCLUDED] && [llength $INCLUDED] > 0 } { + # See /poc/include/libero.tcl for details + set PATHS "../../" + append PATHS [join $INCLUDED ";../../"] + append cmd "set_option -include_path \"$PATHS\"" + append cmd "\n" +} +foreach PARAM $PARAMS { + set assign [join $PARAM] + append cmd "set_option -hdl_param -set \"$assign\"" + append cmd "\n" +} +append cmd "}" +eval $cmd +# Constraints +# PDC is only used for PLACEROUTE. +# SDC is used by ALL (SYNTHESIZE, PLACEROUTE and VERIFYTIMING). +global LIBERO_PLACE_CONSTRAINTS +global LIBERO_OTHER_CONSTRAINTS +if { [info exists LIBERO_OTHER_CONSTRAINTS] } { + set cmd "organize_tool_files -tool {SYNTHESIZE} " + append cmd $LIBERO_OTHER_CONSTRAINTS + append cmd "-module $TOP -input_type {constraint}" + eval $cmd + set cmd "organize_tool_files -tool {VERIFYTIMING} " + append cmd $LIBERO_OTHER_CONSTRAINTS + append cmd "-module $TOP -input_type {constraint}" + eval $cmd +} +if { [info exists LIBERO_PLACE_CONSTRAINTS] } { + set cmd "organize_tool_files -tool {PLACEROUTE} " + append cmd $LIBERO_PLACE_CONSTRAINTS + append cmd "-module $TOP -input_type {constraint}" + eval $cmd } + +fpga_params + +{{ POSTCFG }} + +close_project +{% endif %} + +{% if SYN or PAR or BIT %} +open_project $PROJECT/$PROJECT.prjx + +{% if SYN %} +{{ PRESYN }} + +run_tool -name {SYNTHESIZE} + +{{ POSTSYN }} +{% endif %} + +{% if PAR %} +{{ PREPAR }} + +run_tool -name {PLACEROUTE} +run_tool -name {VERIFYTIMING} + +{{ POSTPAR }} +{% endif %} + +{% if BIT %} +{{ PREBIT }} + +run_tool -name {GENERATEPROGRAMMINGFILE} + +{{ POSTBIT }} +{% endif %} + +close_project +{% endif %} diff --git a/pyfpga/templates/quartus.jinja b/pyfpga/templates/quartus.jinja index 4a46bce8..966035ca 100644 --- a/pyfpga/templates/quartus.jinja +++ b/pyfpga/templates/quartus.jinja @@ -5,40 +5,19 @@ # SPDX-License-Identifier: GPL-3.0-or-later # -set PRESYNTH #PRESYNTH# -set PROJECT #PROJECT# -set PART #PART# -set FAMILY #FAMILY# -set DEVICE #DEVICE# -set PACKAGE #PACKAGE# -set SPEED #SPEED# -set TOP #TOP# -# TASKS = prj syn par bit -set TASKS [list #TASKS#] - -set PARAMS [list #PARAMS#] +set PRESYNTH {{ PRESYNTH }} +set PROJECT {{ PROJECT }} +set PART {{ PART }} +set FAMILY {{ FAMILY }} +set DEVICE {{ DEVICE }} +set PACKAGE {{ PACKAGE }} +set SPEED {{ SPEED }} +set TOP {{ TOP }} -proc fpga_files {} { -#FILES# -} +set PARAMS [list {{ PARAMS }}] -proc fpga_create { PROJECT } { - package require ::quartus::project - project_new $PROJECT -overwrite - set_global_assignment -name NUM_PARALLEL_PROCESSORS ALL -} - -proc fpga_open { PROJECT } { - package require ::quartus::flow - project_open -force $PROJECT.qpf -} - -proc fpga_close {} { - project_close -} - -proc fpga_part { PART } { - set_global_assignment -name DEVICE $PART +proc fpga_files {} { +{{ FILES }} } proc fpga_file {FILE {LIBRARY "work"}} { @@ -75,10 +54,6 @@ proc fpga_include {PATH} { } } -proc fpga_top { TOP } { - set_global_assignment -name TOP_LEVEL_ENTITY $TOP -} - proc fpga_params {} { if { [llength $PARAMS] == 0 } { return } foreach PARAM $PARAMS { @@ -86,44 +61,56 @@ proc fpga_params {} { } } -proc fpga_run_syn {} { - execute_module -tool map -} +#------------------------------------------------------------------------------ -proc fpga_run_par {} { - execute_module -tool fit - execute_module -tool sta -} +{% if PRJ %} +package require ::quartus::project +project_new $PROJECT -overwrite +set_global_assignment -name NUM_PARALLEL_PROCESSORS ALL -proc fpga_run_bit {} { - execute_module -tool asm -} +set_global_assignment -name DEVICE $PART -if { [lsearch -exact $TASKS "prj"] >= 0 } { - fpga_create $PROJECT - fpga_part $PART - {{ PRECFG }} - fpga_files - fpga_top $TOP - fpga_params - {{ POSTCFG }} - fpga_close -} +{{ PRECFG }} -if { [lsearch -regexp $TASKS "syn|par|bit"] >= 0 } { - fpga_open $PROJECT - if { [lsearch -exact $TASKS "syn"] >= 0 } { - {{ PRESYN }} - fpga_run_syn - } - if { [lsearch -exact $TASKS "par"] >= 0 } { - {{ PREPAR }} - fpga_run_par - } - if { [lsearch -exact $TASKS "bit"] >= 0 } { - {{ PREBIT }} - fpga_run_bit - {{ POSTBIT }} - } - fpga_close -} +fpga_files + +set_global_assignment -name TOP_LEVEL_ENTITY $TOP + +fpga_params + +{{ POSTCFG }} + +project_close +{% endif %} + +{% if SYN or PAR or BIT %} +package require ::quartus::flow +project_open -force $PROJECT.qpf + +{% if SYN %} +{{ PRESYN }} + +execute_module -tool map + +{{ POSTSYN }} +{% endif %} + +{% if PAR %} +{{ PREPAR }} + +execute_module -tool fit +execute_module -tool sta + +{{ POSTPAR }} +{% endif %} + +{% if BIT %} +{{ PREBIT }} + +execute_module -tool asm + +{{ POSTBIT }} +{% endif %} + +project_close +{% endif %} diff --git a/pyfpga/templates/vivado.jinja b/pyfpga/templates/vivado.jinja index a8fac10d..0cb772ea 100644 --- a/pyfpga/templates/vivado.jinja +++ b/pyfpga/templates/vivado.jinja @@ -5,39 +5,21 @@ # SPDX-License-Identifier: GPL-3.0-or-later # -set PRESYNTH #PRESYNTH# -set PROJECT #PROJECT# -set PART #PART# -set FAMILY #FAMILY# -set DEVICE #DEVICE# -set PACKAGE #PACKAGE# -set SPEED #SPEED# -set TOP #TOP# -# TASKS = prj syn par bit -set TASKS [list #TASKS#] - -set PARAMS [list #PARAMS#] +set PRESYNTH {{ PRESYNTH }} +set PROJECT {{ PROJECT }} +set PART {{ PART }} +set FAMILY {{ FAMILY }} +set DEVICE {{ DEVICE }} +set PACKAGE {{ PACKAGE }} +set SPEED {{ SPEED }} +set TOP {{ TOP }} -proc fpga_files {} { -#FILES# -} - -proc fpga_create { PROJECT } { - create_project -force $PROJECT -} - -proc fpga_open { PROJECT } { - open_project $PROJECT -} +set PARAMS [list {{ PARAMS }}] -proc fpga_close {} { - close_project +proc fpga_files {} { +{{ FILES }} } -proc fpga_part { PART } { - set_property "part" $PART [current_project] - } - proc fpga_file {FILE {LIBRARY "work"}} { set message "adding the file '$FILE'" if { $LIBRARY != "work" } { append message " (into the VHDL library '$LIBRARY')" } @@ -60,23 +42,6 @@ proc fpga_include {PATH} { set_property "include_dirs" $INCLUDED [current_fileset] } -proc fpga_design {FILE} { - fpga_print "including the block design '$FILE'" - if { [info exists INCLUDED] && [llength $INCLUDED] > 0 } { - set_property "ip_repo_paths" $INCLUDED [get_filesets sources_1] - update_ip_catalog -rebuild - } - source $FILE - make_wrapper -force -files [get_files design_1.bd] -top -import - if { $TOP == "UNDEFINED"} { - set TOP design_1_wrapper - } -} - -proc fpga_top { TOP } { - set_property top $TOP [current_fileset] -} - proc fpga_params {} { if { [llength $PARAMS] == 0 } { return } set assigns [list] @@ -85,54 +50,63 @@ proc fpga_params {} { set_property "generic" "[join $assigns]" -objects $obj } -proc fpga_run_syn {} { - if { $PRESYNTH == "True" } { - set_property design_mode GateLvl [current_fileset] - } else { - reset_run synth_1 - launch_runs synth_1 - wait_on_run synth_1 - } -} +#------------------------------------------------------------------------------ -proc fpga_run_par {} { - if {$PRESYNTH == "False"} { - open_run synth_1 - } - launch_runs impl_1 - wait_on_run impl_1 -} +{% if PRJ %} +create_project -force $PROJECT -proc fpga_run_bit {} { - open_run impl_1 - write_bitstream -force $PROJECT -} +set_property "part" $PART [current_project] + +{{ PRECFG }} + +fpga_files + +set_property top $TOP [current_fileset] + +fpga_params -if { [lsearch -exact $TASKS "prj"] >= 0 } { - fpga_create $PROJECT - fpga_part $PART - {{ PRECFG }} - fpga_files - fpga_top $TOP - fpga_params - {{ POSTCFG }} - fpga_close +{{ POSTCFG }} + +close_project +{% endif %} + +{% if SYN or PAR or BIT %} +open_project $PROJECT + +{% if SYN %} +{{ PRESYN }} + +if { $PRESYNTH == "True" } { + set_property design_mode GateLvl [current_fileset] +} else { + reset_run synth_1 + launch_runs synth_1 + wait_on_run synth_1 } -if { [lsearch -regexp $TASKS "syn|par|bit"] >= 0 } { - fpga_open $PROJECT - if { [lsearch -exact $TASKS "syn"] >= 0 } { - {{ PRESYN }} - fpga_run_syn - } - if { [lsearch -exact $TASKS "par"] >= 0 } { - {{ PREPAR }} - fpga_run_par - } - if { [lsearch -exact $TASKS "bit"] >= 0 } { - {{ PREBIT }} - fpga_run_bit - {{ POSTBIT }} - } - fpga_close +{{ POSTSYN }} +{% endif %} + +{% if PAR %} +{{ PREPAR }} + +if {$PRESYNTH == "False"} { + open_run synth_1 } +launch_runs impl_1 +wait_on_run impl_1 + +{{ POSTPAR }} +{% endif %} + +{% if BIT %} +{{ PREBIT }} + +open_run impl_1 +write_bitstream -force $PROJECT + +{{ POSTBIT }} +{% endif %} + +close_project +{% endif %} From 52e82f1c36816efe0be6f11b4fc61169952a814b Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Fri, 31 May 2024 22:02:01 -0300 Subject: [PATCH 049/254] Renamed step PRJ as CFG --- pyfpga/project.py | 2 +- pyfpga/templates/ise.jinja | 6 ++++-- pyfpga/templates/libero.jinja | 6 ++++-- pyfpga/templates/quartus.jinja | 8 +++++--- pyfpga/templates/vivado.jinja | 9 +++++++-- 5 files changed, 21 insertions(+), 10 deletions(-) diff --git a/pyfpga/project.py b/pyfpga/project.py index d700e8df..26f5a7d9 100644 --- a/pyfpga/project.py +++ b/pyfpga/project.py @@ -121,7 +121,7 @@ def add_hook(self, stage, hook): def make(self, end='bit', start='prj'): """Temp placeholder""" - steps = ['prj', 'syn', 'par', 'bit'] + steps = ['cfg', 'syn', 'par', 'bit'] if end not in steps or start not in steps: raise ValueError('Invalid steps.') _ = self diff --git a/pyfpga/templates/ise.jinja b/pyfpga/templates/ise.jinja index a5f31223..02adbc57 100644 --- a/pyfpga/templates/ise.jinja +++ b/pyfpga/templates/ise.jinja @@ -52,9 +52,9 @@ proc fpga_params {} { project set "Generics, Parameters" "[join $assigns]" -process "Synthesize - XST" } -#------------------------------------------------------------------------------ +#--[ Project configuration ]--------------------------------------------------- -{% if PRJ %} +{% if CFG %} if { [ file exists $PROJECT.xise ] } { file delete $PROJECT.xise } project new $PROJECT.xise @@ -76,6 +76,8 @@ fpga_params project close {% endif %} +#--[ Design flow ]------------------------------------------------------------- + {% if SYN or PAR or BIT %} project open $PROJECT.xise diff --git a/pyfpga/templates/libero.jinja b/pyfpga/templates/libero.jinja index 8e703ed3..ac335d55 100644 --- a/pyfpga/templates/libero.jinja +++ b/pyfpga/templates/libero.jinja @@ -58,9 +58,9 @@ proc fpga_params {} { # They must be specified after set_root (see fpga_top) } -#------------------------------------------------------------------------------ +#--[ Project configuration ]--------------------------------------------------- -{% if PRJ %} +{% if CFG %} if { [ file exists $PROJECT ] } { file delete -force -- $PROJECT } new_project -name $PROJECT -location $PROJECT -hdl {VHDL} -family {SmartFusion2} @@ -116,6 +116,8 @@ fpga_params close_project {% endif %} +#--[ Design flow ]------------------------------------------------------------- + {% if SYN or PAR or BIT %} open_project $PROJECT/$PROJECT.prjx diff --git a/pyfpga/templates/quartus.jinja b/pyfpga/templates/quartus.jinja index 966035ca..7c94c7cb 100644 --- a/pyfpga/templates/quartus.jinja +++ b/pyfpga/templates/quartus.jinja @@ -61,12 +61,11 @@ proc fpga_params {} { } } -#------------------------------------------------------------------------------ +#--[ Project configuration ]--------------------------------------------------- -{% if PRJ %} +{% if CFG %} package require ::quartus::project project_new $PROJECT -overwrite -set_global_assignment -name NUM_PARALLEL_PROCESSORS ALL set_global_assignment -name DEVICE $PART @@ -83,9 +82,12 @@ fpga_params project_close {% endif %} +#--[ Design flow ]------------------------------------------------------------- + {% if SYN or PAR or BIT %} package require ::quartus::flow project_open -force $PROJECT.qpf +set_global_assignment -name NUM_PARALLEL_PROCESSORS ALL {% if SYN %} {{ PRESYN }} diff --git a/pyfpga/templates/vivado.jinja b/pyfpga/templates/vivado.jinja index 0cb772ea..2fc0b4bf 100644 --- a/pyfpga/templates/vivado.jinja +++ b/pyfpga/templates/vivado.jinja @@ -50,9 +50,9 @@ proc fpga_params {} { set_property "generic" "[join $assigns]" -objects $obj } -#------------------------------------------------------------------------------ +#--[ Project configuration ]--------------------------------------------------- -{% if PRJ %} +{% if CFG %} create_project -force $PROJECT set_property "part" $PART [current_project] @@ -70,8 +70,11 @@ fpga_params close_project {% endif %} +#--[ Design flow ]------------------------------------------------------------- + {% if SYN or PAR or BIT %} open_project $PROJECT +set_param general.maxThreads [expr {[exec nproc] < 32 ? [exec nproc] : 32}] {% if SYN %} {{ PRESYN }} @@ -93,6 +96,7 @@ if { $PRESYNTH == "True" } { if {$PRESYNTH == "False"} { open_run synth_1 } +reset_run impl_1 launch_runs impl_1 wait_on_run impl_1 @@ -104,6 +108,7 @@ wait_on_run impl_1 open_run impl_1 write_bitstream -force $PROJECT +write_debug_probes -force -quiet $PROJECT.ltx {{ POSTBIT }} {% endif %} From d1eb4b1c54f7d65ec163831dfce3de86a4c5cb51 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Fri, 31 May 2024 23:41:42 -0300 Subject: [PATCH 050/254] Last iteration on templates before final customization --- pyfpga/templates/ise.jinja | 21 ++++++--------- pyfpga/templates/libero.jinja | 5 ---- pyfpga/templates/quartus.jinja | 5 ---- pyfpga/templates/vivado.jinja | 47 +++++++++++++++++++++------------- 4 files changed, 37 insertions(+), 41 deletions(-) diff --git a/pyfpga/templates/ise.jinja b/pyfpga/templates/ise.jinja index 02adbc57..08678558 100644 --- a/pyfpga/templates/ise.jinja +++ b/pyfpga/templates/ise.jinja @@ -5,7 +5,6 @@ # SPDX-License-Identifier: GPL-3.0-or-later # -set PRESYNTH {{ PRESYNTH }} set PROJECT {{ PROJECT }} set PART {{ PART }} set FAMILY {{ FAMILY }} @@ -16,10 +15,6 @@ set TOP {{ TOP }} set PARAMS [list {{ PARAMS }}] -proc fpga_files {} { -{{ FILES }} -} - proc fpga_file {FILE {LIBRARY "work"}} { set message "adding the file '$FILE'" if { $LIBRARY != "work" } { append message " (into the VHDL library '$LIBRARY')" } @@ -65,7 +60,7 @@ project set speed $SPEED {{ PRECFG }} -fpga_files +{{ FILES }} project set top $TOP @@ -84,13 +79,13 @@ project open $PROJECT.xise {% if SYN %} {{ PRESYN }} -if { $PRESYNTH == "True" } { - project set top_level_module_type "EDIF" -} else { - project clean - process run "Synthesize" - if { [process get "Synthesize" status] == "errors" } { exit 2 } -} +{% if PRESYNTH %} +project set top_level_module_type "EDIF" +{% else %} +project clean +process run "Synthesize" +if { [process get "Synthesize" status] == "errors" } { exit 2 } +{% endif %} {{ POSTSYN }} {% endif %} diff --git a/pyfpga/templates/libero.jinja b/pyfpga/templates/libero.jinja index ac335d55..35ec3f52 100644 --- a/pyfpga/templates/libero.jinja +++ b/pyfpga/templates/libero.jinja @@ -5,7 +5,6 @@ # SPDX-License-Identifier: GPL-3.0-or-later # -set PRESYNTH {{ PRESYNTH }} set PROJECT {{ PROJECT }} set PART {{ PART }} set FAMILY {{ FAMILY }} @@ -16,10 +15,6 @@ set TOP {{ TOP }} set PARAMS [list {{ PARAMS }}] -proc fpga_files {} { -{{ FILES }} -} - proc fpga_file {FILE {LIBRARY "work"}} { set message "adding the file '$FILE'" if { $LIBRARY != "work" } { append message " (into the VHDL library '$LIBRARY')" } diff --git a/pyfpga/templates/quartus.jinja b/pyfpga/templates/quartus.jinja index 7c94c7cb..76c29fc5 100644 --- a/pyfpga/templates/quartus.jinja +++ b/pyfpga/templates/quartus.jinja @@ -5,7 +5,6 @@ # SPDX-License-Identifier: GPL-3.0-or-later # -set PRESYNTH {{ PRESYNTH }} set PROJECT {{ PROJECT }} set PART {{ PART }} set FAMILY {{ FAMILY }} @@ -16,10 +15,6 @@ set TOP {{ TOP }} set PARAMS [list {{ PARAMS }}] -proc fpga_files {} { -{{ FILES }} -} - proc fpga_file {FILE {LIBRARY "work"}} { set message "adding the file '$FILE'" if { $LIBRARY != "work" } { append message " (into the VHDL library '$LIBRARY')" } diff --git a/pyfpga/templates/vivado.jinja b/pyfpga/templates/vivado.jinja index 2fc0b4bf..c1805387 100644 --- a/pyfpga/templates/vivado.jinja +++ b/pyfpga/templates/vivado.jinja @@ -5,7 +5,6 @@ # SPDX-License-Identifier: GPL-3.0-or-later # -set PRESYNTH {{ PRESYNTH }} set PROJECT {{ PROJECT }} set PART {{ PART }} set FAMILY {{ FAMILY }} @@ -16,10 +15,6 @@ set TOP {{ TOP }} set PARAMS [list {{ PARAMS }}] -proc fpga_files {} { -{{ FILES }} -} - proc fpga_file {FILE {LIBRARY "work"}} { set message "adding the file '$FILE'" if { $LIBRARY != "work" } { append message " (into the VHDL library '$LIBRARY')" } @@ -54,16 +49,32 @@ proc fpga_params {} { {% if CFG %} create_project -force $PROJECT +set_property source_mgmt_mode None [current_project] +set_property STEPS.SYNTH_DESIGN.ARGS.ASSERT true [get_runs synth_1] -set_property "part" $PART [current_project] +set_property part $PART [current_project] {{ PRECFG }} -fpga_files +{{ FILES }} set_property top $TOP [current_fileset] -fpga_params +{% if DEFINES %} +set_property verilog_define { {{ DEFINES}} } [current_fileset] +{% endif %} + +{% if INCLUDES %} +set_property include_dirs { {{ INCLUDES}} } [current_fileset] +{% endif %} + +{% if PARAMS %} +set_property generic { {{ PARAMS }} } -objects [get_filesets sources_1] +{% endif %} + +{% if ARCH %} +set_property top_arch {{ ARCH }} [current_fileset] +{% endif %} {{ POSTCFG }} @@ -79,13 +90,13 @@ set_param general.maxThreads [expr {[exec nproc] < 32 ? [exec nproc] : 32}] {% if SYN %} {{ PRESYN }} -if { $PRESYNTH == "True" } { - set_property design_mode GateLvl [current_fileset] -} else { - reset_run synth_1 - launch_runs synth_1 - wait_on_run synth_1 -} +{% if PRESYNTH %} +set_property design_mode GateLvl [current_fileset] +{% else %} +reset_run synth_1 +launch_runs synth_1 +wait_on_run synth_1 +{% endif %} {{ POSTSYN }} {% endif %} @@ -93,9 +104,9 @@ if { $PRESYNTH == "True" } { {% if PAR %} {{ PREPAR }} -if {$PRESYNTH == "False"} { - open_run synth_1 -} +{% if not PRESYNTH %} +open_run synth_1 +{% endif %} reset_run impl_1 launch_runs impl_1 wait_on_run impl_1 From 504b1fcbf5b87281f51af17745b77f1286bdf05b Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sat, 1 Jun 2024 00:04:19 -0300 Subject: [PATCH 051/254] docs: re-added previously removed sections, with a WIP notice --- docs/{wip => }/advanced.rst | 18 ++--- docs/{wip => }/basic.rst | 27 +++++-- docs/index.rst | 5 +- docs/{wip/intro.rst => tools.rst} | 48 +++++++---- docs/wip/conf.py | 129 ------------------------------ docs/wip/dev.rst | 75 ----------------- docs/wip/tools.rst | 34 -------- 7 files changed, 64 insertions(+), 272 deletions(-) rename docs/{wip => }/advanced.rst (97%) rename docs/{wip => }/basic.rst (89%) rename docs/{wip/intro.rst => tools.rst} (71%) delete mode 100644 docs/wip/conf.py delete mode 100644 docs/wip/dev.rst delete mode 100644 docs/wip/tools.rst diff --git a/docs/wip/advanced.rst b/docs/advanced.rst similarity index 97% rename from docs/wip/advanced.rst rename to docs/advanced.rst index 3ee54d32..245f4c08 100644 --- a/docs/wip/advanced.rst +++ b/docs/advanced.rst @@ -1,12 +1,12 @@ -.. program:: pyfpga +Advanced usage +============== -.. _advanced: +.. ATTENTION:: -Advanced usage -############## + (2024-05-31) To be updated. Multi project managment -======================= +----------------------- .. code-block:: python @@ -54,7 +54,7 @@ Multi project managment .. _hooks: Hooks -===== +----- The following table depicts the parts of the *Project Creation* and the *Design Flow* internally performed by PyFPGA. @@ -93,7 +93,7 @@ specify additional *hooks* in different parts of the flow, using: method several times (the commands will be executed in order). Parameters -========== +---------- The generics/parameters of the project can be optionally changed with: @@ -104,7 +104,7 @@ The generics/parameters of the project can be optionally changed with: prj.add_param('paramN', valueN) Generate options -================ +---------------- The method ``generate`` (previously seen at the end of [Basic usage](#basic-usage) section) has optional parameters: @@ -143,7 +143,7 @@ In case of *capture*, it is useful to catch execution messages to be post-processed or saved to a file. Exceptions -========== +---------- Finally, you must run the bitstream generation or its transfer. Both of them are time-consuming tasks, performed by a backend tool, which could fail. diff --git a/docs/wip/basic.rst b/docs/basic.rst similarity index 89% rename from docs/wip/basic.rst rename to docs/basic.rst index 7a5456c8..824aea8d 100644 --- a/docs/wip/basic.rst +++ b/docs/basic.rst @@ -1,12 +1,12 @@ -.. program:: pyfpga +Basic usage +=========== -.. _basic: +.. ATTENTION:: -Basic usage -########### + (2024-05-31) To be updated. Project Creation -================ +---------------- The first steps are import the module and instantiate the ``Project`` *class*, specifying the *TOOL* to use and, optionally, a *PROJECT NAME* (the *tool* @@ -23,6 +23,17 @@ name is used when *no name* is provided). The supported tool are: ``ghdl``, ``ise``, ``libero``, ``openflow``, ``quartus``, ``vivado``, ``yosys``, ``yosys-ise`` and ``yosys-vivado``. +.. ATTENTION:: + + PyFPGA assumes that the backend Tool is ready to run. + This implies, depending on the operating system, things such as: + + * Tool installed. + * A valid License configured. + * Tool available in the system PATH. + * GNU/Linux: extra packages installed, environment variables assigned + and permissions granted on devices (to transfer the bitstream). + By default, the directory where the project is generated is called ``build`` and is located in the same place that the script, but another name and location can be specified. @@ -85,7 +96,7 @@ Finally, the top-level must be specified: to automatically extract the top-level name. Project generation -================== +------------------ Next step if to generate the project. In the most basic form, you can run the following to get a bitstream: @@ -111,7 +122,7 @@ Additionally, you can specify which task to perform: * ``bit``: (default) to perform synthesis, implementation and bitstream generation. Bitstream transfer -================== +------------------ This method is in charge of run the needed tool to transfer a bitstream to a device (commonly an FPGA, but memories are also supported in some cases). @@ -134,7 +145,7 @@ Jtag chain. When a memory is used as *devtype*, the *part* name and the (udev rule, be a part of a group, etc). Logging capabilities -==================== +-------------------- PyFPGA uses the `logging `_ module, with a *NULL* handler and the *INFO* level by default. diff --git a/docs/index.rst b/docs/index.rst index 96f4b3d9..4984a41d 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -8,11 +8,14 @@ PyFPGA's documentation .. ATTENTION:: - (2024-05-20) PyFPGA is in the process of being strongly rewritten/simplified. + (2024-05-31) PyFPGA is in the process of being strongly rewritten/simplified. Most changes are internal, but the API will also change. .. toctree:: intro + basic + advanced hooks + tools api diff --git a/docs/wip/intro.rst b/docs/tools.rst similarity index 71% rename from docs/wip/intro.rst rename to docs/tools.rst index b10d326a..0bbbcc5a 100644 --- a/docs/wip/intro.rst +++ b/docs/tools.rst @@ -1,18 +1,39 @@ -.. program:: pyfpga - -Introduction -############ +Tools support +============= .. ATTENTION:: - PyFPGA assumes that the backend Tool is ready to run. - This implies, depending on the operating system, things such as: + (2024-05-31) To be updated. + ++---------------+-----------+---------+-----+-----------------------------------------------+ +| Tools | Vendor | Version | Tcl | Comment | ++===============+===========+=========+=====+===============================================+ +| ISE | Xilinx | 14.7 | 8.4 | Discontinued in 2013 | ++---------------+-----------+---------+-----+-----------------------------------------------+ +| Libero-SoC | Microsemi | 12.2 | 8.5 | Important changes in version 12.0 (2019) | ++---------------+-----------+---------+-----+-----------------------------------------------+ +| Quartus Prime | Intel | 19.1 | 8.6 | Known as Quartus II until version 15.0 (2015) | ++---------------+-----------+---------+-----+-----------------------------------------------+ +| Vivado | Xilinx | 2019.1 | 8.5 | Introduced in 2012, it superseded ISE | ++---------------+-----------+---------+-----+-----------------------------------------------+ +| Yosys | | 0.9-dev | 8.6 | The open-source synthesizer | ++---------------+-----------+---------+-----+-----------------------------------------------+ + + +* ISE supports devices starting from Spartan 3/Virtex 4 until some first members of the 7 series. + Previous Spartan/Virtex devices were supported until version 10. Vivado supports devices starting + from the 7 series. - * Tool installed. - * A valid License configured. - * Tool available in the system PATH. - * GNU/Linux: extra packages installed, environment variables assigned - and permissions granted on devices (to transfer the bitstream). +* Libero-SoC had a fork for PolarFire devices which was merged in version 12.0 (2019). + Libero SoC v12.0 and later supports PolarFire, RTG4, SmartFusion2 and IGLOO2 FPGA families. + Libero SoC v11.9 and earlier are the alternative to work with SmartFusion, IGLOO, ProASIC3 and + Fusion families. + Libero IDE v9.2 (2016) was the last version of the previous tool to work with antifuse and older + flash devices. + +* Since the change from Quartus II to Prime, three editions are available: Pro (for Agilex, + Stratix 10, Arria 10 and Cyclone GX devices), Standard (for Cyclone 10 LP and earlier devices) + and Lite (a high-volume low-end subset of the Standard edition). Detailed support ---------------- @@ -80,8 +101,3 @@ Detailed support * ``NY``: Not yet, but maybe someday * ``TBD``: To Be Defined * ``TBI``: To Be Implemented - -Next Steps ----------- - -You can read the :ref:`basic` and :ref:`advanced` sections, check the detailed :ref:`api` or start with the available :repositoy:`Examples `. diff --git a/docs/wip/conf.py b/docs/wip/conf.py deleted file mode 100644 index ffead9cc..00000000 --- a/docs/wip/conf.py +++ /dev/null @@ -1,129 +0,0 @@ -# -*- coding: utf-8 -*- - -import sys, re -from pathlib import Path -#from json import dump, loads - -sys.path.insert(0, str(Path('.').resolve())) - -# -- General configuration ------------------------------------------------ - -needs_sphinx = '3.0' - -extensions = [ - #'recommonmark', - 'sphinx.ext.autodoc', - 'sphinx.ext.extlinks', - 'sphinx.ext.intersphinx', - 'sphinx.ext.todo', - 'sphinx.ext.graphviz', - 'sphinx.ext.mathjax', - 'sphinx.ext.ifconfig', - 'sphinx.ext.viewcode', -] - -autodoc_default_options = { - "members": True, - 'undoc-members': True, - #'private-members': True, - 'inherited-members': True, -} - -templates_path = ['_templates'] - -source_suffix = { - '.rst': 'restructuredtext', - # '.txt': 'markdown', - #'.md': 'markdown', -} - -master_doc = 'index' - -roject = u'PyFPGA' -copyright = u'2019-2021, Rodrigo Alejandro Melo and contributors' -author = u'Rodrigo Alejandro Melo and contributors' - -version = "latest" -release = version # The full version, including alpha/beta/rc tags. - -language = None - -exclude_patterns = [] - -todo_include_todos = True -todo_link_only = True - -# -- Options for HTML output ---------------------------------------------- - -html_logo = "images/logo.png" - -html_theme_options = { - 'logo_only': True, - 'home_breadcrumbs': False, - 'vcs_pageview_mode': 'blob', -} - -html_context = { - 'gitlab_user': 'rodrigomelo9', - 'gitlab_repo': 'pyfpga', - 'gitlab_version': 'master', - 'conf_py_path': '/doc/', - 'display_gitlab': True -} -#ctx = Path(__file__).resolve().parent / 'context.json' -#if ctx.is_file(): -# html_context.update(loads(ctx.open('r').read())) - -html_theme_path = ["."] -html_theme = "_theme" - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -#html_static_path = ['_static'] - -# Output file base name for HTML help builder. -htmlhelp_basename = 'PyFPGAdoc' - -# -- Options for LaTeX output --------------------------------------------- - -latex_elements = { - 'papersize': 'a4paper', -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, author, documentclass [howto, manual, or own class]). -latex_documents = [ - (master_doc, 'PyFPGA.tex', u'PyFPGA Documentation', author, 'manual'), -] - -# -- Options for manual page output --------------------------------------- - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - (master_doc, 'PyFPGA', u'PyFPGA Documentation', [author], 1) -] - -# -- Options for Texinfo output ------------------------------------------- - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, dir menu entry, description, category) -texinfo_documents = [ - (master_doc, 'PyFPGA', u'PyFPGA Documentation', author, 'PyFPGA', 'Python Class for vendor-independent FPGA development', 'Miscellaneous'), -] - -# -- Sphinx.Ext.InterSphinx ----------------------------------------------- - -#intersphinx_mapping = { -# 'python': ('https://docs.python.org/3.8/', None), -# 'ghdl': ('https://ghdl.github.io/ghdl', None), -# 'vunit': ('https://vunit.github.io', None), -# 'matplotlib': ('https://matplotlib.org/', None) -#} - -# -- Sphinx.Ext.ExtLinks -------------------------------------------------- -extlinks = { - 'wikipedia': ('https://en.wikipedia.org/wiki/%s', None), - 'repo': ('https://github.com/PyFPGA/pyfpga/blob/main/%s', None) -} diff --git a/docs/wip/dev.rst b/docs/wip/dev.rst deleted file mode 100644 index 39486ea3..00000000 --- a/docs/wip/dev.rst +++ /dev/null @@ -1,75 +0,0 @@ -.. program:: pyfpga - -Development Notes -################# - -To install a local clone of the repository (for development): - -.. code-block:: shell - - git clone https://gitlab.com/rodrigomelo9/pyfpga.git - cd pyfpga - sudo pip install -e . - -.. NOTE:: - With `-e` (`--editable`) your application is installed into site-packages - via a kind of symlink, so you do not need to reinstall it after changes. - -PyFPGA uses PEP8 guidelines. - -The following is an overview of the main PyFPGA components and its -relationship, which is explained in the sub-sections of this document. - -.. figure:: images/schema.png - :align: center - :alt: PyFPGA components - - PyFPGA components - -fpga/tool/template.tcl -====================== - -Many (all?) FPGA development Tools provides a Tcl (Tool Command Language) -interface for the Bitstream generation. -This multi-vendor master Tcl was developed, where the different commands to -solve the complete workflow were encapsulated into procedures -(using the ``fpga_*`` prefix to avoid namespace conflicts). - -.. NOTE:: - To add a new Tool, a *case* in the *switch* of each ``fpga_*`` must be - provided. - -.. NOTE:: - This file is compliant with Tcl 8.4 because is the oldest used by a - supported FPGA Tool (Xilinx ISE). - -fpga/tool/.py -=================== - -A base class (``__init__.py``) was developed to provides a uniform API to be -implemented for each supported Tool. -Also, validation of values is performed here. - -Classes to supports each Tool (``.py``) implements the base class, ideally -setting a few variables. - -.. NOTE:: - Transfer of the bitstream to a device is not always performed by a Tcl - script, so special methods must be developed, following the proposed API. - -fpga/project.py -=============== - -This class implements the Application Programming Interface (API) which is -employed to manage an FPGA project. It solves high-level things such as -collect several files using glob, setting and use of a working/output -directory and time measurement. - -FAQ -==== - -How to deal with deprecated Tcl commands? ------------------------------------------ - -From Vivado 2019.1 to 2019.2, ``open_hw`` changed to ``open_hw_manager``: -``if { [ catch { open_hw_manager } ] } { open_hw }`` diff --git a/docs/wip/tools.rst b/docs/wip/tools.rst deleted file mode 100644 index 365a4844..00000000 --- a/docs/wip/tools.rst +++ /dev/null @@ -1,34 +0,0 @@ -.. program:: pyfpga - -Per tool usage -############## - -+---------------+-----------+---------+-----+-----------------------------------------------+ -| Tools | Vendor | Version | Tcl | Comment | -+===============+===========+=========+=====+===============================================+ -| ISE | Xilinx | 14.7 | 8.4 | Discontinued in 2013 | -+---------------+-----------+---------+-----+-----------------------------------------------+ -| Libero-SoC | Microsemi | 12.2 | 8.5 | Important changes in version 12.0 (2019) | -+---------------+-----------+---------+-----+-----------------------------------------------+ -| Quartus Prime | Intel | 19.1 | 8.6 | Known as Quartus II until version 15.0 (2015) | -+---------------+-----------+---------+-----+-----------------------------------------------+ -| Vivado | Xilinx | 2019.1 | 8.5 | Introduced in 2012, it superseded ISE | -+---------------+-----------+---------+-----+-----------------------------------------------+ -| Yosys | | 0.9-dev | 8.6 | The open-source synthesizer | -+---------------+-----------+---------+-----+-----------------------------------------------+ - - -* ISE supports devices starting from Spartan 3/Virtex 4 until some first members of the 7 series. - Previous Spartan/Virtex devices were supported until version 10. Vivado supports devices starting - from the 7 series. - -* Libero-SoC had a fork for PolarFire devices which was merged in version 12.0 (2019). - Libero SoC v12.0 and later supports PolarFire, RTG4, SmartFusion2 and IGLOO2 FPGA families. - Libero SoC v11.9 and earlier are the alternative to work with SmartFusion, IGLOO, ProASIC3 and - Fusion families. - Libero IDE v9.2 (2016) was the last version of the previous tool to work with antifuse and older - flash devices. - -* Since the change from Quartus II to Prime, three editions are available: Pro (for Agilex, - Stratix 10, Arria 10 and Cyclone GX devices), Standard (for Cyclone 10 LP and earlier devices) - and Lite (a high-volume low-end subset of the Standard edition). From a01bcd7cc20fbc55eaf9306585e8a7d938934d8c Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sat, 1 Jun 2024 01:09:36 -0300 Subject: [PATCH 052/254] README updated/simplified --- README.md | 97 ++++++++++++++----------------------------------------- 1 file changed, 24 insertions(+), 73 deletions(-) diff --git a/README.md b/README.md index 671fa6a1..3a60df40 100644 --- a/README.md +++ b/README.md @@ -6,24 +6,19 @@ ![ISE](https://img.shields.io/badge/ISE-14.7-blue.svg?style=flat-square) ![Openflow](https://img.shields.io/badge/Openflow-GHDL%20%7C%20Yosys%20%7C%20nextpnr%20%7C%20icestorm%20%7C%20prjtrellis-darkgreen.svg?style=flat-square) -> **WARNING:** (2024-05-20) PyFPGA is in the process of being strongly rewritten/simplified. -> Most changes are internal, but the API will also change. +PyFPGA is an abstraction layer for working with FPGA development tools in a vendor-agnostic, programmatic way. It is a Python package that provides: +* One **class** per supported tool for **project creation**, **synthesis**, **place and route**, **bitstream generation**, and **programming**. +* A set of **command-line helpers** for simple projects or quick evaluations. -PyFPGA is a **Python Package** for **vendor-agnostic** FPGA development. -It provides a **Class** which allows the programmatically execution of **synthesis**, -**place and route**, **bitstream generation** and/or **programming** of FPGA devices. -Additionally, a set of **command-line helpers** are provided for quick and simple runs. +With PyFPGA, you can create your own FPGA development workflow tailored to your needs! -Features: -* It's *Version Control Systems* and *Continuous Integration* friendly. -* Allows reproducibility and repeatability. -* Consumes fewer system resources than GUI based workflows. +Some of its benefits are: +* It provides a unified API between tools/devices. +* It's **Version Control Systems** and **Continuous Integration friendly**. +* It ensures reproducibility and repeatability. +* It consumes fewer system resources than GUI-based workflows. -With PyFPGA you can create your custom FPGA tool using a workflow tailored to your needs! - -## Usage - -A minimal example of how to use PyFPGA: +## Basic example ```py from fpga import Project @@ -48,80 +43,36 @@ prj.set_top('Top') prj.generate() ``` -Now, you can read the [docs](https://pyfpga.github.io/pyfpga/) or find -more examples in subdir [examples](examples). +The next steps are to read the [docs](https://pyfpga.github.io/pyfpga) or take a look at [examples](examples). -The API implemented by the `Project class` provides: +## Support -- A constructor where the TOOL must be specified and an optional PROJECT NAME can be indicated -- Methods to set the target device PART, to add multiple HDL, Constraint and Tcl files to the - project (in case of VHDL an optional PACKAGE NAME can be specified) and to specify the TOP-LEVEL -- Methods to specify a different OUTPUT directory or get some project configurations -- Methods to generate a bitstream and transfer it to a device (running the selected EDA Tool) -- The capability of specifying an optimization strategy (area, power or speed) when the bitstream - is generated -- A method to add Verilog Included File directories -- A method to specify generics/parameters values -- Methods to add Tcl commands in up to six different parts of the Flow (workaround for not yet - implemented features) -- Optional logging capabilities which include the display of the Tool Execution Time +PyFPGA is a Python package developed having GNU/Linux platform on mind, but it should run well on any POSIX-compatible OS, and probably others! +If you encounter compatibility issues, please inform us via the [issues](https://github.com/PyFPGA/pyfpga/issues) tracker. -## Support +For a comprehensive list of supported tools, features and limitations, please refer to the [tools support](https://pyfpga.github.io/pyfpga/tools.html) page. -PyFPGA is a Python 3 package, which is developed on Debian GNU/Linux. -It should run on any other POSIX compatible OS and probably also on different OS. -Should you achieve either success of failure on non-POSIX systems, please let us know through the -[issue](https://github.com/PyFPGA/pyfpga/issues) tracker. - -- The whole development flow (from reading HDL and constraint sources to producing a bitstream) - can be performed with ISE (Xilinx), Vivado (Xilinx), Quarts Prime (Intel/Altera), Libero-SoC - (Microsemi) and/or with open-source tools. -- GDHL (`--synth`) allows converting VHDL sources into a VHDL 1993 netlist. -- Yosys allows synthesising Verilog and VHDL (using `ghdl-yosys-plugin`) and supports multiple - output formats: JSON, Verilog, EDIF, etc. - - nextpnr can be used for implementation of JSON netlists. - - Also, ISE and Vivado are supported for implementation of Verilog netlists. -- Transferring bitstreams and programming devices: - - ISE (Impact) can be used for programming FPGAs and/or memories (BPI and SPI) through JTAG. - - Vivado, Quartus and iceprog (IceStorm, for ice40 devices) can be used to programming FPGAs. - - Programming with Libero-SoC and programming ECP5 devices (prjtrellis, openocd) is not yet - supported. - -**Notes:** - -- The open-source tools are supported trough container images from the -[HDL containers](https://hdl.github.io/containers) project, so -[Docker](https://www.docker.com/) ~~or [Podman](https://podman.io/)~~ must be -installed. The same workflow can be used in CI services. -- ISE, Libero-Soc, Quartus Prime and Vivado, must be ready to be executed from -the terminal (installed and well configured). +> **NOTE:** +> PyFPGA assumes that the underlying tools required for operation are ready to be executed from the running terminal. +> This includes having the tools installed, properly configured, and licensed (when needed). ## Installation -PyFPGA requires Python `>=3.6`. For now, it's only available as a git repository -hosted on GitHub. It can be installed with pip: +PyFPGA requires Python>=3.7. + +At the moment, it's only available as a git repository hosted on GitHub. It can be installed with pip: ``` pip install 'git+https://github.com/PyFPGA/pyfpga#egg=pyfpga' ``` -> On GNU/Linux, installing pip packages on the system requires `sudo`. -> Alternatively, use `--local` for installing PyFPGA in your HOME. - -You can get a copy of the repository either through git clone or downloading a -tarball/zipfile: +Alternatively, you can get a copy of the repository either through git clone or downloading a tarball/zipfile, and then: ``` git clone https://github.com/PyFPGA/pyfpga.git cd pyfpga -``` - -Then, use pip from the root of the repo: - -``` pip install -e . ``` -> With `-e` (`--editable`) your application is installed into site-packages via -> a kind of symlink. That allows pulling changes through git or changing the -> branch, without the need to reinstall the package. +> With `-e` (`--editable`) your application is installed into site-packages via a kind of symlink. +> That allows pulling changes through git or changing the branch, avoiding the need to reinstall the package. From 087e3a6d79829b8a91308ab20b0758ffe01ce098 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sat, 1 Jun 2024 18:56:30 -0300 Subject: [PATCH 053/254] Moved helpers from fpga to pyfpga --- {fpga => pyfpga}/helpers/bitprog.py | 0 {fpga => pyfpga}/helpers/hdl2bit.py | 0 {fpga => pyfpga}/helpers/prj2bit.py | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename {fpga => pyfpga}/helpers/bitprog.py (100%) rename {fpga => pyfpga}/helpers/hdl2bit.py (100%) rename {fpga => pyfpga}/helpers/prj2bit.py (100%) diff --git a/fpga/helpers/bitprog.py b/pyfpga/helpers/bitprog.py similarity index 100% rename from fpga/helpers/bitprog.py rename to pyfpga/helpers/bitprog.py diff --git a/fpga/helpers/hdl2bit.py b/pyfpga/helpers/hdl2bit.py similarity index 100% rename from fpga/helpers/hdl2bit.py rename to pyfpga/helpers/hdl2bit.py diff --git a/fpga/helpers/prj2bit.py b/pyfpga/helpers/prj2bit.py similarity index 100% rename from fpga/helpers/prj2bit.py rename to pyfpga/helpers/prj2bit.py From e9f2dca70c65bfaa177b5989a4b491af5a88c211 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sat, 1 Jun 2024 18:57:38 -0300 Subject: [PATCH 054/254] Renamed fpga/project.py as pyfpga/factory.py --- fpga/project.py => pyfpga/factory.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename fpga/project.py => pyfpga/factory.py (100%) diff --git a/fpga/project.py b/pyfpga/factory.py similarity index 100% rename from fpga/project.py rename to pyfpga/factory.py From b6a0356bb364e7113463687be89bbe803a468ba5 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sat, 1 Jun 2024 19:02:34 -0300 Subject: [PATCH 055/254] Simplified some methods at project, defined internal data structure, updated setup.py --- pyfpga/project.py | 12 +++--------- setup.py | 16 ++++++++-------- tests/test_data.py | 19 +++++++++++++++++-- 3 files changed, 28 insertions(+), 19 deletions(-) diff --git a/pyfpga/project.py b/pyfpga/project.py index 26f5a7d9..5ac00cd4 100644 --- a/pyfpga/project.py +++ b/pyfpga/project.py @@ -79,23 +79,17 @@ def add_vlog(self, pathname, options=None): def add_include(self, path): """Temp placeholder""" self.logger.debug('Executing add_include') - if 'includes' not in self.data: - self.data['includes'] = [] - self.data['includes'].append(path) + self.data.setdefault('includes', []).append(path) def add_param(self, name, value): """Temp placeholder""" self.logger.debug('Executing add_param') - if 'params' not in self.data: - self.data['params'] = {} - self.data['params'][name] = value + self.data.setdefault('params', {})[name] = value def add_define(self, name, value): """Temp placeholder""" self.logger.debug('Executing add_define') - if 'defines' not in self.data: - self.data['defines'] = {} - self.data['defines'][name] = value + self.data.setdefault('defines', {})[name] = value def set_arch(self, name): """Temp placeholder""" diff --git a/setup.py b/setup.py index 05036c4a..e6b45d1b 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ setup( name='pyfpga', - version=fpga.__version__, + version=pyfpga.__version__, description='A Python package to use FPGA development tools programmatically', long_description=long_description, long_description_content_type='text/markdown', @@ -15,13 +15,13 @@ author_email='rodrigomelo9@gmail.com', license='GPLv3', url='https://github.com/PyFPGA/pyfpga', - package_data={'': ['tool/*.sh', 'tool/*.tcl', 'helpers/*']}, + package_data={'': ['templates/*.jinja', 'helpers/*']}, packages=find_packages(), entry_points={ 'console_scripts': [ - 'fpga-hdl2bit = fpga.helpers.hdl2bit:main', - 'fpga-prj2bit = fpga.helpers.prj2bit:main', - 'fpga-bitprog = fpga.helpers.bitprog:main' + 'fpga-hdl2bit = pyfpga.helpers.hdl2bit:main', + 'fpga-prj2bit = pyfpga.helpers.prj2bit:main', + 'fpga-bitprog = pyfpga.helpers.bitprog:main' ], }, classifiers=[ @@ -30,14 +30,14 @@ 'Intended Audience :: Developers', 'License :: OSI Approved :: GNU General Public License v3 (GPLv3)', 'Operating System :: OS Independent', - 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: 3.11', + 'Programming Language :: Python :: 3.12', 'Topic :: Utilities', 'Topic :: Software Development :: Build Tools', "Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)" - ], - install_requires=['pyyaml'] + ] ) diff --git a/tests/test_data.py b/tests/test_data.py index e603af21..3b7da4df 100644 --- a/tests/test_data.py +++ b/tests/test_data.py @@ -5,9 +5,13 @@ pattern = { 'part': 'PARTNAME', - 'top': 'TOPNAME', - 'arch': 'ARCHNAME', 'includes': ['INC1', 'INC2', 'INC3'], + 'files': { + 'path1': {'type': 'TYPE1', 'options': 'OPTION1', 'library': 'LIBRARY1'}, + 'path2': {'type': 'TYPE2', 'options': 'OPTION2', 'library': 'LIBRARY2'}, + 'path3': {'type': 'TYPE3', 'options': 'OPTION3', 'library': 'LIBRARY3'} + }, + 'top': 'TOPNAME', 'params': { 'PARAM1': 'VALUE1', 'PARAM2': 'VALUE2', @@ -18,6 +22,17 @@ 'DEF2': 'VALUE2', 'DEF3': 'VALUE3' }, + 'arch': 'ARCHNAME', + 'hooks': { + 'precfg': ['HOOK1', 'HOOK2', 'HOOKn'], + 'postcfg': ['HOOK1', 'HOOK2', 'HOOKn'], + 'presyn': ['HOOK1', 'HOOK2', 'HOOKn'], + 'postsyn': ['HOOK1', 'HOOK2', 'HOOKn'], + 'prepar': ['HOOK1', 'HOOK2', 'HOOKn'], + 'postpar': ['HOOK1', 'HOOK2', 'HOOKn'], + 'prebit': ['HOOK1', 'HOOK2', 'HOOKn'], + 'postbit': ['HOOK1', 'HOOK2', 'HOOKn'] + } } From 16b3999a832341b3626e15ae0c6177f2f4284444 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sat, 1 Jun 2024 19:31:11 -0300 Subject: [PATCH 056/254] Fixed pylint complaint --- tests/test_data.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_data.py b/tests/test_data.py index 3b7da4df..66652fd5 100644 --- a/tests/test_data.py +++ b/tests/test_data.py @@ -7,9 +7,9 @@ 'part': 'PARTNAME', 'includes': ['INC1', 'INC2', 'INC3'], 'files': { - 'path1': {'type': 'TYPE1', 'options': 'OPTION1', 'library': 'LIBRARY1'}, - 'path2': {'type': 'TYPE2', 'options': 'OPTION2', 'library': 'LIBRARY2'}, - 'path3': {'type': 'TYPE3', 'options': 'OPTION3', 'library': 'LIBRARY3'} + 'path1': {'type': 'TYPE1', 'options': 'OPT1', 'library': 'LIB1'}, + 'path2': {'type': 'TYPE2', 'options': 'OPT2', 'library': 'LIB2'}, + 'path3': {'type': 'TYPE3', 'options': 'OPT3', 'library': 'LIB3'} }, 'top': 'TOPNAME', 'params': { From 2bf79bc5f5e23b085089b783b39e74148f684243 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sat, 1 Jun 2024 20:17:52 -0300 Subject: [PATCH 057/254] Fixed pylint complaints --- pyfpga/factory.py | 80 ++++++++--------------------------------------- 1 file changed, 13 insertions(+), 67 deletions(-) diff --git a/pyfpga/factory.py b/pyfpga/factory.py index 069d2be7..d05695d1 100644 --- a/pyfpga/factory.py +++ b/pyfpga/factory.py @@ -1,24 +1,11 @@ # -# Copyright (C) 2019-2022 Rodrigo A. Melo -# Copyright (C) 2019-2020 INTI +# Copyright (C) 2019-2024 Rodrigo A. Melo # -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . +# SPDX-License-Identifier: GPL-3.0-or-later # -"""fpga.project -This module implements the entry-point of PyFPGA, which provides -functionalities to create a project, generate a bitstream and program a device. +""" +A factory class to create FPGA projects. """ import contextlib @@ -29,6 +16,12 @@ import re import time +from fpga.tool.ise import Ise +from fpga.tool.libero import Libero +from fpga.tool.openflow import Openflow +from fpga.tool.quartus import Quartus +from fpga.tool.vivado import Vivado + TOOLS = [ 'ghdl', 'ise', 'libero', 'openflow', 'quartus', 'vivado', 'yosys', @@ -57,29 +50,22 @@ class Project: """ def __init__( - self, tool='vivado', project=None, meta=None, + self, tool='vivado', project=None, relative_to_script=True): """Class constructor.""" if tool == 'ghdl': - from fpga.tool.openflow import Openflow self.tool = Openflow(project, frontend='ghdl', backend='vhdl') elif tool in ['ise', 'yosys-ise']: - from fpga.tool.ise import Ise self.tool = Ise(project, 'yosys' if 'yosys' in tool else '') elif tool == 'libero': - from fpga.tool.libero import Libero self.tool = Libero(project) elif tool == 'openflow': - from fpga.tool.openflow import Openflow self.tool = Openflow(project) elif tool == 'quartus': - from fpga.tool.quartus import Quartus self.tool = Quartus(project) elif tool in ['vivado', 'yosys-vivado']: - from fpga.tool.vivado import Vivado self.tool = Vivado(project, 'yosys' if 'yosys' in tool else '') elif tool == 'yosys': - from fpga.tool.openflow import Openflow self.tool = Openflow(project, frontend='yosys', backend='verilog') else: raise NotImplementedError(tool) @@ -93,42 +79,6 @@ def __init__( self._absdir = os.path.join(self._rundir, self._reldir) _log.debug('ABSDIR = %s', self._absdir) self.set_outdir('build') - self._initialize(meta) - - def _initialize(self, meta): - """Set some of the most used internal parameters.""" - if meta is None: - return - if 'outdir' in meta: - _log.debug('OUTDIR = %s', meta['outdir']) - self.set_outdir(meta['outdir']) - if 'part' in meta: - _log.debug('PART = %s', meta['part']) - self.set_part(meta['part']) - if 'paths' in meta: - for path in meta['paths']: - _log.debug('PATH = %s', path) - self.add_vlog_include(path) - for filetype in ['vhdl', 'verilog', 'constraint']: - if filetype in meta: - for file in meta[filetype]: - if isinstance(file, list): - filename = file[0] - library = file[1] - else: - filename = file - library = None - _log.debug( - 'FILE = %s %s %s', filename, filetype, library - ) - self.add_files(filename, filetype, library) - if 'params' in meta: - for parname, parvalue in meta['params'].items(): - _log.debug('PARAM = %s %s', parname, parvalue) - self.add_param(parname, parvalue) - if 'top' in meta: - _log.debug('TOP = %s', meta['top']) - self.set_top(meta['top']) def set_outdir(self, outdir): """Sets the OUTput DIRectory (where to put the resulting files). @@ -324,8 +274,7 @@ def set_bitstream(self, path): self.tool.set_bitstream(path) def transfer( - self, devtype='fpga', position=1, part='', width=1, - capture=False): + self, devtype='fpga', position=1, part='', width=1): """Transfers the generated bitstream to a device. :param devtype: *fpga* or other valid option @@ -333,7 +282,6 @@ def transfer( :param position: position of the device in the JTAG chain :param part: name of the memory (when device is not *fpga*) :param width: bits width of the memory (when device is not *fpga*) - :param capture: capture STDOUT and STDERR :returns: STDOUT and STDERR messages :raises FileNotFoundError: when the bitstream is not found :raises ValueError: when devtype, position or width are unsupported @@ -344,9 +292,7 @@ def transfer( self.tool.project, self.tool.get_configs()['tool'], self.outdir ) with self._run_in_dir(): - if capture: - _log.info('the execution messages are being captured') - return self.tool.transfer(devtype, position, part, width, capture) + return self.tool.transfer(devtype, position, part, width, True) def clean(self): """Clean the generated project files.""" From c88eaa6caee14696a61130a983085a58c8bad0fd Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sat, 1 Jun 2024 20:23:04 -0300 Subject: [PATCH 058/254] Renamed and implemented _add_file, implemented add_hook --- pyfpga/project.py | 19 +++++++++---------- tests/test_data.py | 43 ++++++++++++++++++++++++++++++++----------- 2 files changed, 41 insertions(+), 21 deletions(-) diff --git a/pyfpga/project.py b/pyfpga/project.py index 5ac00cd4..bb922c9c 100644 --- a/pyfpga/project.py +++ b/pyfpga/project.py @@ -49,24 +49,25 @@ def set_part(self, name): self.logger.debug('Executing set_part') self.data['part'] = name - def add_file(self, pathname, filetype=None, library=None, options=None): - """Temp placeholder""" - raise NotImplementedError('Method is not implemented yet.') + def _add_file(self, pathname, filetype=None, library=None, options=None): + self.data.setdefault('files', {})[pathname] = { + 'type': filetype, 'options': options, 'library': library + } def add_cons(self, pathname, options=None): """Temp placeholder""" self.logger.debug('Executing add_cons') - self.add_file(pathname, filetype='cons', options=options) + self._add_file(pathname, filetype='cons', options=options) def add_slog(self, pathname, options=None): """Temp placeholder""" self.logger.debug('Executing add_slog') - self.add_file(pathname, filetype='slog', options=options) + self._add_file(pathname, filetype='slog', options=options) def add_vhdl(self, pathname, library=None, options=None): """Temp placeholder""" self.logger.debug('Executing add_vhdl') - self.add_file( + self._add_file( pathname, filetype='vhdl', library=library, options=options ) @@ -74,7 +75,7 @@ def add_vhdl(self, pathname, library=None, options=None): def add_vlog(self, pathname, options=None): """Temp placeholder""" self.logger.debug('Executing add_vlog') - self.add_file(pathname, filetype='vlog', options=options) + self._add_file(pathname, filetype='vlog', options=options) def add_include(self, path): """Temp placeholder""" @@ -109,9 +110,7 @@ def add_hook(self, stage, hook): ] if stage not in stages: raise ValueError('Invalid stage.') - _ = self - _ = hook - raise NotImplementedError('Method is not implemented yet.') + self.data.setdefault('hooks', {}).setdefault(stage, []).append(hook) def make(self, end='bit', start='prj'): """Temp placeholder""" diff --git a/tests/test_data.py b/tests/test_data.py index 66652fd5..b17b2dd4 100644 --- a/tests/test_data.py +++ b/tests/test_data.py @@ -7,9 +7,10 @@ 'part': 'PARTNAME', 'includes': ['INC1', 'INC2', 'INC3'], 'files': { - 'path1': {'type': 'TYPE1', 'options': 'OPT1', 'library': 'LIB1'}, - 'path2': {'type': 'TYPE2', 'options': 'OPT2', 'library': 'LIB2'}, - 'path3': {'type': 'TYPE3', 'options': 'OPT3', 'library': 'LIB3'} + 'path1': {'type': 'vhdl', 'options': 'OPT1', 'library': 'LIB1'}, + 'path2': {'type': 'vlog', 'options': 'OPT2', 'library': None}, + 'path3': {'type': 'slog', 'options': 'OPT3', 'library': None}, + 'path4': {'type': 'cons', 'options': 'OPT4', 'library': None} }, 'top': 'TOPNAME', 'params': { @@ -24,14 +25,14 @@ }, 'arch': 'ARCHNAME', 'hooks': { - 'precfg': ['HOOK1', 'HOOK2', 'HOOKn'], - 'postcfg': ['HOOK1', 'HOOK2', 'HOOKn'], - 'presyn': ['HOOK1', 'HOOK2', 'HOOKn'], - 'postsyn': ['HOOK1', 'HOOK2', 'HOOKn'], - 'prepar': ['HOOK1', 'HOOK2', 'HOOKn'], - 'postpar': ['HOOK1', 'HOOK2', 'HOOKn'], - 'prebit': ['HOOK1', 'HOOK2', 'HOOKn'], - 'postbit': ['HOOK1', 'HOOK2', 'HOOKn'] + 'precfg': ['HOOK1', 'HOOK2'], + 'postcfg': ['HOOK1', 'HOOK2'], + 'presyn': ['HOOK1', 'HOOK2'], + 'postsyn': ['HOOK1', 'HOOK2'], + 'prepar': ['HOOK1', 'HOOK2'], + 'postpar': ['HOOK1', 'HOOK2'], + 'prebit': ['HOOK1', 'HOOK2'], + 'postbit': ['HOOK1', 'HOOK2'] } } @@ -50,4 +51,24 @@ def test_names(): prj.add_define('DEF1', 'VALUE1') prj.add_define('DEF2', 'VALUE2') prj.add_define('DEF3', 'VALUE3') + prj.add_vhdl('path1', 'LIB1', 'OPT1') + prj.add_vlog('path2', 'OPT2') + prj.add_slog('path3', 'OPT3') + prj.add_cons('path4', 'OPT4') + prj.add_hook('precfg', 'HOOK1') + prj.add_hook('precfg', 'HOOK2') + prj.add_hook('postcfg', 'HOOK1') + prj.add_hook('postcfg', 'HOOK2') + prj.add_hook('presyn', 'HOOK1') + prj.add_hook('presyn', 'HOOK2') + prj.add_hook('postsyn', 'HOOK1') + prj.add_hook('postsyn', 'HOOK2') + prj.add_hook('prepar', 'HOOK1') + prj.add_hook('prepar', 'HOOK2') + prj.add_hook('postpar', 'HOOK1') + prj.add_hook('postpar', 'HOOK2') + prj.add_hook('prebit', 'HOOK1') + prj.add_hook('prebit', 'HOOK2') + prj.add_hook('postbit', 'HOOK1') + prj.add_hook('postbit', 'HOOK2') assert prj.data == pattern, 'ERROR: unexpected data' From ecb3aa4c27777859d79902690bdd4b68ae57cc36 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sun, 2 Jun 2024 09:28:00 -0300 Subject: [PATCH 059/254] Added some pending doc-strings --- pyfpga/project.py | 88 +++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 82 insertions(+), 6 deletions(-) diff --git a/pyfpga/project.py b/pyfpga/project.py index bb922c9c..0a5479c0 100644 --- a/pyfpga/project.py +++ b/pyfpga/project.py @@ -8,6 +8,7 @@ Base class that implements agnostic methods to deal with FPGA projects. """ +# import glob import logging import os import subprocess @@ -45,11 +46,52 @@ def __init__(self, name=None, odir='results'): self.logger.addHandler(handler) def set_part(self, name): - """Temp placeholder""" + """Set the FPGA part name. + + :param name: FPGA part name + :type name: str + """ self.logger.debug('Executing set_part') self.data['part'] = name def _add_file(self, pathname, filetype=None, library=None, options=None): + # """Adds files to the project. + # + # :param pathname: a relative path to a file, which can contain + # shell-style wildcards (glob compliant) + # :param filetype: specifies the file type + # :param library: an optional VHDL library name + # :param options: to be provided to the underlying tool + # :raises FileNotFoundError: when a file specified as pathname is not + # found + # :raises ValueError: when *filetype* is unsupported + # + # .. note:: Valid values for *filetype* are ``vhdl``, ``verilog``, + # ``system_verilog``, ``constraint`` (default) and ``block_design`` + # (only **Vivado** is currently supported). If None provided, this + # value is automatically discovered based on the extension ( + # ``.vhd`` or ``.vhdl``, ``.v`` and ``.sv``). + # """ + # pathname = os.path.join(self._absdir, pathname) + # pathname = os.path.normpath(pathname) + # _log.debug('PATHNAME = %s', pathname) + # files = glob.glob(pathname) + # if len(files) == 0: + # raise FileNotFoundError(pathname) + # for file in files: + # if not os.path.exists(file): + # raise FileNotFoundError(file) + # if filetype is None: + # ext = os.path.splitext(file)[1] + # if ext in ['.vhd', '.vhdl']: + # filetype = 'vhdl' + # elif ext in ['.v', '.sv']: + # filetype = 'verilog' + # else: + # filetype = 'constraint' + # _log.debug('add_files: %s filetype detected', filetype) + # file = os.path.relpath(file, self.outdir) + # self.tool.add_file(file, filetype, library, options) self.data.setdefault('files', {})[pathname] = { 'type': filetype, 'options': options, 'library': library } @@ -78,27 +120,61 @@ def add_vlog(self, pathname, options=None): self._add_file(pathname, filetype='vlog', options=options) def add_include(self, path): - """Temp placeholder""" + """Add an Include path. + + Specify where to search for Included Verilog Files, IP repos, etc. + + :param path: path of a directory + :type name: str + :raises NotADirectoryError: if path is not a directory + """ self.logger.debug('Executing add_include') + # path = os.path.join(self._absdir, path) + # path = os.path.normpath(path) + # if os.path.isdir(path): + # path = os.path.relpath(path, self.outdir) + # self.tool.add_vlog_include(path) + # else: + # raise NotADirectoryError(path) self.data.setdefault('includes', []).append(path) def add_param(self, name, value): - """Temp placeholder""" + """Add a Parameter/Generic Value. + + :param name: parameter/generic name + :type name: str + :param value: parameter/generic value + :type name: str + """ self.logger.debug('Executing add_param') self.data.setdefault('params', {})[name] = value def add_define(self, name, value): - """Temp placeholder""" + """Add a Verilog Defile Value. + + :param name: define name + :type name: str + :param value: define value + :type name: str + """ self.logger.debug('Executing add_define') self.data.setdefault('defines', {})[name] = value def set_arch(self, name): - """Temp placeholder""" + """Set the VHDL architecture. + + :param name: architecture name + :type name: str + """ self.logger.debug('Executing set_arch') self.data['arch'] = name def set_top(self, name): - """Temp placeholder""" + """Set the name of the top level component. + + :param name: top-level name + :type name: str + """ self.logger.debug('Executing set_top') self.data['top'] = name From 316dfdcb5693c11368d1b2989bd312d2b014d1f1 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sun, 2 Jun 2024 09:28:52 -0300 Subject: [PATCH 060/254] Removed already processed things --- pyfpga/factory.py | 189 +++------------------------------------------- 1 file changed, 12 insertions(+), 177 deletions(-) diff --git a/pyfpga/factory.py b/pyfpga/factory.py index d05695d1..55b92fb3 100644 --- a/pyfpga/factory.py +++ b/pyfpga/factory.py @@ -8,13 +8,9 @@ A factory class to create FPGA projects. """ -import contextlib -import glob import inspect import logging import os -import re -import time from fpga.tool.ise import Ise from fpga.tool.libero import Libero @@ -78,142 +74,6 @@ def __init__( _log.debug('RELDIR = %s', self._reldir) self._absdir = os.path.join(self._rundir, self._reldir) _log.debug('ABSDIR = %s', self._absdir) - self.set_outdir('build') - - def set_outdir(self, outdir): - """Sets the OUTput DIRectory (where to put the resulting files). - - :param outdir: path to the output directory - """ - self.outdir = os.path.normpath(os.path.join(self._absdir, outdir)) - _log.debug('OUTDIR = %s', self.outdir) - - def get_configs(self): - """Gets the Project Configurations. - - :returns: a dict which includes ``tool`` and ``project`` names, the - ``extension`` of a project file (according to the selected tool) and - the ``part`` to be used - """ - return self.tool.get_configs() - - def set_part(self, part): - """Set the target FPGA part. - - :param part: the FPGA part as specified by the tool - """ - self.tool.set_part(part) - - def add_param(self, name, value): - """Add a Generic/Parameter Value. - - :param name: parameter/generic name - :param value: value to be assigned - """ - self.tool.add_param(name, value) - - def add_files(self, pathname, filetype=None, library=None, options=None): - """Adds files to the project. - - :param pathname: a relative path to a file, which can contain - shell-style wildcards (glob compliant) - :param filetype: specifies the file type - :param library: an optional VHDL library name - :param options: to be provided to the underlying tool - :raises FileNotFoundError: when a file specified as pathname is not - found - :raises ValueError: when *filetype* is unsupported - - .. note:: Valid values for *filetype* are ``vhdl``, ``verilog``, - ``system_verilog``, ``constraint`` (default) and ``block_design`` - (only **Vivado** is currently supported). If None provided, this - value is automatically discovered based on the extension ( - ``.vhd`` or ``.vhdl``, ``.v`` and ``.sv``). - """ - pathname = os.path.join(self._absdir, pathname) - pathname = os.path.normpath(pathname) - _log.debug('PATHNAME = %s', pathname) - files = glob.glob(pathname) - if len(files) == 0: - raise FileNotFoundError(pathname) - for file in files: - if not os.path.exists(file): - raise FileNotFoundError(file) - if filetype is None: - ext = os.path.splitext(file)[1] - if ext in ['.vhd', '.vhdl']: - filetype = 'vhdl' - elif ext in ['.v', '.sv']: - filetype = 'verilog' - else: - filetype = 'constraint' - _log.debug('add_files: %s filetype detected', filetype) - file = os.path.relpath(file, self.outdir) - self.tool.add_file(file, filetype, library, options) - - def get_files(self): - """Get the files of the project. - - :returns: a list with the files of the project - """ - return self.tool.get_files() - - def add_vlog_include(self, path): - """Add a Verilog include path. - - Useful to specify where to search Verilog Included Files or IP - repositories. - - :param path: a relative path to a directory - :raises NotADirectoryError: when path is not a directory - """ - path = os.path.join(self._absdir, path) - path = os.path.normpath(path) - if os.path.isdir(path): - path = os.path.relpath(path, self.outdir) - self.tool.add_vlog_include(path) - else: - raise NotADirectoryError(path) - - def add_vlog_define(self, name, value): - """Add a Verilog define.""" - raise NotImplementedError() - - def set_vhdl_arch(self, name): - """Set the VHDL architecture.""" - raise NotImplementedError() - - def set_top(self, toplevel): - """Set the top level of the project. - - :param toplevel: name or file path of the top level entity/module - :raises FileNotFoundError: when toplevel is a not found file - """ - if os.path.splitext(toplevel)[1]: - toplevel = os.path.join(self._absdir, toplevel) - toplevel = os.path.normpath(toplevel) - if os.path.exists(toplevel): - with open(toplevel, 'r', encoding='utf-8') as file: - hdl = file.read() - # Removing comments, newlines and carriage-returns - hdl = re.sub(r'--.*[$\n]|\/\/.*[$\n]', '', hdl) - hdl = hdl.replace('\n', '').replace('\r', '') - hdl = re.sub(r'\/\*.*\*\/', '', hdl) - # Finding modules/entities - top = re.findall(r'module\s+(\w+)\s*[#(;]', hdl) - top.extend(re.findall(r'entity\s+(\w+)\s+is', hdl)) - if len(top) > 0: - self.tool.set_top(top[-1]) - if len(top) > 1: - _log.warning( - 'set_top: more than one Top found (last selected)' - ) - else: - self.tool.set_top('UNDEFINED') - else: - raise FileNotFoundError(toplevel) - else: - self.tool.set_top(toplevel) def add_hook(self, hook, phase='project'): """Adds a hook in the specified phase. @@ -254,14 +114,13 @@ def generate(self, to_task='bit', from_task='prj', capture=False): ``imp`` (to runs implementation) and ``bit`` (to generates the bitstream) """ - _log.info( - 'generating "%s" project using "%s" tool into "%s" directory', - self.tool.project, self.tool.get_configs()['tool'], self.outdir - ) - with self._run_in_dir(): - if capture: - _log.info('the execution messages are being captured') - return self.tool.generate(to_task, from_task, capture) + # _log.info( + # 'generating "%s" project using "%s" tool into "%s" directory', + # self.tool.project, self.tool.get_configs()['tool'], self.outdir + # ) + if capture: + _log.info('the execution messages are being captured') + return self.tool.generate(to_task, from_task, capture) def set_bitstream(self, path): """Set the bitstream file to transfer. @@ -287,32 +146,8 @@ def transfer( :raises ValueError: when devtype, position or width are unsupported :raises RuntimeError: when the tool to be used is not found """ - _log.info( - 'transfering "%s" project using "%s" tool from "%s" directory', - self.tool.project, self.tool.get_configs()['tool'], self.outdir - ) - with self._run_in_dir(): - return self.tool.transfer(devtype, position, part, width, True) - - def clean(self): - """Clean the generated project files.""" - _log.info( - 'cleaning the generated project files into "%s"', self.outdir - ) - with self._run_in_dir(): - self.tool.clean() - - @contextlib.contextmanager - def _run_in_dir(self): - """Run the tool in another directory.""" - start = time.time() - try: - if not os.path.exists(self.outdir): - _log.debug('the output directory did not exist (created)') - os.makedirs(self.outdir) - os.chdir(self.outdir) - yield - finally: - end = time.time() - os.chdir(self._rundir) - _log.info('executed in %.3f seconds', end-start) + # _log.info( + # 'transfering "%s" project using "%s" tool from "%s" directory', + # self.tool.project, self.tool.get_configs()['tool'], self.outdir + # ) + return self.tool.transfer(devtype, position, part, width, True) From 739b12f3967248ed90f665e73088b2aa87bb93aa Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sun, 2 Jun 2024 09:32:05 -0300 Subject: [PATCH 061/254] Removed test_top.py (top autodiscovery was deprecated) --- Makefile | 2 +- tests/test_top.py | 31 ------------------------------- 2 files changed, 1 insertion(+), 32 deletions(-) delete mode 100644 tests/test_top.py diff --git a/Makefile b/Makefile index 351413fc..6e5d920f 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ lint: git diff --check --cached test: - pytest + pytest tests clean: py3clean . diff --git a/tests/test_top.py b/tests/test_top.py deleted file mode 100644 index fdb894f4..00000000 --- a/tests/test_top.py +++ /dev/null @@ -1,31 +0,0 @@ -import os -import pytest - -from fpga.project import Project - - -def get_pathfile(file): - return os.path.join(os.path.dirname(__file__), file) - - -def test_default(): - prj = Project() - assert prj.tool.top == "UNDEFINED" - - -def test_names(): - prj = Project() - prj.set_top('test1') - assert prj.tool.top == "test1" - prj.set_top('test2') - assert prj.tool.top == "test2" - - -def test_files(): - prj = Project() - prj.set_top(get_pathfile('../hdl/fakes/top.vhdl')) - assert prj.tool.top == "Top1" - prj.set_top(get_pathfile('../hdl/fakes/top.v')) - assert prj.tool.top == "Top1" - prj.set_top(get_pathfile('../README.md')) - assert prj.tool.top == "UNDEFINED" From 62e2fd9dd11846a3c5c237d7b92801ca01fb8896 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sun, 2 Jun 2024 13:34:45 -0300 Subject: [PATCH 062/254] Renamed hooks as internals, added info about the internal data structure --- docs/hooks.rst | 29 ------------------- docs/index.rst | 4 +-- docs/internals.rst | 70 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 72 insertions(+), 31 deletions(-) delete mode 100644 docs/hooks.rst create mode 100644 docs/internals.rst diff --git a/docs/hooks.rst b/docs/hooks.rst deleted file mode 100644 index 498aceed..00000000 --- a/docs/hooks.rst +++ /dev/null @@ -1,29 +0,0 @@ -Hooks -===== - -.. code-block:: - - create project - config project - part - precfg hook - params - defines - includes - files - arch - top - postcfg hook - close project - - open project - presyn hook - synthesis - postsyn hook - prepar hook - place_and_route - postpar hook - prebit hook - bitstream - postbit hook - close project diff --git a/docs/index.rst b/docs/index.rst index 4984a41d..88c75aa4 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -16,6 +16,6 @@ PyFPGA's documentation intro basic advanced - hooks - tools api + tools + internals diff --git a/docs/internals.rst b/docs/internals.rst new file mode 100644 index 00000000..0ec8ded5 --- /dev/null +++ b/docs/internals.rst @@ -0,0 +1,70 @@ +Internals +========= + +Underlying tool steps +--------------------- + +.. code-block:: + + create project + config project + part + precfg hook + params + defines + includes + files + arch + top + postcfg hook + close project + + open project + presyn hook + synthesis + postsyn hook + prepar hook + place_and_route + postpar hook + prebit hook + bitstream + postbit hook + close project + +Internal data structure +----------------------- + +.. code-block:: + + data = { + 'part': 'PARTNAME', + 'includes': ['DIR1', 'DIR2', 'DIR3'], + 'files': { + 'file1': {'type': 'vhdl', 'options': 'OPT1', 'library': 'LIB1'}, + 'file2': {'type': 'vlog', 'options': 'OPT2', 'library': None}, + 'file3': {'type': 'slog', 'options': 'OPT3', 'library': None}, + 'file4': {'type': 'cons', 'options': 'OPT4', 'library': None} + }, + 'top': 'TOPNAME', + 'params': { + 'PAR1': 'VAL1', + 'PAR2': 'VAL2', + 'PAR3': 'VAL3' + }, + 'defines': { + 'DEF1': 'VAL1', + 'DEF2': 'VAL2', + 'DEF3': 'VAL3' + }, + 'arch': 'ARCHNAME', + 'hooks': { + 'precfg': ['CMD1', 'CMD2'], + 'postcfg': ['CMD1', 'CMD2'], + 'presyn': ['CMD1', 'CMD2'], + 'postsyn': ['CMD1', 'CMD2'], + 'prepar': ['CMD1', 'CMD2'], + 'postpar': ['CMD1', 'CMD2'], + 'prebit': ['CMD1', 'CMD2'], + 'postbit': ['CMD1', 'CMD2'] + } + } From ea3c46820a003258656b453918811dd8a263b7a7 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sun, 2 Jun 2024 13:36:59 -0300 Subject: [PATCH 063/254] Added a directory with empty files, to test methods that deal with directories/files --- tests/fakedata/dir1/slog1.sv | 0 tests/fakedata/dir1/slog1.svh | 0 tests/fakedata/dir1/vhdl1.vhd | 0 tests/fakedata/dir1/vhdl1.vhdl | 0 tests/fakedata/dir1/vlog1.v | 0 tests/fakedata/dir1/vlog1.vh | 0 tests/fakedata/dir2/slog2.sv | 0 tests/fakedata/dir2/slog2.svh | 0 tests/fakedata/dir2/vhdl2.vhd | 0 tests/fakedata/dir2/vhdl2.vhdl | 0 tests/fakedata/dir2/vlog2.v | 0 tests/fakedata/dir2/vlog2.vh | 0 tests/fakedata/dir3/slog3.sv | 0 tests/fakedata/dir3/slog3.svh | 0 tests/fakedata/dir3/vhdl3.vhd | 0 tests/fakedata/dir3/vhdl3.vhdl | 0 tests/fakedata/dir3/vlog3.v | 0 tests/fakedata/dir3/vlog3.vh | 0 tests/fakedata/slog0.sv | 0 tests/fakedata/slog0.svh | 0 tests/fakedata/vhdl0.vhd | 0 tests/fakedata/vhdl0.vhdl | 0 tests/fakedata/vlog0.v | 0 tests/fakedata/vlog0.vh | 0 24 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/fakedata/dir1/slog1.sv create mode 100644 tests/fakedata/dir1/slog1.svh create mode 100644 tests/fakedata/dir1/vhdl1.vhd create mode 100644 tests/fakedata/dir1/vhdl1.vhdl create mode 100644 tests/fakedata/dir1/vlog1.v create mode 100644 tests/fakedata/dir1/vlog1.vh create mode 100644 tests/fakedata/dir2/slog2.sv create mode 100644 tests/fakedata/dir2/slog2.svh create mode 100644 tests/fakedata/dir2/vhdl2.vhd create mode 100644 tests/fakedata/dir2/vhdl2.vhdl create mode 100644 tests/fakedata/dir2/vlog2.v create mode 100644 tests/fakedata/dir2/vlog2.vh create mode 100644 tests/fakedata/dir3/slog3.sv create mode 100644 tests/fakedata/dir3/slog3.svh create mode 100644 tests/fakedata/dir3/vhdl3.vhd create mode 100644 tests/fakedata/dir3/vhdl3.vhdl create mode 100644 tests/fakedata/dir3/vlog3.v create mode 100644 tests/fakedata/dir3/vlog3.vh create mode 100644 tests/fakedata/slog0.sv create mode 100644 tests/fakedata/slog0.svh create mode 100644 tests/fakedata/vhdl0.vhd create mode 100644 tests/fakedata/vhdl0.vhdl create mode 100644 tests/fakedata/vlog0.v create mode 100644 tests/fakedata/vlog0.vh diff --git a/tests/fakedata/dir1/slog1.sv b/tests/fakedata/dir1/slog1.sv new file mode 100644 index 00000000..e69de29b diff --git a/tests/fakedata/dir1/slog1.svh b/tests/fakedata/dir1/slog1.svh new file mode 100644 index 00000000..e69de29b diff --git a/tests/fakedata/dir1/vhdl1.vhd b/tests/fakedata/dir1/vhdl1.vhd new file mode 100644 index 00000000..e69de29b diff --git a/tests/fakedata/dir1/vhdl1.vhdl b/tests/fakedata/dir1/vhdl1.vhdl new file mode 100644 index 00000000..e69de29b diff --git a/tests/fakedata/dir1/vlog1.v b/tests/fakedata/dir1/vlog1.v new file mode 100644 index 00000000..e69de29b diff --git a/tests/fakedata/dir1/vlog1.vh b/tests/fakedata/dir1/vlog1.vh new file mode 100644 index 00000000..e69de29b diff --git a/tests/fakedata/dir2/slog2.sv b/tests/fakedata/dir2/slog2.sv new file mode 100644 index 00000000..e69de29b diff --git a/tests/fakedata/dir2/slog2.svh b/tests/fakedata/dir2/slog2.svh new file mode 100644 index 00000000..e69de29b diff --git a/tests/fakedata/dir2/vhdl2.vhd b/tests/fakedata/dir2/vhdl2.vhd new file mode 100644 index 00000000..e69de29b diff --git a/tests/fakedata/dir2/vhdl2.vhdl b/tests/fakedata/dir2/vhdl2.vhdl new file mode 100644 index 00000000..e69de29b diff --git a/tests/fakedata/dir2/vlog2.v b/tests/fakedata/dir2/vlog2.v new file mode 100644 index 00000000..e69de29b diff --git a/tests/fakedata/dir2/vlog2.vh b/tests/fakedata/dir2/vlog2.vh new file mode 100644 index 00000000..e69de29b diff --git a/tests/fakedata/dir3/slog3.sv b/tests/fakedata/dir3/slog3.sv new file mode 100644 index 00000000..e69de29b diff --git a/tests/fakedata/dir3/slog3.svh b/tests/fakedata/dir3/slog3.svh new file mode 100644 index 00000000..e69de29b diff --git a/tests/fakedata/dir3/vhdl3.vhd b/tests/fakedata/dir3/vhdl3.vhd new file mode 100644 index 00000000..e69de29b diff --git a/tests/fakedata/dir3/vhdl3.vhdl b/tests/fakedata/dir3/vhdl3.vhdl new file mode 100644 index 00000000..e69de29b diff --git a/tests/fakedata/dir3/vlog3.v b/tests/fakedata/dir3/vlog3.v new file mode 100644 index 00000000..e69de29b diff --git a/tests/fakedata/dir3/vlog3.vh b/tests/fakedata/dir3/vlog3.vh new file mode 100644 index 00000000..e69de29b diff --git a/tests/fakedata/slog0.sv b/tests/fakedata/slog0.sv new file mode 100644 index 00000000..e69de29b diff --git a/tests/fakedata/slog0.svh b/tests/fakedata/slog0.svh new file mode 100644 index 00000000..e69de29b diff --git a/tests/fakedata/vhdl0.vhd b/tests/fakedata/vhdl0.vhd new file mode 100644 index 00000000..e69de29b diff --git a/tests/fakedata/vhdl0.vhdl b/tests/fakedata/vhdl0.vhdl new file mode 100644 index 00000000..e69de29b diff --git a/tests/fakedata/vlog0.v b/tests/fakedata/vlog0.v new file mode 100644 index 00000000..e69de29b diff --git a/tests/fakedata/vlog0.vh b/tests/fakedata/vlog0.vh new file mode 100644 index 00000000..e69de29b From 1455a0993e9f517127f7623574ca19a4875d4549 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sun, 2 Jun 2024 16:55:51 -0300 Subject: [PATCH 064/254] Removed unused things --- pyfpga/factory.py | 137 +++++----------------------------------------- 1 file changed, 14 insertions(+), 123 deletions(-) diff --git a/pyfpga/factory.py b/pyfpga/factory.py index 55b92fb3..d7fc3502 100644 --- a/pyfpga/factory.py +++ b/pyfpga/factory.py @@ -8,146 +8,37 @@ A factory class to create FPGA projects. """ -import inspect -import logging -import os - from fpga.tool.ise import Ise from fpga.tool.libero import Libero from fpga.tool.openflow import Openflow from fpga.tool.quartus import Quartus from fpga.tool.vivado import Vivado +# pylint: disable=return-in-init +# pylint: disable=no-else-return +# pylint: disable=too-many-return-statements +# pylint: disable=too-few-public-methods -TOOLS = [ - 'ghdl', 'ise', 'libero', 'openflow', 'quartus', 'vivado', 'yosys', - 'yosys-ise', 'yosys-vivado' -] - - -_log = logging.getLogger(__name__) -_log.level = logging.INFO -_log.addHandler(logging.NullHandler()) - - -class Project: - """Class to manage an FPGA project. - :param tool: FPGA tool to be used - :param project: project name (the tool name is used if none specified) - :param meta: a dict with metadata about the project - :param relative_to_script: specifies if the files/directories are relative - to the script or the execution directory - :raises NotImplementedError: when tool is unsupported - - .. note:: Valid values for **tool** are ``ghdl``, ``ise``, ``libero``, - ``openflow``, ``quartus``, ``vivado``, ``yosys``, ``yosys-ise`` and - ``yosys-vivado`` - """ +class Factory: + """A factory class to create FPGA projects.""" def __init__( - self, tool='vivado', project=None, - relative_to_script=True): + self, tool='vivado', project=None): """Class constructor.""" if tool == 'ghdl': - self.tool = Openflow(project, frontend='ghdl', backend='vhdl') + return Openflow(project, frontend='ghdl', backend='vhdl') elif tool in ['ise', 'yosys-ise']: - self.tool = Ise(project, 'yosys' if 'yosys' in tool else '') + return Ise(project, 'yosys' if 'yosys' in tool else '') elif tool == 'libero': - self.tool = Libero(project) + return Libero(project) elif tool == 'openflow': - self.tool = Openflow(project) + return Openflow(project) elif tool == 'quartus': - self.tool = Quartus(project) + return Quartus(project) elif tool in ['vivado', 'yosys-vivado']: - self.tool = Vivado(project, 'yosys' if 'yosys' in tool else '') + return Vivado(project, 'yosys' if 'yosys' in tool else '') elif tool == 'yosys': - self.tool = Openflow(project, frontend='yosys', backend='verilog') + return Openflow(project, frontend='yosys', backend='verilog') else: raise NotImplementedError(tool) - self._rundir = os.getcwd() - _log.debug('RUNDIR = %s', self._rundir) - if relative_to_script: - self._reldir = os.path.dirname(inspect.stack()[-1].filename) - else: - self._reldir = '' - _log.debug('RELDIR = %s', self._reldir) - self._absdir = os.path.join(self._rundir, self._reldir) - _log.debug('ABSDIR = %s', self._absdir) - - def add_hook(self, hook, phase='project'): - """Adds a hook in the specified phase. - - A hook is a place that allows you to insert customized programming. - - :param hook: is a string representing a tool specific command - :param phase: the phase where to insert a hook - :raises ValueError: when phase is unsupported - - .. note:: Valid values for *phase* are - ``prefile`` (to add options needed to find files), - ``project`` (to add project related options), - ``preflow`` (to change options previous to run the flow), - ``postsyn`` (to perform an action between *syn* and *par*), - ``postpar`` (to perform an action between *par* and *bit*) and - ``postbit`` (to perform an action after *bit*) - - .. warning:: Using a hook, you will be probably broken the vendor - independence - """ - self.tool.add_hook(hook, phase) - - def generate(self, to_task='bit', from_task='prj', capture=False): - """Run the FPGA tool. - - :param to_task: last task - :param from_task: first task - :param capture: capture STDOUT and STDERR - :returns: STDOUT and STDERR messages - :raises ValueError: when from_task is later than to_task - :raises ValueError: when to_task or from_task are unsupported - :raises RuntimeError: when the tool to be used is not found - - .. note:: Valid values for **tasks** are - ``prj`` (to creates the project file), - ``syn`` (to performs the synthesis), - ``imp`` (to runs implementation) and - ``bit`` (to generates the bitstream) - """ - # _log.info( - # 'generating "%s" project using "%s" tool into "%s" directory', - # self.tool.project, self.tool.get_configs()['tool'], self.outdir - # ) - if capture: - _log.info('the execution messages are being captured') - return self.tool.generate(to_task, from_task, capture) - - def set_bitstream(self, path): - """Set the bitstream file to transfer. - - :param path: path to the bitstream file - """ - path = os.path.join(self._absdir, path) - if not os.path.exists(path): - raise FileNotFoundError(path) - self.tool.set_bitstream(path) - - def transfer( - self, devtype='fpga', position=1, part='', width=1): - """Transfers the generated bitstream to a device. - - :param devtype: *fpga* or other valid option - (depending on the used tool, it could be *spi*, *bpi*, etc) - :param position: position of the device in the JTAG chain - :param part: name of the memory (when device is not *fpga*) - :param width: bits width of the memory (when device is not *fpga*) - :returns: STDOUT and STDERR messages - :raises FileNotFoundError: when the bitstream is not found - :raises ValueError: when devtype, position or width are unsupported - :raises RuntimeError: when the tool to be used is not found - """ - # _log.info( - # 'transfering "%s" project using "%s" tool from "%s" directory', - # self.tool.project, self.tool.get_configs()['tool'], self.outdir - # ) - return self.tool.transfer(devtype, position, part, width, True) From df2a2a2d94d9802bc0260e0491aba1ada57b70ca Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sun, 2 Jun 2024 16:56:45 -0300 Subject: [PATCH 065/254] Removed test_files.py --- Makefile | 2 +- tests/test_files.py | 18 ------------------ 2 files changed, 1 insertion(+), 19 deletions(-) delete mode 100644 tests/test_files.py diff --git a/Makefile b/Makefile index 6e5d920f..9c8032b2 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ lint: git diff --check --cached test: - pytest tests + cd tests; pytest clean: py3clean . diff --git a/tests/test_files.py b/tests/test_files.py deleted file mode 100644 index ffe55f81..00000000 --- a/tests/test_files.py +++ /dev/null @@ -1,18 +0,0 @@ -import os -import pytest - -from fpga.project import Project, TOOLS - - -def get_path(file): - return os.path.join(os.path.dirname(__file__), file) - - -@pytest.mark.parametrize('tool', TOOLS) -def test_files(tool): - prj = Project(tool) - prj.add_files(get_path('../hdl/*.vhdl')) - prj.add_files(get_path('../hdl/*.v')) - assert len(prj.get_files()['verilog']) == 3 - assert len(prj.get_files()['vhdl']) == 4 - assert len(prj.get_files()['constraint']) == 0 From 79175ed68107321a9e2a45101b0aedebeb011c87 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sun, 2 Jun 2024 16:57:03 -0300 Subject: [PATCH 066/254] Implemented directory/file exists check --- pyfpga/project.py | 131 +++++++++++++++++++++++++-------------------- tests/test_data.py | 115 ++++++++++++++++++++++----------------- 2 files changed, 140 insertions(+), 106 deletions(-) diff --git a/pyfpga/project.py b/pyfpga/project.py index 0a5479c0..f7d49537 100644 --- a/pyfpga/project.py +++ b/pyfpga/project.py @@ -8,7 +8,7 @@ Base class that implements agnostic methods to deal with FPGA projects. """ -# import glob +import glob import logging import os import subprocess @@ -55,59 +55,48 @@ def set_part(self, name): self.data['part'] = name def _add_file(self, pathname, filetype=None, library=None, options=None): - # """Adds files to the project. - # - # :param pathname: a relative path to a file, which can contain - # shell-style wildcards (glob compliant) - # :param filetype: specifies the file type - # :param library: an optional VHDL library name - # :param options: to be provided to the underlying tool - # :raises FileNotFoundError: when a file specified as pathname is not - # found - # :raises ValueError: when *filetype* is unsupported - # - # .. note:: Valid values for *filetype* are ``vhdl``, ``verilog``, - # ``system_verilog``, ``constraint`` (default) and ``block_design`` - # (only **Vivado** is currently supported). If None provided, this - # value is automatically discovered based on the extension ( - # ``.vhd`` or ``.vhdl``, ``.v`` and ``.sv``). - # """ - # pathname = os.path.join(self._absdir, pathname) - # pathname = os.path.normpath(pathname) - # _log.debug('PATHNAME = %s', pathname) - # files = glob.glob(pathname) - # if len(files) == 0: - # raise FileNotFoundError(pathname) - # for file in files: - # if not os.path.exists(file): - # raise FileNotFoundError(file) - # if filetype is None: - # ext = os.path.splitext(file)[1] - # if ext in ['.vhd', '.vhdl']: - # filetype = 'vhdl' - # elif ext in ['.v', '.sv']: - # filetype = 'verilog' - # else: - # filetype = 'constraint' - # _log.debug('add_files: %s filetype detected', filetype) - # file = os.path.relpath(file, self.outdir) - # self.tool.add_file(file, filetype, library, options) - self.data.setdefault('files', {})[pathname] = { - 'type': filetype, 'options': options, 'library': library - } - - def add_cons(self, pathname, options=None): - """Temp placeholder""" + files = glob.glob(pathname) + if len(files) == 0: + raise FileNotFoundError(pathname) + for file in files: + path = Path(file).resolve() + self.data.setdefault('files', {})[path] = { + 'type': filetype, 'options': options, 'library': library + } + + def add_cons(self, pathname): + """Add constraint file/s. + + :param pathname: path to a constraint file (glob compliant) + :type pathname: str + :raises FileNotFoundError: when pathname is not found + """ self.logger.debug('Executing add_cons') - self._add_file(pathname, filetype='cons', options=options) + self._add_file(pathname, filetype='cons', options=None) def add_slog(self, pathname, options=None): - """Temp placeholder""" + """Add System Verilog file/s. + + :param pathname: path to a SV file (glob compliant) + :type pathname: str + :param options: for the underlying tool + :type options: str, optional + :raises FileNotFoundError: when pathname is not found + """ self.logger.debug('Executing add_slog') self._add_file(pathname, filetype='slog', options=options) def add_vhdl(self, pathname, library=None, options=None): - """Temp placeholder""" + """Add VHDL file/s. + + :param pathname: path to a SV file (glob compliant) + :type pathname: str + :param library: VHDL library name + :type library: str, optional + :param options: for the underlying tool + :type options: str, optional + :raises FileNotFoundError: when pathname is not found + """ self.logger.debug('Executing add_vhdl') self._add_file( pathname, filetype='vhdl', @@ -115,7 +104,14 @@ def add_vhdl(self, pathname, library=None, options=None): ) def add_vlog(self, pathname, options=None): - """Temp placeholder""" + """Add Verilog file/s. + + :param pathname: path to a SV file (glob compliant) + :type pathname: str + :param options: for the underlying tool + :type options: str, optional + :raises FileNotFoundError: when pathname is not found + """ self.logger.debug('Executing add_vlog') self._add_file(pathname, filetype='vlog', options=options) @@ -129,13 +125,9 @@ def add_include(self, path): :raises NotADirectoryError: if path is not a directory """ self.logger.debug('Executing add_include') - # path = os.path.join(self._absdir, path) - # path = os.path.normpath(path) - # if os.path.isdir(path): - # path = os.path.relpath(path, self.outdir) - # self.tool.add_vlog_include(path) - # else: - # raise NotADirectoryError(path) + path = Path(path).resolve() + if not path.is_dir(): + raise NotADirectoryError(path) self.data.setdefault('includes', []).append(path) def add_param(self, name, value): @@ -179,7 +171,16 @@ def set_top(self, name): self.data['top'] = name def add_hook(self, stage, hook): - """Add hook for a specific stage.""" + """Add a hook in the specific stage. + + A hook is a place that allows you to insert customized code. + + :param stage: where to insert the hook + :type stage: str + :param hook: a tool-specific command + :type hook: str + :raises ValueError: when stage is invalid + """ stages = [ 'precfg', 'postcfg', 'presyn', 'postsyn', 'prepar', 'postpar', 'prebit', 'postbit' @@ -189,7 +190,15 @@ def add_hook(self, stage, hook): self.data.setdefault('hooks', {}).setdefault(stage, []).append(hook) def make(self, end='bit', start='prj'): - """Temp placeholder""" + """Run the underlying tool. + + :param end: last task + :type end: str, optional + :param start: first task + :type start: str, optional + + .. note:: Valid values are ``cfg``, ``syn``, ``imp`` and ``bit``. + """ steps = ['cfg', 'syn', 'par', 'bit'] if end not in steps or start not in steps: raise ValueError('Invalid steps.') @@ -197,7 +206,13 @@ def make(self, end='bit', start='prj'): raise NotImplementedError('Method is not implemented yet.') def prog(self, position=1, bitstream=None): - """Temp placeholder""" + """Program the FPGA + + :param position: position of the device in the JTAG chain + :type position: str, optional + :param bitstream: bitstream to be programmed + :type bitstream: str, optional + """ raise NotImplementedError('Method is not implemented yet.') def _run(self, command): diff --git a/tests/test_data.py b/tests/test_data.py index b17b2dd4..4c363de5 100644 --- a/tests/test_data.py +++ b/tests/test_data.py @@ -1,38 +1,58 @@ import os import pytest +from pathlib import Path + from pyfpga.project import Project pattern = { 'part': 'PARTNAME', - 'includes': ['INC1', 'INC2', 'INC3'], + 'includes': [ + Path('fakedata/dir1').resolve(), + Path('fakedata/dir2').resolve(), + Path('fakedata/dir3').resolve() + ], 'files': { - 'path1': {'type': 'vhdl', 'options': 'OPT1', 'library': 'LIB1'}, - 'path2': {'type': 'vlog', 'options': 'OPT2', 'library': None}, - 'path3': {'type': 'slog', 'options': 'OPT3', 'library': None}, - 'path4': {'type': 'cons', 'options': 'OPT4', 'library': None} + Path('fakedata/dir1/slog1.sv').resolve(): + {'type': 'slog', 'options': None, 'library': None}, + Path('fakedata/dir2/slog2.sv').resolve(): + {'type': 'slog', 'options': None, 'library': None}, + Path('fakedata/dir3/slog3.sv').resolve(): + {'type': 'slog', 'options': None, 'library': None}, + Path('fakedata/dir1/vhdl1.vhdl').resolve(): + {'type': 'vhdl', 'options': None, 'library': None}, + Path('fakedata/dir2/vhdl2.vhdl').resolve(): + {'type': 'vhdl', 'options': None, 'library': None}, + Path('fakedata/dir3/vhdl3.vhdl').resolve(): + {'type': 'vhdl', 'options': None, 'library': None}, + Path('fakedata/dir1/vlog1.v').resolve(): + {'type': 'vlog', 'options': None, 'library': None}, + Path('fakedata/dir2/vlog2.v').resolve(): + {'type': 'vlog', 'options': None, 'library': None}, + Path('fakedata/dir3/vlog3.v').resolve(): + {'type': 'vlog', 'options': None, 'library': None} }, 'top': 'TOPNAME', 'params': { - 'PARAM1': 'VALUE1', - 'PARAM2': 'VALUE2', - 'PARAM3': 'VALUE3' + 'PAR1': 'VAL1', + 'PAR2': 'VAL2', + 'PAR3': 'VAL3' }, 'defines': { - 'DEF1': 'VALUE1', - 'DEF2': 'VALUE2', - 'DEF3': 'VALUE3' + 'DEF1': 'VAL1', + 'DEF2': 'VAL2', + 'DEF3': 'VAL3' }, 'arch': 'ARCHNAME', 'hooks': { - 'precfg': ['HOOK1', 'HOOK2'], - 'postcfg': ['HOOK1', 'HOOK2'], - 'presyn': ['HOOK1', 'HOOK2'], - 'postsyn': ['HOOK1', 'HOOK2'], - 'prepar': ['HOOK1', 'HOOK2'], - 'postpar': ['HOOK1', 'HOOK2'], - 'prebit': ['HOOK1', 'HOOK2'], - 'postbit': ['HOOK1', 'HOOK2'] + 'precfg': ['CMD1', 'CMD2'], + 'postcfg': ['CMD1', 'CMD2'], + 'presyn': ['CMD1', 'CMD2'], + 'postsyn': ['CMD1', 'CMD2'], + 'prepar': ['CMD1', 'CMD2'], + 'postpar': ['CMD1', 'CMD2'], + 'prebit': ['CMD1', 'CMD2'], + 'postbit': ['CMD1', 'CMD2'] } } @@ -42,33 +62,32 @@ def test_names(): prj.set_part('PARTNAME') prj.set_top('TOPNAME') prj.set_arch('ARCHNAME') - prj.add_include('INC1') - prj.add_include('INC2') - prj.add_include('INC3') - prj.add_param('PARAM1', 'VALUE1') - prj.add_param('PARAM2', 'VALUE2') - prj.add_param('PARAM3', 'VALUE3') - prj.add_define('DEF1', 'VALUE1') - prj.add_define('DEF2', 'VALUE2') - prj.add_define('DEF3', 'VALUE3') - prj.add_vhdl('path1', 'LIB1', 'OPT1') - prj.add_vlog('path2', 'OPT2') - prj.add_slog('path3', 'OPT3') - prj.add_cons('path4', 'OPT4') - prj.add_hook('precfg', 'HOOK1') - prj.add_hook('precfg', 'HOOK2') - prj.add_hook('postcfg', 'HOOK1') - prj.add_hook('postcfg', 'HOOK2') - prj.add_hook('presyn', 'HOOK1') - prj.add_hook('presyn', 'HOOK2') - prj.add_hook('postsyn', 'HOOK1') - prj.add_hook('postsyn', 'HOOK2') - prj.add_hook('prepar', 'HOOK1') - prj.add_hook('prepar', 'HOOK2') - prj.add_hook('postpar', 'HOOK1') - prj.add_hook('postpar', 'HOOK2') - prj.add_hook('prebit', 'HOOK1') - prj.add_hook('prebit', 'HOOK2') - prj.add_hook('postbit', 'HOOK1') - prj.add_hook('postbit', 'HOOK2') + prj.add_include('fakedata/dir1') + prj.add_include('fakedata/dir2') + prj.add_include('fakedata/dir3') + prj.add_slog('fakedata/**/*.sv') + prj.add_vhdl('fakedata/**/*.vhdl') + prj.add_vlog('fakedata/**/*.v') + prj.add_param('PAR1', 'VAL1') + prj.add_param('PAR2', 'VAL2') + prj.add_param('PAR3', 'VAL3') + prj.add_define('DEF1', 'VAL1') + prj.add_define('DEF2', 'VAL2') + prj.add_define('DEF3', 'VAL3') + prj.add_hook('precfg', 'CMD1') + prj.add_hook('precfg', 'CMD2') + prj.add_hook('postcfg', 'CMD1') + prj.add_hook('postcfg', 'CMD2') + prj.add_hook('presyn', 'CMD1') + prj.add_hook('presyn', 'CMD2') + prj.add_hook('postsyn', 'CMD1') + prj.add_hook('postsyn', 'CMD2') + prj.add_hook('prepar', 'CMD1') + prj.add_hook('prepar', 'CMD2') + prj.add_hook('postpar', 'CMD1') + prj.add_hook('postpar', 'CMD2') + prj.add_hook('prebit', 'CMD1') + prj.add_hook('prebit', 'CMD2') + prj.add_hook('postbit', 'CMD1') + prj.add_hook('postbit', 'CMD2') assert prj.data == pattern, 'ERROR: unexpected data' From 53bb363aa5c5cc2cf5ad5af846b019e2fd0bf0be Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Tue, 4 Jun 2024 21:25:39 -0300 Subject: [PATCH 067/254] Moved things from fpga to pyfpga --- pyfpga/factory.py | 2 +- {fpga/tool => pyfpga}/ise.py | 16 ++-------------- {fpga/tool => pyfpga}/libero.py | 16 ++-------------- {fpga/tool => pyfpga}/openflow.py | 16 ++-------------- {fpga/tool => pyfpga}/quartus.py | 16 ++-------------- .../templates/openflow-prog.jinja | 17 +++-------------- pyfpga/templates/vivado.jinja | 1 - fpga/tool/__init__.py => pyfpga/tool.py | 16 ++-------------- {fpga/tool => pyfpga}/vivado.py | 16 ++-------------- 9 files changed, 16 insertions(+), 100 deletions(-) rename {fpga/tool => pyfpga}/ise.py (88%) rename {fpga/tool => pyfpga}/libero.py (76%) rename {fpga/tool => pyfpga}/openflow.py (91%) rename {fpga/tool => pyfpga}/quartus.py (66%) rename fpga/tool/openprog.sh => pyfpga/templates/openflow-prog.jinja (57%) rename fpga/tool/__init__.py => pyfpga/tool.py (92%) rename {fpga/tool => pyfpga}/vivado.py (78%) diff --git a/pyfpga/factory.py b/pyfpga/factory.py index d7fc3502..d48547f6 100644 --- a/pyfpga/factory.py +++ b/pyfpga/factory.py @@ -1,5 +1,5 @@ # -# Copyright (C) 2019-2024 Rodrigo A. Melo +# Copyright (C) 2024 Rodrigo A. Melo # # SPDX-License-Identifier: GPL-3.0-or-later # diff --git a/fpga/tool/ise.py b/pyfpga/ise.py similarity index 88% rename from fpga/tool/ise.py rename to pyfpga/ise.py index c6f5e83d..817ca433 100644 --- a/fpga/tool/ise.py +++ b/pyfpga/ise.py @@ -1,19 +1,7 @@ # -# Copyright (C) 2019-2020 INTI -# Copyright (C) 2019-2021 Rodrigo A. Melo +# Copyright (C) 2019-2024 Rodrigo A. Melo # -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . +# SPDX-License-Identifier: GPL-3.0-or-later # """fpga.tool.ise diff --git a/fpga/tool/libero.py b/pyfpga/libero.py similarity index 76% rename from fpga/tool/libero.py rename to pyfpga/libero.py index ee463993..c037284c 100644 --- a/fpga/tool/libero.py +++ b/pyfpga/libero.py @@ -1,19 +1,7 @@ # -# Copyright (C) 2019-2020 INTI -# Copyright (C) 2019-2021 Rodrigo A. Melo +# Copyright (C) 2019-2024 Rodrigo A. Melo # -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . +# SPDX-License-Identifier: GPL-3.0-or-later # """fpga.tool.libero diff --git a/fpga/tool/openflow.py b/pyfpga/openflow.py similarity index 91% rename from fpga/tool/openflow.py rename to pyfpga/openflow.py index 0eb6b905..b8207d58 100644 --- a/fpga/tool/openflow.py +++ b/pyfpga/openflow.py @@ -1,19 +1,7 @@ # -# Copyright (C) 2020 INTI -# Copyright (C) 2020-2021 Rodrigo A. Melo +# Copyright (C) 2020-2024 Rodrigo A. Melo # -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . +# SPDX-License-Identifier: GPL-3.0-or-later # """fpga.tool.openflow diff --git a/fpga/tool/quartus.py b/pyfpga/quartus.py similarity index 66% rename from fpga/tool/quartus.py rename to pyfpga/quartus.py index f7644618..94db6ad6 100644 --- a/fpga/tool/quartus.py +++ b/pyfpga/quartus.py @@ -1,19 +1,7 @@ # -# Copyright (C) 2019-2020 INTI -# Copyright (C) 2019-2020 Rodrigo A. Melo +# Copyright (C) 2019-2024 Rodrigo A. Melo # -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . +# SPDX-License-Identifier: GPL-3.0-or-later # """fpga.tool.quartus diff --git a/fpga/tool/openprog.sh b/pyfpga/templates/openflow-prog.jinja similarity index 57% rename from fpga/tool/openprog.sh rename to pyfpga/templates/openflow-prog.jinja index 2746d050..3be40f53 100644 --- a/fpga/tool/openprog.sh +++ b/pyfpga/templates/openflow-prog.jinja @@ -1,19 +1,8 @@ -#!/bin/bash # -# Copyright (C) 2020 Rodrigo A. Melo +# PyFPGA +# Copyright (C) 2020-2024 Rodrigo A. Melo # -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . +# SPDX-License-Identifier: GPL-3.0-or-later # set -e diff --git a/pyfpga/templates/vivado.jinja b/pyfpga/templates/vivado.jinja index c1805387..16bed243 100644 --- a/pyfpga/templates/vivado.jinja +++ b/pyfpga/templates/vivado.jinja @@ -85,7 +85,6 @@ close_project {% if SYN or PAR or BIT %} open_project $PROJECT -set_param general.maxThreads [expr {[exec nproc] < 32 ? [exec nproc] : 32}] {% if SYN %} {{ PRESYN }} diff --git a/fpga/tool/__init__.py b/pyfpga/tool.py similarity index 92% rename from fpga/tool/__init__.py rename to pyfpga/tool.py index 9fc09685..f02931a3 100644 --- a/fpga/tool/__init__.py +++ b/pyfpga/tool.py @@ -1,19 +1,7 @@ # -# Copyright (C) 2019-2020 INTI -# Copyright (C) 2019-2021 Rodrigo A. Melo +# Copyright (C) 2019-2024 Rodrigo A. Melo # -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . +# SPDX-License-Identifier: GPL-3.0-or-later # """fpga.tool diff --git a/fpga/tool/vivado.py b/pyfpga/vivado.py similarity index 78% rename from fpga/tool/vivado.py rename to pyfpga/vivado.py index 067644ed..24cd2ae9 100644 --- a/fpga/tool/vivado.py +++ b/pyfpga/vivado.py @@ -1,19 +1,7 @@ # -# Copyright (C) 2019-2020 INTI -# Copyright (C) 2019-2020 Rodrigo A. Melo +# Copyright (C) 2019-2024 Rodrigo A. Melo # -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . +# SPDX-License-Identifier: GPL-3.0-or-later # """fpga.tool.vivado From 29aae524114ed2b7f835cbf74220fda888b534de Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Tue, 4 Jun 2024 22:09:40 -0300 Subject: [PATCH 068/254] Removed unused things, commented others --- pyfpga/factory.py | 22 +-- pyfpga/ise.py | 243 +++++++++++++++-------------- pyfpga/libero.py | 127 ++++++++------- pyfpga/openflow.py | 381 +++++++++++++++++++++++---------------------- pyfpga/quartus.py | 94 +++++------ pyfpga/tool.py | 372 ++++++++++++++++++------------------------- pyfpga/vivado.py | 115 +++++++------- 7 files changed, 660 insertions(+), 694 deletions(-) diff --git a/pyfpga/factory.py b/pyfpga/factory.py index d48547f6..6236730e 100644 --- a/pyfpga/factory.py +++ b/pyfpga/factory.py @@ -8,11 +8,11 @@ A factory class to create FPGA projects. """ -from fpga.tool.ise import Ise -from fpga.tool.libero import Libero -from fpga.tool.openflow import Openflow -from fpga.tool.quartus import Quartus -from fpga.tool.vivado import Vivado +from pyfpga.ise import Ise +from pyfpga.libero import Libero +from pyfpga.openflow import Openflow +from pyfpga.quartus import Quartus +from pyfpga.vivado import Vivado # pylint: disable=return-in-init # pylint: disable=no-else-return @@ -27,18 +27,18 @@ def __init__( self, tool='vivado', project=None): """Class constructor.""" if tool == 'ghdl': - return Openflow(project, frontend='ghdl', backend='vhdl') - elif tool in ['ise', 'yosys-ise']: - return Ise(project, 'yosys' if 'yosys' in tool else '') + return Openflow(project) # , frontend='ghdl', backend='vhdl') + elif tool == 'ise': + return Ise(project) elif tool == 'libero': return Libero(project) elif tool == 'openflow': return Openflow(project) elif tool == 'quartus': return Quartus(project) - elif tool in ['vivado', 'yosys-vivado']: - return Vivado(project, 'yosys' if 'yosys' in tool else '') + elif tool == 'vivado': + return Vivado(project) elif tool == 'yosys': - return Openflow(project, frontend='yosys', backend='verilog') + return Openflow(project) # , frontend='yosys', backend='verilog') else: raise NotImplementedError(tool) diff --git a/pyfpga/ise.py b/pyfpga/ise.py index 817ca433..329e6e48 100644 --- a/pyfpga/ise.py +++ b/pyfpga/ise.py @@ -4,14 +4,13 @@ # SPDX-License-Identifier: GPL-3.0-or-later # -"""fpga.tool.ise - -Implements the support of ISE (Xilinx). +""" +Implements support for ISE. """ -import re +# import re -from fpga.tool import Tool, run +from pyfpga.project import Project _TEMPLATES = { 'fpga': """setMode -bs @@ -68,118 +67,126 @@ """ } +# pylint: disable=too-few-public-methods + -class Ise(Tool): - """Implementation of the class to support ISE.""" - - _TOOL = 'ise' - _EXTENSION = 'xise' - _PART = 'xc7k160t-3-fbg484' - _GEN_PROGRAM = 'xtclsh' - _GEN_COMMAND = 'xtclsh ise.tcl' - _TRF_PROGRAM = 'impact' - _TRF_COMMAND = 'impact -batch ise-prog.impact' - _BIT_EXT = ['bit'] - _DEVTYPES = ['fpga', 'spi', 'bpi', 'detect', 'unlock'] - _CLEAN = [ - # directories - 'iseconfig', '_ngo', 'xlnx_auto_0_xdb', '_xmsgs', 'xst', - # files - '*.bgn', '*.bld', '*.bit', - '*.cmd_log', '*.csv', - '*.drc', - '*.gise', - '*.html', - '*.log', '*.lso', - '*.map', '*.mrp', - '*.ncd', '*.ngc', '*.ngd', '*.ngm', '*.ngr', - '*.pad', '*.par', '*.pcf', '*.prj', '*.ptwx', - '*.stx', '*.syr', - '*.twr', '*.twx', - '*.unroutes', '*.ut', - '*.txt', - '*.xml', '*.xpi', '*.xrpt', '*.xst', '*.xwbt', - '_impact*', - # pyfpga - '*.impact', 'ise.tcl' - ] - - def __init__(self, project, frontend=None): - super().__init__(project) - if frontend == 'yosys': - from fpga.tool.openflow import Openflow - self.tool = Openflow( - self.project, - frontend='yosys', - backend='ise' - ) - self.presynth = True - - def set_part(self, part): - try: - device, speed, package = re.findall(r'(\w+)-(\w+)-(\w+)', part)[0] - if len(speed) > len(package): - speed, package = package, speed - part = f'{device}-{speed}-{package}' - except IndexError: - raise ValueError( - 'Part must be DEVICE-SPEED-PACKAGE or DEVICE-PACKAGE-SPEED' - ) - self.part['name'] = part - self.part['family'] = get_family(part) - self.part['device'] = device - self.part['package'] = package - self.part['speed'] = '-' + speed - - def generate(self, to_task, from_task, capture): - if self.presynth and from_task in ['prj', 'syn']: - self.tool.set_part(self.part['name']) - self.tool.set_top(self.top) - self.tool.paths = self.paths - self.tool.files['vhdl'] = self.files['vhdl'] - self.tool.files['verilog'] = self.files['verilog'] - self.tool.params = self.params - output1 = self.tool.generate('syn', 'prj', capture) - output2 = super().generate(to_task, from_task, capture) - return str(output1) + str(output2) - return super().generate(to_task, from_task, capture) - - def transfer(self, devtype, position, part, width, capture): - super().transfer(devtype, position, part, width, capture) - temp = _TEMPLATES[devtype] - if devtype not in ['detect', 'unlock']: - temp = temp.replace('#BITSTREAM#', self.bitstream) - temp = temp.replace('#POSITION#', str(position)) - temp = temp.replace('#NAME#', part) - temp = temp.replace('#WIDTH#', str(width)) - with open('ise-prog.impact', 'w', encoding='utf-8') as file: - file.write(temp) - return run(self._TRF_COMMAND, capture) - - -def get_family(part): - """Get the Family name from the specified part name.""" - part = part.lower() - families = { - r'xc7a\d+l': 'artix7l', - r'xc7a': 'artix7', - r'xc7k\d+l': 'kintex7l', - r'xc7k': 'kintex7', - r'xc3sd\d+a': 'spartan3adsp', - r'xc3s\d+a': 'spartan3a', - r'xc3s\d+e': 'spartan3e', - r'xc3s': 'spartan3', - r'xc6s\d+l': 'spartan6l', - r'xc6s': 'spartan6', - r'xc4v': 'virtex4', - r'xc5v': 'virtex5', - r'xc6v\d+l': 'virtex6l', - r'xc6v': 'virtex6', - r'xc7v\d+l': 'virtex7l', - r'xc7v': 'virtex7', - r'xc7z': 'zynq' +class Ise(Project): + """Class to support ISE projects.""" + + tool = { + 'program': 'xtclsh', + 'command': 'xtclsh ise.tcl', } - for key, value in families.items(): - if re.match(key, part): - return value - return 'UNKNOWN' + +# _TOOL = 'ise' +# _EXTENSION = 'xise' +# _PART = 'xc7k160t-3-fbg484' +# _GEN_PROGRAM = 'xtclsh' +# _GEN_COMMAND = 'xtclsh ise.tcl' +# _TRF_PROGRAM = 'impact' +# _TRF_COMMAND = 'impact -batch ise-prog.impact' +# _BIT_EXT = ['bit'] +# _DEVTYPES = ['fpga', 'spi', 'bpi', 'detect', 'unlock'] +# _CLEAN = [ +# # directories +# 'iseconfig', '_ngo', 'xlnx_auto_0_xdb', '_xmsgs', 'xst', +# # files +# '*.bgn', '*.bld', '*.bit', +# '*.cmd_log', '*.csv', +# '*.drc', +# '*.gise', +# '*.html', +# '*.log', '*.lso', +# '*.map', '*.mrp', +# '*.ncd', '*.ngc', '*.ngd', '*.ngm', '*.ngr', +# '*.pad', '*.par', '*.pcf', '*.prj', '*.ptwx', +# '*.stx', '*.syr', +# '*.twr', '*.twx', +# '*.unroutes', '*.ut', +# '*.txt', +# '*.xml', '*.xpi', '*.xrpt', '*.xst', '*.xwbt', +# '_impact*', +# # pyfpga +# '*.impact', 'ise.tcl' +# ] + +# def __init__(self, project, frontend=None): +# super().__init__(project) +# if frontend == 'yosys': +# from fpga.tool.openflow import Openflow +# self.tool = Openflow( +# self.project, +# frontend='yosys', +# backend='ise' +# ) +# self.presynth = True + +# def set_part(self, part): +# try: +# device, speed, package = +# re.findall(r'(\w+)-(\w+)-(\w+)', part)[0] +# if len(speed) > len(package): +# speed, package = package, speed +# part = f'{device}-{speed}-{package}' +# except IndexError: +# raise ValueError( +# 'Part must be DEVICE-SPEED-PACKAGE or DEVICE-PACKAGE-SPEED' +# ) +# self.part['name'] = part +# self.part['family'] = get_family(part) +# self.part['device'] = device +# self.part['package'] = package +# self.part['speed'] = '-' + speed + +# def generate(self, to_task, from_task, capture): +# if self.presynth and from_task in ['prj', 'syn']: +# self.tool.set_part(self.part['name']) +# self.tool.set_top(self.top) +# self.tool.paths = self.paths +# self.tool.files['vhdl'] = self.files['vhdl'] +# self.tool.files['verilog'] = self.files['verilog'] +# self.tool.params = self.params +# output1 = self.tool.generate('syn', 'prj', capture) +# output2 = super().generate(to_task, from_task, capture) +# return str(output1) + str(output2) +# return super().generate(to_task, from_task, capture) + +# def transfer(self, devtype, position, part, width, capture): +# super().transfer(devtype, position, part, width, capture) +# temp = _TEMPLATES[devtype] +# if devtype not in ['detect', 'unlock']: +# temp = temp.replace('#BITSTREAM#', self.bitstream) +# temp = temp.replace('#POSITION#', str(position)) +# temp = temp.replace('#NAME#', part) +# temp = temp.replace('#WIDTH#', str(width)) +# with open('ise-prog.impact', 'w', encoding='utf-8') as file: +# file.write(temp) +# return run(self._TRF_COMMAND, capture) + + +# def get_family(part): +# """Get the Family name from the specified part name.""" +# part = part.lower() +# families = { +# r'xc7a\d+l': 'artix7l', +# r'xc7a': 'artix7', +# r'xc7k\d+l': 'kintex7l', +# r'xc7k': 'kintex7', +# r'xc3sd\d+a': 'spartan3adsp', +# r'xc3s\d+a': 'spartan3a', +# r'xc3s\d+e': 'spartan3e', +# r'xc3s': 'spartan3', +# r'xc6s\d+l': 'spartan6l', +# r'xc6s': 'spartan6', +# r'xc4v': 'virtex4', +# r'xc5v': 'virtex5', +# r'xc6v\d+l': 'virtex6l', +# r'xc6v': 'virtex6', +# r'xc7v\d+l': 'virtex7l', +# r'xc7v': 'virtex7', +# r'xc7z': 'zynq' +# } +# for key, value in families.items(): +# if re.match(key, part): +# return value +# return 'UNKNOWN' diff --git a/pyfpga/libero.py b/pyfpga/libero.py index c037284c..5513698e 100644 --- a/pyfpga/libero.py +++ b/pyfpga/libero.py @@ -4,14 +4,13 @@ # SPDX-License-Identifier: GPL-3.0-or-later # -"""fpga.tool.libero - -Implements the support of Libero (Microchip/Microsemi). +""" +Implements support for Libero. """ -import re +# import re -from fpga.tool import Tool +from pyfpga.project import Project _TEMPLATES = { 'fpga': """\ @@ -28,64 +27,72 @@ # -clk_mode {free_running_clk} -programming_method {spi_slave} \ # -force_freq {OFF} -freq {4000000}" +# pylint: disable=too-few-public-methods + -class Libero(Tool): - """Implementation of the class to support Libero.""" +class Libero(Project): + """Class to support Libero.""" - _TOOL = 'libero' - _EXTENSION = 'prjx' - _PART = 'mpf100t-1-fcg484' - _GEN_PROGRAM = 'libero' - _GEN_COMMAND = 'libero SCRIPT:libero.tcl' - _DEVTYPES = ['fpga'] - _CLEAN = [ - # directories - 'libero', - # pyfpga - 'libero.tcl' - ] + tool = { + 'program': 'libero', + 'command': 'libero SCRIPT:libero.tcl', + } - def set_part(self, part): - try: - device, speed, package = re.findall(r'(\w+)-(\w+)-*(\w*)', part)[0] - if len(speed) > len(package): - speed, package = package, speed - if speed == '': - speed = 'STD' - part = f'{device}-{speed}-{package}' - except IndexError: - raise ValueError( - 'Part must be DEVICE-SPEED-PACKAGE or DEVICE-PACKAGE' - ) - self.part['name'] = part - self.part['family'] = get_family(part) - self.part['device'] = device - self.part['package'] = package - self.part['speed'] = 'STD' if speed == 'STD' else '-' + speed +# _TOOL = 'libero' +# _EXTENSION = 'prjx' +# _PART = 'mpf100t-1-fcg484' +# _GEN_PROGRAM = 'libero' +# _GEN_COMMAND = 'libero SCRIPT:libero.tcl' +# _DEVTYPES = ['fpga'] +# _CLEAN = [ +# # directories +# 'libero', +# # pyfpga +# 'libero.tcl' +# ] - def transfer(self, devtype, position, part, width, capture): - super().transfer(devtype, position, part, width, capture) - raise NotImplementedError('transfer(libero)') +# def set_part(self, part): +# try: +# device, speed, package = +# re.findall(r'(\w+)-(\w+)-*(\w*)', part)[0] +# if len(speed) > len(package): +# speed, package = package, speed +# if speed == '': +# speed = 'STD' +# part = f'{device}-{speed}-{package}' +# except IndexError: +# raise ValueError( +# 'Part must be DEVICE-SPEED-PACKAGE or DEVICE-PACKAGE' +# ) +# self.part['name'] = part +# self.part['family'] = get_family(part) +# self.part['device'] = device +# self.part['package'] = package +# self.part['speed'] = 'STD' if speed == 'STD' else '-' + speed +# def transfer(self, devtype, position, part, width, capture): +# super().transfer(devtype, position, part, width, capture) +# raise NotImplementedError('transfer(libero)') -def get_family(part): - """Get the Family name from the specified part name.""" - part = part.lower() - families = { - r'm2s': 'SmartFusion2', - r'm2gl': 'Igloo2', - r'rt4g': 'RTG4', - r'mpf': 'PolarFire', - r'a2f': 'SmartFusion', - r'afs': 'Fusion', - r'aglp': 'IGLOO+', - r'agle': 'IGLOOE', - r'agl': 'IGLOO', - r'a3p\d+l': 'ProAsic3L', - r'a3pe': 'ProAsic3E', - r'a3p': 'ProAsic3' - } - for key, value in families.items(): - if re.match(key, part): - return value - return 'UNKNOWN' + +# def get_family(part): +# """Get the Family name from the specified part name.""" +# part = part.lower() +# families = { +# r'm2s': 'SmartFusion2', +# r'm2gl': 'Igloo2', +# r'rt4g': 'RTG4', +# r'mpf': 'PolarFire', +# r'a2f': 'SmartFusion', +# r'afs': 'Fusion', +# r'aglp': 'IGLOO+', +# r'agle': 'IGLOOE', +# r'agl': 'IGLOO', +# r'a3p\d+l': 'ProAsic3L', +# r'a3pe': 'ProAsic3E', +# r'a3p': 'ProAsic3' +# } +# for key, value in families.items(): +# if re.match(key, part): +# return value +# return 'UNKNOWN' diff --git a/pyfpga/openflow.py b/pyfpga/openflow.py index b8207d58..b010ae40 100644 --- a/pyfpga/openflow.py +++ b/pyfpga/openflow.py @@ -4,203 +4,210 @@ # SPDX-License-Identifier: GPL-3.0-or-later # -"""fpga.tool.openflow - -Implements the support of the open-source tools. """ +Implements support for an Open Source development flow. +""" + +# import os +from pyfpga.project import Project + +# pylint: disable=too-few-public-methods -import os -from fpga.tool import Tool, run +class Openflow(Project): + """Class to support Openflow.""" -class Openflow(Tool): - """Implementation of the class to support the open-source tools.""" + tool = { + 'program': 'docker', + 'command': 'bash openflow.sh', + } - _TOOL = 'openflow' - _PART = 'hx8k-ct256' - _GEN_PROGRAM = 'docker' - _GEN_COMMAND = 'bash openflow.sh' - _TRF_PROGRAM = 'docker' - _TRF_COMMAND = 'bash openprog.sh' - _BIT_EXT = ['bit'] - _DEVTYPES = ['fpga'] - _CLEAN = [ - # files - '*.asc', '*.bit', '*.cf', '*.config', '*.edif', '*.json', '*.rpt', - '*.svf', - # pyfpga - '*.sh' - ] +# _TOOL = 'openflow' +# _PART = 'hx8k-ct256' +# _GEN_PROGRAM = 'docker' +# _GEN_COMMAND = 'bash openflow.sh' +# _TRF_PROGRAM = 'docker' +# _TRF_COMMAND = 'bash openprog.sh' +# _BIT_EXT = ['bit'] +# _DEVTYPES = ['fpga'] +# _CLEAN = [ +# # files +# '*.asc', '*.bit', '*.cf', '*.config', '*.edif', '*.json', '*.rpt', +# '*.svf', +# # pyfpga +# '*.sh' +# ] - def __init__(self, project, frontend='yosys', backend='nextpnr'): - # The valid frontends are be ghdl and yosys - # The valid backends are: - # * For ghdl -> vhdl - # * For yosys -> ise, nextpnr, verilog, verilog-nosynth and vivado - super().__init__(project) - self.backend = backend - self.frontend = frontend +# def __init__(self, project, frontend='yosys', backend='nextpnr'): +# # The valid frontends are be ghdl and yosys +# # The valid backends are: +# # * For ghdl -> vhdl +# # * For yosys -> ise, nextpnr, verilog, verilog-nosynth and vivado +# super().__init__(project) +# self.backend = backend +# self.frontend = frontend - def _configure(self): - super()._configure() - # OCI ENGINE - engine = self.configs.get('oci', {}).get('engine', {}) - command = engine.get('command', 'docker') + ' run --rm' - volumes = '-v ' + ('-v ').join(engine.get('volumes', ['$HOME:$HOME'])) - work = '-w ' + engine.get('work', '$PWD') - self.oci_engine = f'{command} {volumes} {work}' - # Containers - defaults = { - 'ghdl': 'ghdl/synth:beta', - 'yosys': 'ghdl/synth:beta', - 'nextpnr-ice40': 'ghdl/synth:nextpnr-ice40', - 'icetime': 'ghdl/synth:icestorm', - 'icepack': 'ghdl/synth:icestorm', - 'iceprog': '--device /dev/bus/usb ghdl/synth:prog', - 'nextpnr-ecp5': 'ghdl/synth:nextpnr-ecp5', - 'ecppack': 'ghdl/synth:trellis', - 'openocd': '--device /dev/bus/usb ghdl/synth:prog' - } - self.tools = {} - self.conts = {} - tools = self.configs.get('tools', {}) - containers = self.configs.get('oci', {}).get('containers', {}) - for tool, container in defaults.items(): - self.tools[tool] = tools.get(tool, tool) - self.conts[tool] = containers.get(tool, container) +# def _configure(self): +# super()._configure() +# # OCI ENGINE +# engine = self.configs.get('oci', {}).get('engine', {}) +# command = engine.get('command', 'docker') + ' run --rm' +# volumes = +# '-v ' + ('-v ').join(engine.get('volumes', ['$HOME:$HOME'])) +# work = '-w ' + engine.get('work', '$PWD') +# self.oci_engine = f'{command} {volumes} {work}' +# # Containers +# defaults = { +# 'ghdl': 'ghdl/synth:beta', +# 'yosys': 'ghdl/synth:beta', +# 'nextpnr-ice40': 'ghdl/synth:nextpnr-ice40', +# 'icetime': 'ghdl/synth:icestorm', +# 'icepack': 'ghdl/synth:icestorm', +# 'iceprog': '--device /dev/bus/usb ghdl/synth:prog', +# 'nextpnr-ecp5': 'ghdl/synth:nextpnr-ecp5', +# 'ecppack': 'ghdl/synth:trellis', +# 'openocd': '--device /dev/bus/usb ghdl/synth:prog' +# } +# self.tools = {} +# self.conts = {} +# tools = self.configs.get('tools', {}) +# containers = self.configs.get('oci', {}).get('containers', {}) +# for tool, container in defaults.items(): +# self.tools[tool] = tools.get(tool, tool) +# self.conts[tool] = containers.get(tool, container) - def set_part(self, part): - self.part['name'] = part - self.part['family'] = get_family(part) - if self.part['family'] in ['ice40', 'ecp5']: - aux = part.split('-') - if len(aux) == 2: - self.part['device'] = aux[0] - self.part['package'] = aux[1] - elif len(aux) == 3: - self.part['device'] = f'{aux[0]}-{aux[1]}' - self.part['package'] = aux[2] - else: - raise ValueError('Part must be DEVICE-PACKAGE') - if self.part['device'].endswith('4k'): - # See http://www.clifford.at/icestorm/ - self.part['device'] = self.part['device'].replace('4', '8') - self.part['package'] += ":4k" +# def set_part(self, part): +# self.part['name'] = part +# self.part['family'] = get_family(part) +# if self.part['family'] in ['ice40', 'ecp5']: +# aux = part.split('-') +# if len(aux) == 2: +# self.part['device'] = aux[0] +# self.part['package'] = aux[1] +# elif len(aux) == 3: +# self.part['device'] = f'{aux[0]}-{aux[1]}' +# self.part['package'] = aux[2] +# else: +# raise ValueError('Part must be DEVICE-PACKAGE') +# if self.part['device'].endswith('4k'): +# # See http://www.clifford.at/icestorm/ +# self.part['device'] = self.part['device'].replace('4', '8') +# self.part['package'] += ":4k" - def _create_gen_script(self, tasks): - # Verilog includes - paths = [] - for path in self.paths: - paths.append(f'verilog_defaults -add -I{path}') - # Files - constraints = [] - verilogs = [] - vhdls = [] - for file in self.files['vhdl']: - lib = '' - if file[1] is not None: - lib = f'--work={file[1]}' - vhdls.append(f'{self.tools["ghdl"]} -a $FLAGS {lib} {file[0]}') - for file in self.files['verilog']: - if file[0].endswith('.sv'): - verilogs.append(f'read_verilog -sv -defer {file[0]}') - else: - verilogs.append(f'read_verilog -defer {file[0]}') - for file in self.files['constraint']: - constraints.append(file[0]) - if len(vhdls) > 0: - verilogs = [f'ghdl $FLAGS {self.top}'] - # Parameters - params = [] - for param in self.params: - params.append(f'chparam -set {param[0]} {param[1]} {self.top}') - # Script creation - template = os.path.join(os.path.dirname(__file__), 'template.sh') - with open(template, 'r', encoding='utf-8') as file: - text = file.read() - text = text.format( - backend=self.backend, - constraints='\\\n'+'\n'.join(constraints), - device=self.part['device'], - includes='\\\n'+'\n'.join(paths), - family=self.part['family'], - frontend=self.frontend, - package=self.part['package'], - params='\\\n'+'\n'.join(params), - project=self.project, - tasks=tasks, - top=self.top, - verilogs='\\\n'+'\n'.join(verilogs), - vhdls='\\\n'+'\n'.join(vhdls), - # - oci_engine=self.oci_engine, - cont_ghdl=self.conts['ghdl'], - cont_yosys=self.conts['yosys'], - cont_nextpnr_ice40=self.conts['nextpnr-ice40'], - cont_icetime=self.conts['icetime'], - cont_icepack=self.conts['icepack'], - cont_nextpnr_ecp5=self.conts['nextpnr-ecp5'], - cont_ecppack=self.conts['ecppack'], - tool_ghdl=self.tools['ghdl'], - tool_yosys=self.tools['yosys'], - tool_nextpnr_ice40=self.tools['nextpnr-ice40'], - tool_icetime=self.tools['icetime'], - tool_icepack=self.tools['icepack'], - tool_nextpnr_ecp5=self.tools['nextpnr-ecp5'], - tool_ecppack=self.tools['ecppack'] - ) - with open(f'{self._TOOL}.sh', 'w', encoding='utf-8') as file: - file.write(text) +# def _create_gen_script(self, tasks): +# # Verilog includes +# paths = [] +# for path in self.paths: +# paths.append(f'verilog_defaults -add -I{path}') +# # Files +# constraints = [] +# verilogs = [] +# vhdls = [] +# for file in self.files['vhdl']: +# lib = '' +# if file[1] is not None: +# lib = f'--work={file[1]}' +# vhdls.append(f'{self.tools["ghdl"]} -a $FLAGS {lib} {file[0]}') +# for file in self.files['verilog']: +# if file[0].endswith('.sv'): +# verilogs.append(f'read_verilog -sv -defer {file[0]}') +# else: +# verilogs.append(f'read_verilog -defer {file[0]}') +# for file in self.files['constraint']: +# constraints.append(file[0]) +# if len(vhdls) > 0: +# verilogs = [f'ghdl $FLAGS {self.top}'] +# # Parameters +# params = [] +# for param in self.params: +# params.append(f'chparam -set {param[0]} {param[1]} {self.top}') +# # Script creation +# template = os.path.join(os.path.dirname(__file__), 'template.sh') +# with open(template, 'r', encoding='utf-8') as file: +# text = file.read() +# text = text.format( +# backend=self.backend, +# constraints='\\\n'+'\n'.join(constraints), +# device=self.part['device'], +# includes='\\\n'+'\n'.join(paths), +# family=self.part['family'], +# frontend=self.frontend, +# package=self.part['package'], +# params='\\\n'+'\n'.join(params), +# project=self.project, +# tasks=tasks, +# top=self.top, +# verilogs='\\\n'+'\n'.join(verilogs), +# vhdls='\\\n'+'\n'.join(vhdls), +# # +# oci_engine=self.oci_engine, +# cont_ghdl=self.conts['ghdl'], +# cont_yosys=self.conts['yosys'], +# cont_nextpnr_ice40=self.conts['nextpnr-ice40'], +# cont_icetime=self.conts['icetime'], +# cont_icepack=self.conts['icepack'], +# cont_nextpnr_ecp5=self.conts['nextpnr-ecp5'], +# cont_ecppack=self.conts['ecppack'], +# tool_ghdl=self.tools['ghdl'], +# tool_yosys=self.tools['yosys'], +# tool_nextpnr_ice40=self.tools['nextpnr-ice40'], +# tool_icetime=self.tools['icetime'], +# tool_icepack=self.tools['icepack'], +# tool_nextpnr_ecp5=self.tools['nextpnr-ecp5'], +# tool_ecppack=self.tools['ecppack'] +# ) +# with open(f'{self._TOOL}.sh', 'w', encoding='utf-8') as file: +# file.write(text) - def generate(self, to_task, from_task, capture): - if self.frontend == 'ghdl' or 'verilog' in self.backend: - to_task = 'syn' - from_task = 'syn' - return super().generate(to_task, from_task, capture) +# def generate(self, to_task, from_task, capture): +# if self.frontend == 'ghdl' or 'verilog' in self.backend: +# to_task = 'syn' +# from_task = 'syn' +# return super().generate(to_task, from_task, capture) - def transfer(self, devtype, position, part, width, capture): - super().transfer(devtype, position, part, width, capture) - template = os.path.join(os.path.dirname(__file__), 'openprog.sh') - with open(template, 'r', encoding='utf-8') as file: - text = file.read() - text = text.format( - family=self.part['family'], - project=self.project, - # - oci_engine=self.oci_engine, - cont_iceprog=self.conts['iceprog'], - cont_openocd=self.conts['openocd'], - tool_iceprog=self.tools['iceprog'], - tool_openocd=self.tools['openocd'] - ) - with open('openprog.sh', 'w', encoding='utf-8') as file: - file.write(text) - return run(self._TRF_COMMAND, capture) +# def transfer(self, devtype, position, part, width, capture): +# super().transfer(devtype, position, part, width, capture) +# template = os.path.join(os.path.dirname(__file__), 'openprog.sh') +# with open(template, 'r', encoding='utf-8') as file: +# text = file.read() +# text = text.format( +# family=self.part['family'], +# project=self.project, +# # +# oci_engine=self.oci_engine, +# cont_iceprog=self.conts['iceprog'], +# cont_openocd=self.conts['openocd'], +# tool_iceprog=self.tools['iceprog'], +# tool_openocd=self.tools['openocd'] +# ) +# with open('openprog.sh', 'w', encoding='utf-8') as file: +# file.write(text) +# return run(self._TRF_COMMAND, capture) -def get_family(part): - """Get the Family name from the specified part name.""" - part = part.lower() - families = [ - # From /techlibs/xilinx/synth_xilinx.cc - 'xcup', 'xcu', 'xc7', 'xc6s', 'xc6v', 'xc5v', 'xc4v', 'xc3sda', - 'xc3sa', 'xc3se', 'xc3s', 'xc2vp', 'xc2v', 'xcve', 'xcv' - ] - for family in families: - if part.startswith(family): - return family - families = [ - # From /ice40/main.cc - 'lp384', 'lp1k', 'lp4k', 'lp8k', 'hx1k', 'hx4k', 'hx8k', - 'up3k', 'up5k', 'u1k', 'u2k', 'u4k' - ] - if part.startswith(tuple(families)): - return 'ice40' - families = [ - # From /ecp5/main.cc - '12k', '25k', '45k', '85k', 'um-25k', 'um-45k', 'um-85k', - 'um5g-25k', 'um5g-45k', 'um5g-85k' - ] - if part.startswith(tuple(families)): - return 'ecp5' - return 'UNKNOWN' +# def get_family(part): +# """Get the Family name from the specified part name.""" +# part = part.lower() +# families = [ +# # From /techlibs/xilinx/synth_xilinx.cc +# 'xcup', 'xcu', 'xc7', 'xc6s', 'xc6v', 'xc5v', 'xc4v', 'xc3sda', +# 'xc3sa', 'xc3se', 'xc3s', 'xc2vp', 'xc2v', 'xcve', 'xcv' +# ] +# for family in families: +# if part.startswith(family): +# return family +# families = [ +# # From /ice40/main.cc +# 'lp384', 'lp1k', 'lp4k', 'lp8k', 'hx1k', 'hx4k', 'hx8k', +# 'up3k', 'up5k', 'u1k', 'u2k', 'u4k' +# ] +# if part.startswith(tuple(families)): +# return 'ice40' +# families = [ +# # From /ecp5/main.cc +# '12k', '25k', '45k', '85k', 'um-25k', 'um-45k', 'um-85k', +# 'um5g-25k', 'um5g-45k', 'um5g-85k' +# ] +# if part.startswith(tuple(families)): +# return 'ecp5' +# return 'UNKNOWN' diff --git a/pyfpga/quartus.py b/pyfpga/quartus.py index 94db6ad6..9c6c861e 100644 --- a/pyfpga/quartus.py +++ b/pyfpga/quartus.py @@ -4,50 +4,54 @@ # SPDX-License-Identifier: GPL-3.0-or-later # -"""fpga.tool.quartus - -Implements the support of Quartus (Intel/Altera). +""" +Implements support for Quartus. """ -import re -import subprocess - -from fpga.tool import Tool, run - - -class Quartus(Tool): - """Implementation of the class to support Quartus.""" - - _TOOL = 'quartus' - _EXTENSION = 'qpf' - _PART = '10cl120zf780i8g' - _GEN_PROGRAM = 'quartus_sh' - _GEN_COMMAND = 'quartus_sh --script quartus.tcl' - _TRF_PROGRAM = 'quartus_pgm' - _TRF_COMMAND = 'quartus_pgm -c %s --mode jtag -o "p;%s@%s"' - _BIT_EXT = ['sof', 'pof'] - _DEVTYPES = ['fpga', 'detect'] - _CLEAN = [ - # directories - 'db', 'incremental_db', 'output_files', - # files - '*.done', '*.jdi', '*.log', '*.pin', '*.pof', '*.qws', '*.rpt', - '*.smsg', '*.sld', '*.sof', '*.sop', '*.summary', '*.txt', - # pyfpga - 'quartus.tcl' - ] - - def transfer(self, devtype, position, part, width, capture): - super().transfer(devtype, position, part, width, capture) - result = subprocess.run( - 'jtagconfig', shell=True, check=True, - stdout=subprocess.PIPE, universal_newlines=True - ) - result = result.stdout - if devtype == 'detect': - print(result) - else: - cable = re.match(r"1\) (.*) \[", result).groups()[0] - cmd = self._TRF_COMMAND % (cable, self.bitstream, position) - result = run(cmd, capture) - return result +# import re +# import subprocess + +from pyfpga.project import Project + + +class Quartus(Project): + """Class to support Quartus.""" + + tool = { + 'program': 'quartus_sh', + 'command': 'quartus_sh --script quartus.tcl', + } + +# _TOOL = 'quartus' +# _EXTENSION = 'qpf' +# _PART = '10cl120zf780i8g' +# _GEN_PROGRAM = 'quartus_sh' +# _GEN_COMMAND = 'quartus_sh --script quartus.tcl' +# _TRF_PROGRAM = 'quartus_pgm' +# _TRF_COMMAND = 'quartus_pgm -c %s --mode jtag -o "p;%s@%s"' +# _BIT_EXT = ['sof', 'pof'] +# _DEVTYPES = ['fpga', 'detect'] +# _CLEAN = [ +# # directories +# 'db', 'incremental_db', 'output_files', +# # files +# '*.done', '*.jdi', '*.log', '*.pin', '*.pof', '*.qws', '*.rpt', +# '*.smsg', '*.sld', '*.sof', '*.sop', '*.summary', '*.txt', +# # pyfpga +# 'quartus.tcl' +# ] + +# def transfer(self, devtype, position, part, width, capture): +# super().transfer(devtype, position, part, width, capture) +# result = subprocess.run( +# 'jtagconfig', shell=True, check=True, +# stdout=subprocess.PIPE, universal_newlines=True +# ) +# result = result.stdout +# if devtype == 'detect': +# print(result) +# else: +# cable = re.match(r"1\) (.*) \[", result).groups()[0] +# cmd = self._TRF_COMMAND % (cable, self.bitstream, position) +# result = run(cmd, capture) +# return result diff --git a/pyfpga/tool.py b/pyfpga/tool.py index f02931a3..19a756d4 100644 --- a/pyfpga/tool.py +++ b/pyfpga/tool.py @@ -9,40 +9,17 @@ Defines the interface to be inherited to support a tool. """ -from glob import glob -import os -import subprocess -from shutil import rmtree, which -from yaml import safe_load - FILETYPES = ['verilog', 'vhdl', 'constraint', 'design'] MEMWIDTHS = [1, 2, 4, 8, 16, 32] PHASES = ['prefile', 'project', 'preflow', 'postsyn', 'postpar', 'postbit'] TASKS = ['prj', 'syn', 'par', 'bit'] +# def tcl_path(path): +# """Returns a Tcl suitable path.""" +# return path.replace(os.path.sep, "/") -def check_value(value, values): - """Check if VALUE is included in VALUES.""" - if value not in values: - joined_values = ", ".join(values) - raise ValueError(f'{value} is not a valid value [{joined_values}]') - - -def run(command, capture): - """Run a command.""" - output = subprocess.PIPE if capture else None - check = not capture - result = subprocess.run( - command, shell=True, check=check, universal_newlines=True, - stdout=output, stderr=subprocess.STDOUT - ) - return result.stdout - - -def tcl_path(path): - """Returns a Tcl suitable path.""" - return path.replace(os.path.sep, "/") +# pylint: disable=too-few-public-methods class Tool: @@ -51,195 +28,152 @@ class Tool: It is the basic interface for tool implementations. """ - # Following variables are set in each inheritance (if employed) - _TOOL = None # tool name - _EXTENSION = None # project file extension - _PART = None # default device part name - _GEN_PROGRAM = None # program used when generate is executed - _GEN_COMMAND = None # command to run when generate is executed - _TRF_PROGRAM = None # program used when transfer is executed - _TRF_COMMAND = None # command to run when transfer is executed - _BIT_EXT = [] # Supported BITstream EXTensions - _DEVTYPES = [] # Supported DEVice TYPES - _CLEAN = [] # Files to be CLEAN - - def __init__(self, project): - """Initializes the attributes of the class.""" - self.bitstream = None - self.cmds = { - 'prefile': [], - 'project': [], - 'preflow': [], - 'postsyn': [], - 'postpar': [], - 'postbit': [] - } - self.files = { - 'vhdl': [], - 'verilog': [], - 'constraint': [], - 'design': [] - } - self.params = [] - self.part = { - 'name': 'UNSET', - 'family': 'UNSET', - 'device': 'UNSET', - 'package': 'UNSET', - 'speed': 'UNSET' - } - self.paths = [] - self.presynth = False - self.project = self._TOOL if project is None else project - self.set_part(self._PART) - self.set_top('UNDEFINED') - self._configure() - - def _configure(self): - """Configures the underlying tools.""" - filename = '.pyfpga.yml' - self.configs = {} - if os.path.exists(filename): - with open(filename, 'r', encoding='utf-8') as file: - data = safe_load(file) - if self._TOOL in data: - self.configs = data[self._TOOL] - - def get_configs(self): - """Get Configurations.""" - return { - 'tool': self._TOOL, - 'project': self.project, - 'extension': self._EXTENSION, - 'part': self.part['name'] - } - - def set_part(self, part): - """Set the target PART.""" - self.part['name'] = part - - def add_param(self, name, value): - """Add a Generic/Parameter Value.""" - self.params.append([name, value]) - - def add_file(self, file, filetype, library, options): - """Add a file to the project of the specified **type**.""" - check_value(filetype, FILETYPES) - self.files[filetype].append([file, library, options]) - - def get_files(self): - """Get the files of the project.""" - return self.files - - def add_vlog_include(self, path): - """Add a Verilog include path.""" - self.paths.append(path) - - def set_top(self, top): - """Set the TOP LEVEL of the project.""" - self.top = top - - def add_hook(self, hook, phase): - """Add the specified *hook* in the desired *phase*.""" - check_value(phase, PHASES) - self.cmds[phase].append(hook) - - def _create_gen_script(self, tasks): - """Create the script for generate execution.""" - # Paths and files - files = [] - if self.presynth: - files.append(f' fpga_file {self.project}.edif') - else: - for path in self.paths: - files.append(f' fpga_include {tcl_path(path)}') - for file in self.files['verilog']: - files.append(f' fpga_file {tcl_path(file[0])}') - for file in self.files['vhdl']: - if file[1] is None: - files.append(f' fpga_file {tcl_path(file[0])}') - else: - files.append( - f' fpga_file {tcl_path(file[0])} {file[1]}' - ) - for file in self.files['design']: - files.append(f' fpga_design {tcl_path(file[0])}') - for file in self.files['constraint']: - files.append(f' fpga_file {tcl_path(file[0])}') - # Parameters - params = [] - for param in self.params: - params.append(f'{{ {param[0]} {param[1]} }}') - # Script creation - template = os.path.join(os.path.dirname(__file__), 'template.tcl') - with open(template, 'r', encoding='utf-8') as file: - tcl = file.read() - tcl = tcl.replace('#TOOL#', self._TOOL) - tcl = tcl.replace('#PRESYNTH#', "True" if self.presynth else "False") - tcl = tcl.replace('#PROJECT#', self.project) - tcl = tcl.replace('#PART#', self.part['name']) - tcl = tcl.replace('#FAMILY#', self.part['family']) - tcl = tcl.replace('#DEVICE#', self.part['device']) - tcl = tcl.replace('#PACKAGE#', self.part['package']) - tcl = tcl.replace('#SPEED#', self.part['speed']) - tcl = tcl.replace('#PARAMS#', ' '.join(params)) - tcl = tcl.replace('#FILES#', '\n'.join(files)) - tcl = tcl.replace('#TOP#', self.top) - tcl = tcl.replace('#TASKS#', tasks) - tcl = tcl.replace('#PREFILE_CMDS#', '\n'.join(self.cmds['prefile'])) - tcl = tcl.replace('#PROJECT_CMDS#', '\n'.join(self.cmds['project'])) - tcl = tcl.replace('#PREFLOW_CMDS#', '\n'.join(self.cmds['preflow'])) - tcl = tcl.replace('#POSTSYN_CMDS#', '\n'.join(self.cmds['postsyn'])) - tcl = tcl.replace('#POSTPAR_CMDS#', '\n'.join(self.cmds['postpar'])) - tcl = tcl.replace('#POSTBIT_CMDS#', '\n'.join(self.cmds['postbit'])) - with open(f'{self._TOOL}.tcl', 'w', encoding='utf-8') as file: - file.write(tcl) - - def generate(self, to_task, from_task, capture): - """Run the FPGA tool.""" - check_value(to_task, TASKS) - check_value(from_task, TASKS) - to_index = TASKS.index(to_task) - from_index = TASKS.index(from_task) - if from_index > to_index: - raise ValueError( - f'initial task "{from_task}" cannot be later than the ' + - f'last task "{to_task}"' - ) - tasks = " ".join(TASKS[from_index:to_index+1]) - self._create_gen_script(tasks) - if not which(self._GEN_PROGRAM): - raise RuntimeError(f'program "{self._GEN_PROGRAM}" not found') - return run(self._GEN_COMMAND, capture) - - def set_bitstream(self, path): - """Set the bitstream file to transfer.""" - self.bitstream = path - - def transfer(self, devtype, position, part, width, capture): - """Transfer a bitstream.""" - if not which(self._TRF_PROGRAM): - raise RuntimeError(f'program "{self._TRF_PROGRAM}" not found') - check_value(devtype, self._DEVTYPES) - check_value(position, range(10)) - isinstance(part, str) - check_value(width, MEMWIDTHS) - isinstance(capture, bool) - # Bitstream autodiscovery - if not self.bitstream and devtype not in ['detect', 'unlock']: - bitstream = [] - for ext in self._BIT_EXT: - bitstream.extend(glob(f'**/*.{ext}', recursive=True)) - if len(bitstream) == 0: - raise FileNotFoundError('bitStream not found') - self.bitstream = bitstream[0] - - def clean(self): - """Clean the generated project files.""" - for path in self._CLEAN: - elements = glob(path) - for element in elements: - if os.path.isfile(element): - os.remove(element) - else: - rmtree(element) +# # Following variables are set in each inheritance (if employed) +# _TOOL = None # tool name +# _EXTENSION = None # project file extension +# _PART = None # default device part name +# _GEN_PROGRAM = None # program used when generate is executed +# _GEN_COMMAND = None # command to run when generate is executed +# _TRF_PROGRAM = None # program used when transfer is executed +# _TRF_COMMAND = None # command to run when transfer is executed +# _BIT_EXT = [] # Supported BITstream EXTensions +# _DEVTYPES = [] # Supported DEVice TYPES +# _CLEAN = [] # Files to be CLEAN + +# def __init__(self, project): +# """Initializes the attributes of the class.""" +# self.bitstream = None +# self.cmds = { +# 'prefile': [], +# 'project': [], +# 'preflow': [], +# 'postsyn': [], +# 'postpar': [], +# 'postbit': [] +# } +# self.files = { +# 'vhdl': [], +# 'verilog': [], +# 'constraint': [], +# 'design': [] +# } +# self.params = [] +# self.part = { +# 'name': 'UNSET', +# 'family': 'UNSET', +# 'device': 'UNSET', +# 'package': 'UNSET', +# 'speed': 'UNSET' +# } +# self.paths = [] +# self.presynth = False +# self.project = self._TOOL if project is None else project +# self.set_part(self._PART) +# self.set_top('UNDEFINED') +# self._configure() + +# def _configure(self): +# """Configures the underlying tools.""" +# filename = '.pyfpga.yml' +# self.configs = {} +# if os.path.exists(filename): +# with open(filename, 'r', encoding='utf-8') as file: +# data = safe_load(file) +# if self._TOOL in data: +# self.configs = data[self._TOOL] + +# def _create_gen_script(self, tasks): +# """Create the script for generate execution.""" +# # Paths and files +# files = [] +# if self.presynth: +# files.append(f' fpga_file {self.project}.edif') +# else: +# for path in self.paths: +# files.append(f' fpga_include {tcl_path(path)}') +# for file in self.files['verilog']: +# files.append(f' fpga_file {tcl_path(file[0])}') +# for file in self.files['vhdl']: +# if file[1] is None: +# files.append(f' fpga_file {tcl_path(file[0])}') +# else: +# files.append( +# f' fpga_file {tcl_path(file[0])} {file[1]}' +# ) +# for file in self.files['design']: +# files.append(f' fpga_design {tcl_path(file[0])}') +# for file in self.files['constraint']: +# files.append(f' fpga_file {tcl_path(file[0])}') +# # Parameters +# params = [] +# for param in self.params: +# params.append(f'{{ {param[0]} {param[1]} }}') +# # Script creation +# template = os.path.join(os.path.dirname(__file__), 'template.tcl') +# with open(template, 'r', encoding='utf-8') as file: +# tcl = file.read() +# tcl = tcl.replace('#TOOL#', self._TOOL) +# tcl = tcl.replace('#PRESYNTH#', "True" if self.presynth else "False") +# tcl = tcl.replace('#PROJECT#', self.project) +# tcl = tcl.replace('#PART#', self.part['name']) +# tcl = tcl.replace('#FAMILY#', self.part['family']) +# tcl = tcl.replace('#DEVICE#', self.part['device']) +# tcl = tcl.replace('#PACKAGE#', self.part['package']) +# tcl = tcl.replace('#SPEED#', self.part['speed']) +# tcl = tcl.replace('#PARAMS#', ' '.join(params)) +# tcl = tcl.replace('#FILES#', '\n'.join(files)) +# tcl = tcl.replace('#TOP#', self.top) +# tcl = tcl.replace('#TASKS#', tasks) +# tcl = tcl.replace('#PREFILE_CMDS#', '\n'.join(self.cmds['prefile'])) +# tcl = tcl.replace('#PROJECT_CMDS#', '\n'.join(self.cmds['project'])) +# tcl = tcl.replace('#PREFLOW_CMDS#', '\n'.join(self.cmds['preflow'])) +# tcl = tcl.replace('#POSTSYN_CMDS#', '\n'.join(self.cmds['postsyn'])) +# tcl = tcl.replace('#POSTPAR_CMDS#', '\n'.join(self.cmds['postpar'])) +# tcl = tcl.replace('#POSTBIT_CMDS#', '\n'.join(self.cmds['postbit'])) +# with open(f'{self._TOOL}.tcl', 'w', encoding='utf-8') as file: +# file.write(tcl) + +# def generate(self, to_task, from_task, capture): +# """Run the FPGA tool.""" +# check_value(to_task, TASKS) +# check_value(from_task, TASKS) +# to_index = TASKS.index(to_task) +# from_index = TASKS.index(from_task) +# if from_index > to_index: +# raise ValueError( +# f'initial task "{from_task}" cannot be later than the ' + +# f'last task "{to_task}"' +# ) +# tasks = " ".join(TASKS[from_index:to_index+1]) +# self._create_gen_script(tasks) +# if not which(self._GEN_PROGRAM): +# raise RuntimeError(f'program "{self._GEN_PROGRAM}" not found') +# return run(self._GEN_COMMAND, capture) + +# def transfer(self, devtype, position, part, width, capture): +# """Transfer a bitstream.""" +# if not which(self._TRF_PROGRAM): +# raise RuntimeError(f'program "{self._TRF_PROGRAM}" not found') +# check_value(devtype, self._DEVTYPES) +# check_value(position, range(10)) +# isinstance(part, str) +# check_value(width, MEMWIDTHS) +# isinstance(capture, bool) +# # Bitstream autodiscovery +# if not self.bitstream and devtype not in ['detect', 'unlock']: +# bitstream = [] +# for ext in self._BIT_EXT: +# bitstream.extend(glob(f'**/*.{ext}', recursive=True)) +# if len(bitstream) == 0: +# raise FileNotFoundError('bitStream not found') +# self.bitstream = bitstream[0] + +# def clean(self): +# """Clean the generated project files.""" +# for path in self._CLEAN: +# elements = glob(path) +# for element in elements: +# if os.path.isfile(element): +# os.remove(element) +# else: +# rmtree(element) diff --git a/pyfpga/vivado.py b/pyfpga/vivado.py index 24cd2ae9..46008024 100644 --- a/pyfpga/vivado.py +++ b/pyfpga/vivado.py @@ -4,12 +4,11 @@ # SPDX-License-Identifier: GPL-3.0-or-later # -"""fpga.tool.vivado - -Implements the support of Vivado (Xilinx). +""" +Implements support for Vivado. """ -from fpga.tool import Tool, run +from pyfpga.project import Project _TEMPLATES = { 'fpga': """\ @@ -28,58 +27,66 @@ """ } +# pylint: disable=too-few-public-methods + + +class Vivado(Project): + """Class to support Vivado.""" -class Vivado(Tool): - """Implementation of the class to support Vivado.""" + tool = { + 'program': 'vivado', + 'command': 'vivado -mode batch -notrace -quiet -source vivado.tcl', + } - _TOOL = 'vivado' - _EXTENSION = 'xpr' - _PART = 'xc7k160t-3-fbg484' - _GEN_PROGRAM = 'vivado' - _GEN_COMMAND = 'vivado -mode batch -notrace -quiet -source vivado.tcl' - _TRF_PROGRAM = 'vivado' - _TRF_COMMAND = 'vivado -mode batch -notrace -quiet -source vivado-prog.tcl' - _BIT_EXT = ['bit'] - _DEVTYPES = ['fpga', 'detect'] - _CLEAN = [ - # directories - '*.cache', '*.hw', '*.ip_user_files', '*.runs', '*.sim', '.Xil', - # files - '*.bit', '*.jou', '*.log', '*.rpt', 'vivado_*.zip', - # pyfpga - 'vivado.tcl', 'vivado-prog.tcl' - ] +# _TOOL = 'vivado' +# _EXTENSION = 'xpr' +# _PART = 'xc7k160t-3-fbg484' +# _GEN_PROGRAM = 'vivado' +# _GEN_COMMAND = 'vivado -mode batch -notrace -quiet -source vivado.tcl' +# _TRF_PROGRAM = 'vivado' +# _TRF_COMMAND = +# 'vivado -mode batch -notrace -quiet -source vivado-prog.tcl' +# _BIT_EXT = ['bit'] +# _DEVTYPES = ['fpga', 'detect'] +# _CLEAN = [ +# # directories +# '*.cache', '*.hw', '*.ip_user_files', '*.runs', '*.sim', '.Xil', +# # files +# '*.bit', '*.jou', '*.log', '*.rpt', 'vivado_*.zip', +# # pyfpga +# 'vivado.tcl', 'vivado-prog.tcl' +# ] - def __init__(self, project, frontend=None): - super().__init__(project) - if frontend == 'yosys': - from fpga.tool.openflow import Openflow - self.tool = Openflow( - self.project, - frontend='yosys', - backend='vivado' - ) - self.presynth = True +# def __init__(self, project, frontend=None): +# super().__init__(project) +# if frontend == 'yosys': +# from fpga.tool.openflow import Openflow +# self.tool = Openflow( +# self.project, +# frontend='yosys', +# backend='vivado' +# ) +# self.presynth = True - def generate(self, to_task, from_task, capture): - if self.presynth and from_task in ['prj', 'syn']: - self.tool.set_part(self.part['name']) - self.tool.set_top(self.top) - self.tool.paths = self.paths - self.tool.files['vhdl'] = self.files['vhdl'] - self.tool.files['verilog'] = self.files['verilog'] - self.tool.params = self.params - output1 = self.tool.generate('syn', 'prj', capture) - self.set_top(self.project) - output2 = super().generate(to_task, from_task, capture) - return str(output1) + str(output2) - return super().generate(to_task, from_task, capture) +# def generate(self, to_task, from_task, capture): +# if self.presynth and from_task in ['prj', 'syn']: +# self.tool.set_part(self.part['name']) +# self.tool.set_top(self.top) +# self.tool.paths = self.paths +# self.tool.files['vhdl'] = self.files['vhdl'] +# self.tool.files['verilog'] = self.files['verilog'] +# self.tool.params = self.params +# output1 = self.tool.generate('syn', 'prj', capture) +# self.set_top(self.project) +# output2 = super().generate(to_task, from_task, capture) +# return str(output1) + str(output2) +# return super().generate(to_task, from_task, capture) - def transfer(self, devtype, position, part, width, capture): - super().transfer(devtype, position, part, width, capture) - temp = _TEMPLATES[devtype] - if devtype != 'detect': - temp = temp.replace('#BITSTREAM#', self.bitstream) - with open('vivado-prog.tcl', 'w', encoding='utf-8') as file: - file.write(temp) - return run(self._TRF_COMMAND, capture) +# def transfer(self, devtype, position, part, width, capture): +# super().transfer(devtype, position, part, width, capture) +# temp = _TEMPLATES[devtype] +# if devtype != 'detect': +# temp = temp.replace('#BITSTREAM#', self.bitstream) +# with open('vivado-prog.tcl', 'w', encoding='utf-8') as file: +# file.write(temp) +# return run(self._TRF_COMMAND, capture) From 7d10d7b9acb7422776d07aad6253157c96799b70 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Tue, 4 Jun 2024 22:14:41 -0300 Subject: [PATCH 069/254] Added to check if the needed underlying tool is available --- pyfpga/project.py | 46 ++++++++++++++++++++++++++++++---------------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/pyfpga/project.py b/pyfpga/project.py index f7d49537..5c054dd3 100644 --- a/pyfpga/project.py +++ b/pyfpga/project.py @@ -15,6 +15,7 @@ from datetime import datetime from pathlib import Path +from shutil import which from time import time @@ -25,9 +26,12 @@ class Project: :type name: str, optional :param odir: output directory :type odir: str, optional - :raises NotImplementedError: when a method is not implemented yet + :raises ValueError: when an invalid value is specified + :raises RuntimeError: when the needed underlying tool is not available """ + tool = {} + def __init__(self, name=None, odir='results'): """Class constructor.""" self.data = {} @@ -189,31 +193,41 @@ def add_hook(self, stage, hook): raise ValueError('Invalid stage.') self.data.setdefault('hooks', {}).setdefault(stage, []).append(hook) - def make(self, end='bit', start='prj'): + def make(self, last='bit', first='prj'): """Run the underlying tool. - :param end: last task - :type end: str, optional - :param start: first task - :type start: str, optional + :param last: last step + :type last: str, optional + :param first: first step + :type first: str, optional - .. note:: Valid values are ``cfg``, ``syn``, ``imp`` and ``bit``. + .. note:: valid steps are ``cfg``, ``syn``, ``imp`` and ``bit``. """ steps = ['cfg', 'syn', 'par', 'bit'] - if end not in steps or start not in steps: - raise ValueError('Invalid steps.') - _ = self - raise NotImplementedError('Method is not implemented yet.') - - def prog(self, position=1, bitstream=None): + if last not in steps: + raise ValueError('Invalid last step.') + if first not in steps: + raise ValueError('Invalid first step.') + if steps.index(first) > steps.index(last): + raise ValueError('Invalid steps combination.') + if not which(self.tool['program']): + raise RuntimeError(f'{self.tool["program"]} not found.') + self._run(self.tool['command']) + + def prog(self, bitstream=None, position=1): """Program the FPGA - :param position: position of the device in the JTAG chain - :type position: str, optional :param bitstream: bitstream to be programmed :type bitstream: str, optional + :param position: position of the device in the JTAG chain + :type position: str, optional """ - raise NotImplementedError('Method is not implemented yet.') + if position not in range(1, 9): + raise ValueError('Invalid position.') + _ = bitstream + if not which(self.tool['program']): + raise RuntimeError(f'{self.tool["program"]} not found.') + self._run(self.tool['command']) def _run(self, command): self.logger.info('Running the underlying tool (%s)', datetime.now()) From b1c26827eb18119e15019d3922127643dfad6cdc Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Wed, 5 Jun 2024 20:30:37 -0300 Subject: [PATCH 070/254] Removed unused things --- pyfpga/ise.py | 64 ++++++------------------------------- pyfpga/libero.py | 20 +++++------- pyfpga/openflow.py | 24 ++++++-------- pyfpga/quartus.py | 27 ++++++---------- pyfpga/tool.py | 78 ++-------------------------------------------- pyfpga/vivado.py | 62 +++++------------------------------- 6 files changed, 45 insertions(+), 230 deletions(-) diff --git a/pyfpga/ise.py b/pyfpga/ise.py index 329e6e48..17fb4706 100644 --- a/pyfpga/ise.py +++ b/pyfpga/ise.py @@ -78,48 +78,17 @@ class Ise(Project): 'command': 'xtclsh ise.tcl', } -# _TOOL = 'ise' -# _EXTENSION = 'xise' -# _PART = 'xc7k160t-3-fbg484' -# _GEN_PROGRAM = 'xtclsh' -# _GEN_COMMAND = 'xtclsh ise.tcl' -# _TRF_PROGRAM = 'impact' -# _TRF_COMMAND = 'impact -batch ise-prog.impact' -# _BIT_EXT = ['bit'] + tool = { + 'def-part': 'xc7k160t-3-fbg484', + 'proj-ext': 'xise', + 'make-app': 'xtclsh', + 'make-opt': 'ise.tcl', + 'prog-app': 'impact', + 'prog-opt': '-batch ise-prog.impact', + 'binaries': ['bit'] + } + # _DEVTYPES = ['fpga', 'spi', 'bpi', 'detect', 'unlock'] -# _CLEAN = [ -# # directories -# 'iseconfig', '_ngo', 'xlnx_auto_0_xdb', '_xmsgs', 'xst', -# # files -# '*.bgn', '*.bld', '*.bit', -# '*.cmd_log', '*.csv', -# '*.drc', -# '*.gise', -# '*.html', -# '*.log', '*.lso', -# '*.map', '*.mrp', -# '*.ncd', '*.ngc', '*.ngd', '*.ngm', '*.ngr', -# '*.pad', '*.par', '*.pcf', '*.prj', '*.ptwx', -# '*.stx', '*.syr', -# '*.twr', '*.twx', -# '*.unroutes', '*.ut', -# '*.txt', -# '*.xml', '*.xpi', '*.xrpt', '*.xst', '*.xwbt', -# '_impact*', -# # pyfpga -# '*.impact', 'ise.tcl' -# ] - -# def __init__(self, project, frontend=None): -# super().__init__(project) -# if frontend == 'yosys': -# from fpga.tool.openflow import Openflow -# self.tool = Openflow( -# self.project, -# frontend='yosys', -# backend='ise' -# ) -# self.presynth = True # def set_part(self, part): # try: @@ -138,19 +107,6 @@ class Ise(Project): # self.part['package'] = package # self.part['speed'] = '-' + speed -# def generate(self, to_task, from_task, capture): -# if self.presynth and from_task in ['prj', 'syn']: -# self.tool.set_part(self.part['name']) -# self.tool.set_top(self.top) -# self.tool.paths = self.paths -# self.tool.files['vhdl'] = self.files['vhdl'] -# self.tool.files['verilog'] = self.files['verilog'] -# self.tool.params = self.params -# output1 = self.tool.generate('syn', 'prj', capture) -# output2 = super().generate(to_task, from_task, capture) -# return str(output1) + str(output2) -# return super().generate(to_task, from_task, capture) - # def transfer(self, devtype, position, part, width, capture): # super().transfer(devtype, position, part, width, capture) # temp = _TEMPLATES[devtype] diff --git a/pyfpga/libero.py b/pyfpga/libero.py index 5513698e..966ac5d9 100644 --- a/pyfpga/libero.py +++ b/pyfpga/libero.py @@ -38,18 +38,14 @@ class Libero(Project): 'command': 'libero SCRIPT:libero.tcl', } -# _TOOL = 'libero' -# _EXTENSION = 'prjx' -# _PART = 'mpf100t-1-fcg484' -# _GEN_PROGRAM = 'libero' -# _GEN_COMMAND = 'libero SCRIPT:libero.tcl' -# _DEVTYPES = ['fpga'] -# _CLEAN = [ -# # directories -# 'libero', -# # pyfpga -# 'libero.tcl' -# ] + tool = { + 'def-part': 'mpf100t-1-fcg484', + 'proj-ext': 'prjx', + 'make-app': 'libero', + 'make-opt': 'SCRIPT:libero.tcl', + 'prog-app': '', + 'prog-opt': '' + } # def set_part(self, part): # try: diff --git a/pyfpga/openflow.py b/pyfpga/openflow.py index b010ae40..f8a1150b 100644 --- a/pyfpga/openflow.py +++ b/pyfpga/openflow.py @@ -22,21 +22,15 @@ class Openflow(Project): 'command': 'bash openflow.sh', } -# _TOOL = 'openflow' -# _PART = 'hx8k-ct256' -# _GEN_PROGRAM = 'docker' -# _GEN_COMMAND = 'bash openflow.sh' -# _TRF_PROGRAM = 'docker' -# _TRF_COMMAND = 'bash openprog.sh' -# _BIT_EXT = ['bit'] -# _DEVTYPES = ['fpga'] -# _CLEAN = [ -# # files -# '*.asc', '*.bit', '*.cf', '*.config', '*.edif', '*.json', '*.rpt', -# '*.svf', -# # pyfpga -# '*.sh' -# ] + tool = { + 'def-part': 'hx8k-ct256', + 'proj-ext': '', + 'make-app': 'docker', + 'make-cmd': 'bash openflow.sh', + 'prog-app': 'docker', + 'prog-cmd': 'bash openprog.sh', + 'binaries': ['bit'] + } # def __init__(self, project, frontend='yosys', backend='nextpnr'): # # The valid frontends are be ghdl and yosys diff --git a/pyfpga/quartus.py b/pyfpga/quartus.py index 9c6c861e..064af33b 100644 --- a/pyfpga/quartus.py +++ b/pyfpga/quartus.py @@ -22,24 +22,15 @@ class Quartus(Project): 'command': 'quartus_sh --script quartus.tcl', } -# _TOOL = 'quartus' -# _EXTENSION = 'qpf' -# _PART = '10cl120zf780i8g' -# _GEN_PROGRAM = 'quartus_sh' -# _GEN_COMMAND = 'quartus_sh --script quartus.tcl' -# _TRF_PROGRAM = 'quartus_pgm' -# _TRF_COMMAND = 'quartus_pgm -c %s --mode jtag -o "p;%s@%s"' -# _BIT_EXT = ['sof', 'pof'] -# _DEVTYPES = ['fpga', 'detect'] -# _CLEAN = [ -# # directories -# 'db', 'incremental_db', 'output_files', -# # files -# '*.done', '*.jdi', '*.log', '*.pin', '*.pof', '*.qws', '*.rpt', -# '*.smsg', '*.sld', '*.sof', '*.sop', '*.summary', '*.txt', -# # pyfpga -# 'quartus.tcl' -# ] + tool = { + 'def-part': '10cl120zf780i8g', + 'proj-ext': 'qpf', + 'make-app': 'quartus_sh', + 'make-opt': '--script quartus.tcl', + 'prog-app': 'quartus_pgm', + 'prog-opt': '-c %s --mode jtag -o "p;%s@%s"', + 'binaries': ['sof', 'pof'] + } # def transfer(self, devtype, position, part, width, capture): # super().transfer(devtype, position, part, width, capture) diff --git a/pyfpga/tool.py b/pyfpga/tool.py index 19a756d4..30309ad0 100644 --- a/pyfpga/tool.py +++ b/pyfpga/tool.py @@ -4,17 +4,10 @@ # SPDX-License-Identifier: GPL-3.0-or-later # -"""fpga.tool - +""" Defines the interface to be inherited to support a tool. """ - -FILETYPES = ['verilog', 'vhdl', 'constraint', 'design'] -MEMWIDTHS = [1, 2, 4, 8, 16, 32] -PHASES = ['prefile', 'project', 'preflow', 'postsyn', 'postpar', 'postbit'] -TASKS = ['prj', 'syn', 'par', 'bit'] - # def tcl_path(path): # """Returns a Tcl suitable path.""" # return path.replace(os.path.sep, "/") @@ -23,64 +16,7 @@ class Tool: - """Tool interface. - - It is the basic interface for tool implementations. - """ - -# # Following variables are set in each inheritance (if employed) -# _TOOL = None # tool name -# _EXTENSION = None # project file extension -# _PART = None # default device part name -# _GEN_PROGRAM = None # program used when generate is executed -# _GEN_COMMAND = None # command to run when generate is executed -# _TRF_PROGRAM = None # program used when transfer is executed -# _TRF_COMMAND = None # command to run when transfer is executed -# _BIT_EXT = [] # Supported BITstream EXTensions -# _DEVTYPES = [] # Supported DEVice TYPES -# _CLEAN = [] # Files to be CLEAN - -# def __init__(self, project): -# """Initializes the attributes of the class.""" -# self.bitstream = None -# self.cmds = { -# 'prefile': [], -# 'project': [], -# 'preflow': [], -# 'postsyn': [], -# 'postpar': [], -# 'postbit': [] -# } -# self.files = { -# 'vhdl': [], -# 'verilog': [], -# 'constraint': [], -# 'design': [] -# } -# self.params = [] -# self.part = { -# 'name': 'UNSET', -# 'family': 'UNSET', -# 'device': 'UNSET', -# 'package': 'UNSET', -# 'speed': 'UNSET' -# } -# self.paths = [] -# self.presynth = False -# self.project = self._TOOL if project is None else project -# self.set_part(self._PART) -# self.set_top('UNDEFINED') -# self._configure() - -# def _configure(self): -# """Configures the underlying tools.""" -# filename = '.pyfpga.yml' -# self.configs = {} -# if os.path.exists(filename): -# with open(filename, 'r', encoding='utf-8') as file: -# data = safe_load(file) -# if self._TOOL in data: -# self.configs = data[self._TOOL] + """Tool interface.""" # def _create_gen_script(self, tasks): # """Create the script for generate execution.""" @@ -167,13 +103,3 @@ class Tool: # if len(bitstream) == 0: # raise FileNotFoundError('bitStream not found') # self.bitstream = bitstream[0] - -# def clean(self): -# """Clean the generated project files.""" -# for path in self._CLEAN: -# elements = glob(path) -# for element in elements: -# if os.path.isfile(element): -# os.remove(element) -# else: -# rmtree(element) diff --git a/pyfpga/vivado.py b/pyfpga/vivado.py index 46008024..c5b36cdb 100644 --- a/pyfpga/vivado.py +++ b/pyfpga/vivado.py @@ -34,59 +34,11 @@ class Vivado(Project): """Class to support Vivado.""" tool = { - 'program': 'vivado', - 'command': 'vivado -mode batch -notrace -quiet -source vivado.tcl', + 'def-part': 'xc7k160t-3-fbg484', + 'proj-ext': 'xpr', + 'make-app': 'vivado', + 'make-opt': '-mode batch -notrace -quiet -source vivado.tcl', + 'prog-app': 'vivado', + 'prog-opt': '-mode batch -notrace -quiet -source vivado-prog.tcl', + 'binaries': ['bit'] } - -# _TOOL = 'vivado' -# _EXTENSION = 'xpr' -# _PART = 'xc7k160t-3-fbg484' -# _GEN_PROGRAM = 'vivado' -# _GEN_COMMAND = 'vivado -mode batch -notrace -quiet -source vivado.tcl' -# _TRF_PROGRAM = 'vivado' -# _TRF_COMMAND = -# 'vivado -mode batch -notrace -quiet -source vivado-prog.tcl' -# _BIT_EXT = ['bit'] -# _DEVTYPES = ['fpga', 'detect'] -# _CLEAN = [ -# # directories -# '*.cache', '*.hw', '*.ip_user_files', '*.runs', '*.sim', '.Xil', -# # files -# '*.bit', '*.jou', '*.log', '*.rpt', 'vivado_*.zip', -# # pyfpga -# 'vivado.tcl', 'vivado-prog.tcl' -# ] - -# def __init__(self, project, frontend=None): -# super().__init__(project) -# if frontend == 'yosys': -# from fpga.tool.openflow import Openflow -# self.tool = Openflow( -# self.project, -# frontend='yosys', -# backend='vivado' -# ) -# self.presynth = True - -# def generate(self, to_task, from_task, capture): -# if self.presynth and from_task in ['prj', 'syn']: -# self.tool.set_part(self.part['name']) -# self.tool.set_top(self.top) -# self.tool.paths = self.paths -# self.tool.files['vhdl'] = self.files['vhdl'] -# self.tool.files['verilog'] = self.files['verilog'] -# self.tool.params = self.params -# output1 = self.tool.generate('syn', 'prj', capture) -# self.set_top(self.project) -# output2 = super().generate(to_task, from_task, capture) -# return str(output1) + str(output2) -# return super().generate(to_task, from_task, capture) - -# def transfer(self, devtype, position, part, width, capture): -# super().transfer(devtype, position, part, width, capture) -# temp = _TEMPLATES[devtype] -# if devtype != 'detect': -# temp = temp.replace('#BITSTREAM#', self.bitstream) -# with open('vivado-prog.tcl', 'w', encoding='utf-8') as file: -# file.write(temp) -# return run(self._TRF_COMMAND, capture) From 6abcbd5d3a76cc2093f82388be3b19c06863d8fb Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Wed, 5 Jun 2024 21:21:09 -0300 Subject: [PATCH 071/254] Moved things from Python scripts to templates --- pyfpga/ise.py | 55 ------------------------- pyfpga/libero.py | 15 ------- pyfpga/templates/ise-prog.jinja | 60 ++++++++++++++++++++++++++++ pyfpga/templates/ise.jinja | 1 - pyfpga/templates/libero-prog.jinja | 20 ++++++++++ pyfpga/templates/libero.jinja | 1 - pyfpga/templates/openflow-prog.jinja | 1 - pyfpga/templates/openflow.jinja | 2 - pyfpga/templates/quartus-prog.jinja | 7 ++++ pyfpga/templates/quartus.jinja | 1 - pyfpga/templates/vivado-prog.jinja | 13 ++++++ pyfpga/templates/vivado.jinja | 1 - pyfpga/vivado.py | 17 -------- 13 files changed, 100 insertions(+), 94 deletions(-) create mode 100644 pyfpga/templates/ise-prog.jinja create mode 100644 pyfpga/templates/libero-prog.jinja create mode 100644 pyfpga/templates/quartus-prog.jinja create mode 100644 pyfpga/templates/vivado-prog.jinja diff --git a/pyfpga/ise.py b/pyfpga/ise.py index 17fb4706..cd39634e 100644 --- a/pyfpga/ise.py +++ b/pyfpga/ise.py @@ -12,61 +12,6 @@ from pyfpga.project import Project -_TEMPLATES = { - 'fpga': """setMode -bs -setCable -port auto -Identify -inferir -assignFile -p #POSITION# -file #BITSTREAM# -Program -p #POSITION# - -quit -""", - 'spi': """setMode -pff -addConfigDevice -name #NAME# -path . -setSubmode -pffspi -addDesign -version 0 -name 0 -addDeviceChain -index 0 -addDevice -p 1 -file #BITSTREAM# -generate -generic - -setMode -bs -setCable -port auto -Identify -attachflash -position #POSITION# -spi #NAME# -assignfiletoattachedflash -position #POSITION# -file ./#NAME#.mcs -Program -p #POSITION# -dataWidth #WIDTH# -spionly -e -v -loadfpga - -quit -""", - 'bpi': """setMode -pff -addConfigDevice -name #NAME# -path . -setSubmode -pffbpi -addDesign -version 0 -name 0 -addDeviceChain -index 0 -setAttribute -configdevice -attr flashDataWidth -value #WIDTH# -addDevice -p 1 -file #BITSTREAM# -generate -generic - -setMode -bs -setCable -port auto -Identify -attachflash -position #POSITION# -bpi #NAME# -assignfiletoattachedflash -position #POSITION# -file ./#NAME#.mcs -Program -p #POSITION# -dataWidth #WIDTH# \ --rs1 NONE -rs0 NONE -bpionly -e -v -loadfpga - -quit -""", - 'detect': """setMode -bs -setCable -port auto -Identify -inferir -quit -""", - 'unlock': """cleancablelock -quit -""" -} - # pylint: disable=too-few-public-methods diff --git a/pyfpga/libero.py b/pyfpga/libero.py index 966ac5d9..7061f040 100644 --- a/pyfpga/libero.py +++ b/pyfpga/libero.py @@ -12,21 +12,6 @@ from pyfpga.project import Project -_TEMPLATES = { - 'fpga': """\ -""", - 'detect': """\ -""" -} - -# open_project -file {$TEMPDIR/libero.prjx} -# run_tool -name {CONFIGURE_CHAIN} -script {$TEMPDIR/flashpro.tcl} -# run_tool -name {PROGRAMDEVICE} - -# set flashpro_programmer "configure_flashpro5_prg -vpump {ON} \ -# -clk_mode {free_running_clk} -programming_method {spi_slave} \ -# -force_freq {OFF} -freq {4000000}" - # pylint: disable=too-few-public-methods diff --git a/pyfpga/templates/ise-prog.jinja b/pyfpga/templates/ise-prog.jinja new file mode 100644 index 00000000..4c18a072 --- /dev/null +++ b/pyfpga/templates/ise-prog.jinja @@ -0,0 +1,60 @@ +# +# Copyright (C) 2015-2024 Rodrigo A. Melo +# +# SPDX-License-Identifier: GPL-3.0-or-later +# + +_TEMPLATES = { + 'fpga': """setMode -bs +setCable -port auto +Identify -inferir +assignFile -p #POSITION# -file #BITSTREAM# +Program -p #POSITION# + +quit +""", + 'spi': """setMode -pff +addConfigDevice -name #NAME# -path . +setSubmode -pffspi +addDesign -version 0 -name 0 +addDeviceChain -index 0 +addDevice -p 1 -file #BITSTREAM# +generate -generic + +setMode -bs +setCable -port auto +Identify +attachflash -position #POSITION# -spi #NAME# +assignfiletoattachedflash -position #POSITION# -file ./#NAME#.mcs +Program -p #POSITION# -dataWidth #WIDTH# -spionly -e -v -loadfpga + +quit +""", + 'bpi': """setMode -pff +addConfigDevice -name #NAME# -path . +setSubmode -pffbpi +addDesign -version 0 -name 0 +addDeviceChain -index 0 +setAttribute -configdevice -attr flashDataWidth -value #WIDTH# +addDevice -p 1 -file #BITSTREAM# +generate -generic + +setMode -bs +setCable -port auto +Identify +attachflash -position #POSITION# -bpi #NAME# +assignfiletoattachedflash -position #POSITION# -file ./#NAME#.mcs +Program -p #POSITION# -dataWidth #WIDTH# \ +-rs1 NONE -rs0 NONE -bpionly -e -v -loadfpga + +quit +""", + 'detect': """setMode -bs +setCable -port auto +Identify -inferir +quit +""", + 'unlock': """cleancablelock +quit +""" +} diff --git a/pyfpga/templates/ise.jinja b/pyfpga/templates/ise.jinja index 08678558..c70efad6 100644 --- a/pyfpga/templates/ise.jinja +++ b/pyfpga/templates/ise.jinja @@ -1,5 +1,4 @@ # -# PyFPGA # Copyright (C) 2015-2024 Rodrigo A. Melo # # SPDX-License-Identifier: GPL-3.0-or-later diff --git a/pyfpga/templates/libero-prog.jinja b/pyfpga/templates/libero-prog.jinja new file mode 100644 index 00000000..ed9effe2 --- /dev/null +++ b/pyfpga/templates/libero-prog.jinja @@ -0,0 +1,20 @@ +# +# Copyright (C) 2015-2024 Rodrigo A. Melo +# +# SPDX-License-Identifier: GPL-3.0-or-later +# + +_TEMPLATES = { + 'fpga': """\ +""", + 'detect': """\ +""" +} + +# open_project -file {$TEMPDIR/libero.prjx} +# run_tool -name {CONFIGURE_CHAIN} -script {$TEMPDIR/flashpro.tcl} +# run_tool -name {PROGRAMDEVICE} + +# set flashpro_programmer "configure_flashpro5_prg -vpump {ON} \ +# -clk_mode {free_running_clk} -programming_method {spi_slave} \ +# -force_freq {OFF} -freq {4000000}" diff --git a/pyfpga/templates/libero.jinja b/pyfpga/templates/libero.jinja index 35ec3f52..1b230c2c 100644 --- a/pyfpga/templates/libero.jinja +++ b/pyfpga/templates/libero.jinja @@ -1,5 +1,4 @@ # -# PyFPGA # Copyright (C) 2015-2024 Rodrigo A. Melo # # SPDX-License-Identifier: GPL-3.0-or-later diff --git a/pyfpga/templates/openflow-prog.jinja b/pyfpga/templates/openflow-prog.jinja index 3be40f53..027eb388 100644 --- a/pyfpga/templates/openflow-prog.jinja +++ b/pyfpga/templates/openflow-prog.jinja @@ -1,5 +1,4 @@ # -# PyFPGA # Copyright (C) 2020-2024 Rodrigo A. Melo # # SPDX-License-Identifier: GPL-3.0-or-later diff --git a/pyfpga/templates/openflow.jinja b/pyfpga/templates/openflow.jinja index f50b0786..d3a0a703 100644 --- a/pyfpga/templates/openflow.jinja +++ b/pyfpga/templates/openflow.jinja @@ -1,6 +1,4 @@ -#!/bin/bash # -# PyFPGA # Copyright (C) 2020-2024 Rodrigo A. Melo # # SPDX-License-Identifier: GPL-3.0-or-later diff --git a/pyfpga/templates/quartus-prog.jinja b/pyfpga/templates/quartus-prog.jinja new file mode 100644 index 00000000..45ee765b --- /dev/null +++ b/pyfpga/templates/quartus-prog.jinja @@ -0,0 +1,7 @@ +# +# Copyright (C) 2015-2024 Rodrigo A. Melo +# +# SPDX-License-Identifier: GPL-3.0-or-later +# + +{{ COMMAND }} -c %s --mode jtag -o "p;{{ BITSTREAM }}@{{ POSITION }}" diff --git a/pyfpga/templates/quartus.jinja b/pyfpga/templates/quartus.jinja index 76c29fc5..cf660a8d 100644 --- a/pyfpga/templates/quartus.jinja +++ b/pyfpga/templates/quartus.jinja @@ -1,5 +1,4 @@ # -# PyFPGA # Copyright (C) 2015-2024 Rodrigo A. Melo # # SPDX-License-Identifier: GPL-3.0-or-later diff --git a/pyfpga/templates/vivado-prog.jinja b/pyfpga/templates/vivado-prog.jinja new file mode 100644 index 00000000..7b148b22 --- /dev/null +++ b/pyfpga/templates/vivado-prog.jinja @@ -0,0 +1,13 @@ +# +# Copyright (C) 2015-2024 Rodrigo A. Melo +# +# SPDX-License-Identifier: GPL-3.0-or-later +# + +if { [ catch { open_hw_manager } ] } { open_hw } +connect_hw_server +open_hw_target +puts [get_hw_devices] +set obj [lindex [get_hw_devices [current_hw_device]] 0] +set_property PROGRAM.FILE {{ BITSTREAM }} $obj +program_hw_devices $obj diff --git a/pyfpga/templates/vivado.jinja b/pyfpga/templates/vivado.jinja index 16bed243..5c4e6049 100644 --- a/pyfpga/templates/vivado.jinja +++ b/pyfpga/templates/vivado.jinja @@ -1,5 +1,4 @@ # -# PyFPGA # Copyright (C) 2015-2024 Rodrigo A. Melo # # SPDX-License-Identifier: GPL-3.0-or-later diff --git a/pyfpga/vivado.py b/pyfpga/vivado.py index c5b36cdb..15dfa008 100644 --- a/pyfpga/vivado.py +++ b/pyfpga/vivado.py @@ -10,23 +10,6 @@ from pyfpga.project import Project -_TEMPLATES = { - 'fpga': """\ -if { [ catch { open_hw_manager } ] } { open_hw } -connect_hw_server -open_hw_target -set obj [lindex [get_hw_devices [current_hw_device]] 0] -set_property PROGRAM.FILE #BITSTREAM# $obj -program_hw_devices $obj -""", - 'detect': """\ -if { [ catch { open_hw_manager } ] } { open_hw } -connect_hw_server -open_hw_target -puts [get_hw_devices] -""" -} - # pylint: disable=too-few-public-methods From 713bbc75ecfd5589ec2fc1f6a01142ff1e653cfe Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Thu, 6 Jun 2024 23:18:17 -0300 Subject: [PATCH 072/254] Added a method to create files based on templates --- pyfpga/project.py | 42 +++++++++++++++++++++++++++++++----------- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/pyfpga/project.py b/pyfpga/project.py index 5c054dd3..a6193683 100644 --- a/pyfpga/project.py +++ b/pyfpga/project.py @@ -17,6 +17,7 @@ from pathlib import Path from shutil import which from time import time +from jinja2 import Environment, FileSystemLoader class Project: @@ -37,7 +38,6 @@ def __init__(self, name=None, odir='results'): self.data = {} self.name = name self.odir = odir - # self.odir.mkdir(parents=True, exist_ok=True) # logging config self.logger = logging.getLogger(self.__class__.__name__) self.logger.setLevel(logging.INFO) @@ -210,9 +210,10 @@ def make(self, last='bit', first='prj'): raise ValueError('Invalid first step.') if steps.index(first) > steps.index(last): raise ValueError('Invalid steps combination.') - if not which(self.tool['program']): - raise RuntimeError(f'{self.tool["program"]} not found.') - self._run(self.tool['command']) + self._make_prepare() + if not which(self.tool['make-app']): + raise RuntimeError(f'{self.tool["make-app"]} not found.') + self._run(self.tool['make-cmd']) def prog(self, bitstream=None, position=1): """Program the FPGA @@ -225,9 +226,28 @@ def prog(self, bitstream=None, position=1): if position not in range(1, 9): raise ValueError('Invalid position.') _ = bitstream - if not which(self.tool['program']): - raise RuntimeError(f'{self.tool["program"]} not found.') - self._run(self.tool['command']) + self._prog_prepare() + if not which(self.tool['prog-app']): + raise RuntimeError(f'{self.tool["prog-app"]} not found.') + self._run(self.tool['prog-cmd']) + + def _make_prepare(self): + raise NotImplementedError('Tool-dependent') + + def _prog_prepare(self): + raise NotImplementedError('Tool-dependent') + + def _create_file(self, basename, extension, context): + tempdir = Path(__file__).parent.joinpath('templates') + jinja_file_loader = FileSystemLoader(str(tempdir)) + jinja_env = Environment(loader=jinja_file_loader) + jinja_template = jinja_env.get_template(f'{basename}.jinja') + content = jinja_template.render(context) + directory = Path(self.odir) + directory.mkdir(parents=True, exist_ok=True) + filename = f'{basename}.{extension}' + with open(directory / filename, 'w', encoding='utf-8') as file: + file.write(content) def _run(self, command): self.logger.info('Running the underlying tool (%s)', datetime.now()) @@ -237,14 +257,14 @@ def _run(self, command): start = time.time() try: os.chdir(new_dir) - with open('run.log', 'w', encoding='utf-8') as logfile: + with open('run.log', 'w', encoding='utf-8') as file: subprocess.run( command, shell=True, check=True, text=True, - stdout=logfile, stderr=subprocess.STDOUT + stdout=file, stderr=subprocess.STDOUT ) except subprocess.CalledProcessError: - with open('run.log', 'r', encoding='utf-8') as logfile: - lines = logfile.readlines() + with open('run.log', 'r', encoding='utf-8') as file: + lines = file.readlines() last_lines = lines[-10:] if len(lines) >= 10 else lines for line in last_lines: self.logger.error(line.strip()) From e506e2fb858186d7e54ac23009f486797885c6fc Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Thu, 6 Jun 2024 23:18:40 -0300 Subject: [PATCH 073/254] Tool-specific data reorganized --- pyfpga/ise.py | 27 +++++++++++---------------- pyfpga/libero.py | 26 +++++++++++--------------- pyfpga/openflow.py | 28 +++++++++++----------------- pyfpga/quartus.py | 31 ++++++++++++++----------------- pyfpga/vivado.py | 29 +++++++++++++++++------------ 5 files changed, 64 insertions(+), 77 deletions(-) diff --git a/pyfpga/ise.py b/pyfpga/ise.py index cd39634e..61fee4be 100644 --- a/pyfpga/ise.py +++ b/pyfpga/ise.py @@ -12,26 +12,22 @@ from pyfpga.project import Project -# pylint: disable=too-few-public-methods - class Ise(Project): """Class to support ISE projects.""" - tool = { - 'program': 'xtclsh', - 'command': 'xtclsh ise.tcl', - } + def __init__(self, name='ise', odir='results'): + super().__init__(name=name, odir=odir) + self.set_part('xc7k160t-3-fbg484') + + def _make_prepare(self): + self.tool['make-app'] = 'xtclsh' + self.tool['make-cmd'] = 'xtclsh ise.tcl' - tool = { - 'def-part': 'xc7k160t-3-fbg484', - 'proj-ext': 'xise', - 'make-app': 'xtclsh', - 'make-opt': 'ise.tcl', - 'prog-app': 'impact', - 'prog-opt': '-batch ise-prog.impact', - 'binaries': ['bit'] - } + def _prog_prepare(self): + # binaries = ['bit'] + self.tool['prog-app'] = 'impact' + self.tool['prog-cmd'] = 'impact -batch impact-prog' # _DEVTYPES = ['fpga', 'spi', 'bpi', 'detect', 'unlock'] @@ -64,7 +60,6 @@ class Ise(Project): # file.write(temp) # return run(self._TRF_COMMAND, capture) - # def get_family(part): # """Get the Family name from the specified part name.""" # part = part.lower() diff --git a/pyfpga/libero.py b/pyfpga/libero.py index 7061f040..1c842fb9 100644 --- a/pyfpga/libero.py +++ b/pyfpga/libero.py @@ -12,25 +12,22 @@ from pyfpga.project import Project -# pylint: disable=too-few-public-methods - class Libero(Project): """Class to support Libero.""" - tool = { - 'program': 'libero', - 'command': 'libero SCRIPT:libero.tcl', - } + def __init__(self, name='libero', odir='results'): + super().__init__(name=name, odir=odir) + self.set_part('mpf100t-1-fcg484') + + def _make_prepare(self): + self.tool['make-app'] = 'libero' + self.tool['make-cmd'] = 'libero SCRIPT:libero.tcl' - tool = { - 'def-part': 'mpf100t-1-fcg484', - 'proj-ext': 'prjx', - 'make-app': 'libero', - 'make-opt': 'SCRIPT:libero.tcl', - 'prog-app': '', - 'prog-opt': '' - } + def _prog_prepare(self): + # binaries = ['bit'] + self.tool['prog-app'] = '' + self.tool['prog-cmd'] = '' # def set_part(self, part): # try: @@ -55,7 +52,6 @@ class Libero(Project): # super().transfer(devtype, position, part, width, capture) # raise NotImplementedError('transfer(libero)') - # def get_family(part): # """Get the Family name from the specified part name.""" # part = part.lower() diff --git a/pyfpga/openflow.py b/pyfpga/openflow.py index f8a1150b..7ecbb9cd 100644 --- a/pyfpga/openflow.py +++ b/pyfpga/openflow.py @@ -8,29 +8,24 @@ Implements support for an Open Source development flow. """ -# import os from pyfpga.project import Project -# pylint: disable=too-few-public-methods - class Openflow(Project): """Class to support Openflow.""" - tool = { - 'program': 'docker', - 'command': 'bash openflow.sh', - } + def __init__(self, name='openflow', odir='results'): + super().__init__(name=name, odir=odir) + self.set_part('hx8k-ct256') + + def _make_prepare(self): + self.tool['make-app'] = 'docker' + self.tool['make-cmd'] = 'bash openflow.sh' - tool = { - 'def-part': 'hx8k-ct256', - 'proj-ext': '', - 'make-app': 'docker', - 'make-cmd': 'bash openflow.sh', - 'prog-app': 'docker', - 'prog-cmd': 'bash openprog.sh', - 'binaries': ['bit'] - } + def _prog_prepare(self): + # binaries = ['bit'] + self.tool['prog-app'] = 'docker' + self.tool['prog-cmd'] = 'bash openflow-prog.sh' # def __init__(self, project, frontend='yosys', backend='nextpnr'): # # The valid frontends are be ghdl and yosys @@ -178,7 +173,6 @@ class Openflow(Project): # file.write(text) # return run(self._TRF_COMMAND, capture) - # def get_family(part): # """Get the Family name from the specified part name.""" # part = part.lower() diff --git a/pyfpga/quartus.py b/pyfpga/quartus.py index 064af33b..55dd6434 100644 --- a/pyfpga/quartus.py +++ b/pyfpga/quartus.py @@ -9,28 +9,25 @@ """ # import re -# import subprocess from pyfpga.project import Project class Quartus(Project): - """Class to support Quartus.""" - - tool = { - 'program': 'quartus_sh', - 'command': 'quartus_sh --script quartus.tcl', - } - - tool = { - 'def-part': '10cl120zf780i8g', - 'proj-ext': 'qpf', - 'make-app': 'quartus_sh', - 'make-opt': '--script quartus.tcl', - 'prog-app': 'quartus_pgm', - 'prog-opt': '-c %s --mode jtag -o "p;%s@%s"', - 'binaries': ['sof', 'pof'] - } + """Class to support Quartus projects.""" + + def __init__(self, name='quartus', odir='results'): + super().__init__(name=name, odir=odir) + self.set_part('10cl120zf780i8g') + + def _make_prepare(self): + self.tool['make-app'] = 'quartus_sh' + self.tool['make-cmd'] = 'quartus_sh --script quartus.tcl' + + def _prog_prepare(self): + # binaries = ['sof', 'pof'] + self.tool['prog-app'] = 'quartus_pgm' + self.tool['prog-cmd'] = 'bash quartus-prog.sh' # def transfer(self, devtype, position, part, width, capture): # super().transfer(devtype, position, part, width, capture) diff --git a/pyfpga/vivado.py b/pyfpga/vivado.py index 15dfa008..4b53f7ca 100644 --- a/pyfpga/vivado.py +++ b/pyfpga/vivado.py @@ -10,18 +10,23 @@ from pyfpga.project import Project -# pylint: disable=too-few-public-methods - class Vivado(Project): - """Class to support Vivado.""" + """Class to support Vivado projects.""" + + def __init__(self, name='vivado', odir='results'): + super().__init__(name=name, odir=odir) + self.set_part('xc7k160t-3-fbg484') + + def _make_prepare(self): + self.tool['make-app'] = 'vivado' + self.tool['make-cmd'] = ( + 'vivado -mode batch -notrace -quiet -source vivado.tcl' + ) - tool = { - 'def-part': 'xc7k160t-3-fbg484', - 'proj-ext': 'xpr', - 'make-app': 'vivado', - 'make-opt': '-mode batch -notrace -quiet -source vivado.tcl', - 'prog-app': 'vivado', - 'prog-opt': '-mode batch -notrace -quiet -source vivado-prog.tcl', - 'binaries': ['bit'] - } + def _prog_prepare(self): + # binaries = ['bit'] + self.tool['prog-app'] = 'vivado' + self.tool['prog-cmd'] = ( + 'vivado -mode batch -notrace -quiet -source vivado-prog.tcl' + ) From bebd9e892e3011a468342a9ff7157f63f238f795 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Fri, 7 Jun 2024 00:36:30 -0300 Subject: [PATCH 074/254] Ignored results --- .gitignore | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 2e7693ab..964a59fd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,5 @@ *build ignore* +results venv __pycache__ -*.egg-info -_theme From f496a2a0bb9a86be5d64c82d61734115430144ad Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Fri, 7 Jun 2024 00:37:08 -0300 Subject: [PATCH 075/254] Removed unused imports --- tests/test_data.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tests/test_data.py b/tests/test_data.py index 4c363de5..0994d9db 100644 --- a/tests/test_data.py +++ b/tests/test_data.py @@ -1,6 +1,3 @@ -import os -import pytest - from pathlib import Path from pyfpga.project import Project @@ -57,7 +54,7 @@ } -def test_names(): +def test_data(): prj = Project() prj.set_part('PARTNAME') prj.set_top('TOPNAME') From cf81ebfdf0d81a057b771da2f53039635d28bcd8 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Mon, 10 Jun 2024 20:25:11 -0300 Subject: [PATCH 076/254] Modified how to deal with steps; started the Vivado implementation --- pyfpga/ise.py | 2 +- pyfpga/libero.py | 2 +- pyfpga/openflow.py | 2 +- pyfpga/project.py | 15 ++++--- pyfpga/quartus.py | 2 +- pyfpga/templates/vivado.jinja | 73 +++++++++++++++-------------------- pyfpga/vivado.py | 22 ++++++++--- tests/test_part.py | 44 ++++++++++----------- tests/test_vivado.py | 22 +++++++++++ 9 files changed, 104 insertions(+), 80 deletions(-) create mode 100644 tests/test_vivado.py diff --git a/pyfpga/ise.py b/pyfpga/ise.py index 61fee4be..72e577bc 100644 --- a/pyfpga/ise.py +++ b/pyfpga/ise.py @@ -20,7 +20,7 @@ def __init__(self, name='ise', odir='results'): super().__init__(name=name, odir=odir) self.set_part('xc7k160t-3-fbg484') - def _make_prepare(self): + def _make_prepare(self, steps): self.tool['make-app'] = 'xtclsh' self.tool['make-cmd'] = 'xtclsh ise.tcl' diff --git a/pyfpga/libero.py b/pyfpga/libero.py index 1c842fb9..35d4d89f 100644 --- a/pyfpga/libero.py +++ b/pyfpga/libero.py @@ -20,7 +20,7 @@ def __init__(self, name='libero', odir='results'): super().__init__(name=name, odir=odir) self.set_part('mpf100t-1-fcg484') - def _make_prepare(self): + def _make_prepare(self, steps): self.tool['make-app'] = 'libero' self.tool['make-cmd'] = 'libero SCRIPT:libero.tcl' diff --git a/pyfpga/openflow.py b/pyfpga/openflow.py index 7ecbb9cd..cb756727 100644 --- a/pyfpga/openflow.py +++ b/pyfpga/openflow.py @@ -18,7 +18,7 @@ def __init__(self, name='openflow', odir='results'): super().__init__(name=name, odir=odir) self.set_part('hx8k-ct256') - def _make_prepare(self): + def _make_prepare(self, steps): self.tool['make-app'] = 'docker' self.tool['make-cmd'] = 'bash openflow.sh' diff --git a/pyfpga/project.py b/pyfpga/project.py index a6193683..e71b550a 100644 --- a/pyfpga/project.py +++ b/pyfpga/project.py @@ -193,7 +193,7 @@ def add_hook(self, stage, hook): raise ValueError('Invalid stage.') self.data.setdefault('hooks', {}).setdefault(stage, []).append(hook) - def make(self, last='bit', first='prj'): + def make(self, last='bit', first='cfg'): """Run the underlying tool. :param last: last step @@ -208,9 +208,12 @@ def make(self, last='bit', first='prj'): raise ValueError('Invalid last step.') if first not in steps: raise ValueError('Invalid first step.') - if steps.index(first) > steps.index(last): + first_index = steps.index(first) + last_index = steps.index(last) + if first_index > last_index: raise ValueError('Invalid steps combination.') - self._make_prepare() + selected_steps = steps[first_index:last_index + 1] + self._make_prepare([step.upper() for step in selected_steps]) if not which(self.tool['make-app']): raise RuntimeError(f'{self.tool["make-app"]} not found.') self._run(self.tool['make-cmd']) @@ -231,7 +234,7 @@ def prog(self, bitstream=None, position=1): raise RuntimeError(f'{self.tool["prog-app"]} not found.') self._run(self.tool['prog-cmd']) - def _make_prepare(self): + def _make_prepare(self, steps): raise NotImplementedError('Tool-dependent') def _prog_prepare(self): @@ -254,7 +257,7 @@ def _run(self, command): run_error = 0 old_dir = Path.cwd() new_dir = Path(self.odir) - start = time.time() + start = time() try: os.chdir(new_dir) with open('run.log', 'w', encoding='utf-8') as file: @@ -271,7 +274,7 @@ def _run(self, command): run_error = 1 finally: os.chdir(old_dir) - end = time.time() + end = time() self.logger.info('Done (%s)', datetime.now()) elapsed = end - start self.logger.info( diff --git a/pyfpga/quartus.py b/pyfpga/quartus.py index 55dd6434..c635ffa5 100644 --- a/pyfpga/quartus.py +++ b/pyfpga/quartus.py @@ -20,7 +20,7 @@ def __init__(self, name='quartus', odir='results'): super().__init__(name=name, odir=odir) self.set_part('10cl120zf780i8g') - def _make_prepare(self): + def _make_prepare(self, steps): self.tool['make-app'] = 'quartus_sh' self.tool['make-cmd'] = 'quartus_sh --script quartus.tcl' diff --git a/pyfpga/templates/vivado.jinja b/pyfpga/templates/vivado.jinja index 5c4e6049..cfc6734b 100644 --- a/pyfpga/templates/vivado.jinja +++ b/pyfpga/templates/vivado.jinja @@ -6,43 +6,37 @@ set PROJECT {{ PROJECT }} set PART {{ PART }} -set FAMILY {{ FAMILY }} -set DEVICE {{ DEVICE }} -set PACKAGE {{ PACKAGE }} -set SPEED {{ SPEED }} set TOP {{ TOP }} -set PARAMS [list {{ PARAMS }}] - -proc fpga_file {FILE {LIBRARY "work"}} { - set message "adding the file '$FILE'" - if { $LIBRARY != "work" } { append message " (into the VHDL library '$LIBRARY')" } - regexp -nocase {\.(\w*)$} $FILE -> ext - if { $ext == "tcl" } { - source $FILE - return - } - if { $LIBRARY != "work" } { - add_files $FILE - set_property library $LIBRARY [get_files $FILE] - } else { - add_files $FILE - } -} - -proc fpga_include {PATH} { - lappend INCLUDED $PATH - # Verilog Included Files are NOT added - set_property "include_dirs" $INCLUDED [current_fileset] -} - -proc fpga_params {} { - if { [llength $PARAMS] == 0 } { return } - set assigns [list] - foreach PARAM $PARAMS { lappend assigns [join $PARAM "="] } - set obj [get_filesets sources_1] - set_property "generic" "[join $assigns]" -objects $obj -} +# proc fpga_file {FILE {LIBRARY "work"}} { +# set message "adding the file '$FILE'" +# if { $LIBRARY != "work" } { append message " (into the VHDL library '$LIBRARY')" } +# regexp -nocase {\.(\w*)$} $FILE -> ext +# if { $ext == "tcl" } { +# source $FILE +# return +# } +# if { $LIBRARY != "work" } { +# add_files $FILE +# set_property library $LIBRARY [get_files $FILE] +# } else { +# add_files $FILE +# } +# } + +# proc fpga_include {PATH} { +# lappend INCLUDED $PATH +# # Verilog Included Files are NOT added +# set_property "include_dirs" $INCLUDED [current_fileset] +# } + +# proc fpga_params {} { +# if { [llength $PARAMS] == 0 } { return } +# set assigns [list] +# foreach PARAM $PARAMS { lappend assigns [join $PARAM "="] } +# set obj [get_filesets sources_1] +# set_property "generic" "[join $assigns]" -objects $obj +# } #--[ Project configuration ]--------------------------------------------------- @@ -88,13 +82,11 @@ open_project $PROJECT {% if SYN %} {{ PRESYN }} -{% if PRESYNTH %} -set_property design_mode GateLvl [current_fileset] -{% else %} +# PRESYNTH +# set_property design_mode GateLvl [current_fileset] reset_run synth_1 launch_runs synth_1 wait_on_run synth_1 -{% endif %} {{ POSTSYN }} {% endif %} @@ -102,9 +94,6 @@ wait_on_run synth_1 {% if PAR %} {{ PREPAR }} -{% if not PRESYNTH %} -open_run synth_1 -{% endif %} reset_run impl_1 launch_runs impl_1 wait_on_run impl_1 diff --git a/pyfpga/vivado.py b/pyfpga/vivado.py index 4b53f7ca..7c7c88b8 100644 --- a/pyfpga/vivado.py +++ b/pyfpga/vivado.py @@ -14,15 +14,27 @@ class Vivado(Project): """Class to support Vivado projects.""" - def __init__(self, name='vivado', odir='results'): - super().__init__(name=name, odir=odir) - self.set_part('xc7k160t-3-fbg484') - - def _make_prepare(self): + def _make_prepare(self, steps): self.tool['make-app'] = 'vivado' self.tool['make-cmd'] = ( 'vivado -mode batch -notrace -quiet -source vivado.tcl' ) + context = { + 'PROJECT': self.name or 'vivado', + 'PART': self.data.get('part', 'xc7k160t-3-fbg484'), + 'TOP': self.data.get('top', 'top') + } + for step in steps: + context[step] = 1 + if 'hooks' in self.data: + for stage in self.data['hooks']: + context[stage.upper()] = '\n'.join(self.data['hooks'][stage]) + # FILES + # DEFINES + # INCLUDES + # PARAMS + # ARCH + self._create_file('vivado', 'tcl', context) def _prog_prepare(self): # binaries = ['bit'] diff --git a/tests/test_part.py b/tests/test_part.py index a1c2861a..5cee3e71 100644 --- a/tests/test_part.py +++ b/tests/test_part.py @@ -1,29 +1,27 @@ -import pytest +from pyfpga.project import Project -from fpga.project import Project +# def get_part(prj): +# return prj.get_configs()['part'].lower() -def get_part(prj): - return prj.get_configs()['part'].lower() +# def test_ise(): +# prj = Project('ise') +# assert get_part(prj) == "xc7k160t-3-fbg484" +# prj.set_part('XC6SLX9-2-CSG324') +# assert get_part(prj) == "xc6slx9-2-csg324" +# prj.set_part('XC6SLX9-2L-CSG324') +# assert get_part(prj) == "xc6slx9-2l-csg324" +# prj.set_part('XC6SLX9-CSG324-3') +# assert get_part(prj) == "xc6slx9-3-csg324" -def test_ise(): - prj = Project('ise') - assert get_part(prj) == "xc7k160t-3-fbg484" - prj.set_part('XC6SLX9-2-CSG324') - assert get_part(prj) == "xc6slx9-2-csg324" - prj.set_part('XC6SLX9-2L-CSG324') - assert get_part(prj) == "xc6slx9-2l-csg324" - prj.set_part('XC6SLX9-CSG324-3') - assert get_part(prj) == "xc6slx9-3-csg324" - -def test_libero(): - prj = Project('libero') - assert get_part(prj) == "mpf100t-1-fcg484" - prj.set_part('m2s010-3-tq144') - assert get_part(prj) == "m2s010-3-tq144" - prj.set_part('m2s010-tq144-2') - assert get_part(prj) == "m2s010-2-tq144" - prj.set_part('m2s010-tq144') - assert get_part(prj) == "m2s010-std-tq144" +# def test_libero(): +# prj = Project('libero') +# assert get_part(prj) == "mpf100t-1-fcg484" +# prj.set_part('m2s010-3-tq144') +# assert get_part(prj) == "m2s010-3-tq144" +# prj.set_part('m2s010-tq144-2') +# assert get_part(prj) == "m2s010-2-tq144" +# prj.set_part('m2s010-tq144') +# assert get_part(prj) == "m2s010-std-tq144" diff --git a/tests/test_vivado.py b/tests/test_vivado.py new file mode 100644 index 00000000..1aa74916 --- /dev/null +++ b/tests/test_vivado.py @@ -0,0 +1,22 @@ +from pyfpga.vivado import Vivado + + +def test_vivado(): + prj = Vivado() + prj.add_hook('precfg', 'HOOK1') + prj.add_hook('precfg', 'HOOK1') + prj.add_hook('postcfg', 'HOOK2') + prj.add_hook('postcfg', 'HOOK2') + prj.add_hook('presyn', 'HOOK3') + prj.add_hook('presyn', 'HOOK3') + prj.add_hook('postsyn', 'HOOK4') + prj.add_hook('postsyn', 'HOOK4') + prj.add_hook('prepar', 'HOOK5') + prj.add_hook('prepar', 'HOOK5') + prj.add_hook('postpar', 'HOOK6') + prj.add_hook('postpar', 'HOOK6') + prj.add_hook('prebit', 'HOOK7') + prj.add_hook('prebit', 'HOOK7') + prj.add_hook('postbit', 'HOOK8') + prj.add_hook('postbit', 'HOOK8') + prj.make() From d02b81ed17f74a99b44e1c1cc1f02d0a95cee786 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Mon, 10 Jun 2024 22:25:03 -0300 Subject: [PATCH 077/254] Implemented use of arch, defines, includes and params --- pyfpga/templates/vivado.jinja | 34 ---------------------------------- pyfpga/vivado.py | 27 ++++++++++++++++++++++----- tests/test_vivado.py | 8 ++++++++ 3 files changed, 30 insertions(+), 39 deletions(-) diff --git a/pyfpga/templates/vivado.jinja b/pyfpga/templates/vivado.jinja index cfc6734b..d12942e2 100644 --- a/pyfpga/templates/vivado.jinja +++ b/pyfpga/templates/vivado.jinja @@ -8,38 +8,6 @@ set PROJECT {{ PROJECT }} set PART {{ PART }} set TOP {{ TOP }} -# proc fpga_file {FILE {LIBRARY "work"}} { -# set message "adding the file '$FILE'" -# if { $LIBRARY != "work" } { append message " (into the VHDL library '$LIBRARY')" } -# regexp -nocase {\.(\w*)$} $FILE -> ext -# if { $ext == "tcl" } { -# source $FILE -# return -# } -# if { $LIBRARY != "work" } { -# add_files $FILE -# set_property library $LIBRARY [get_files $FILE] -# } else { -# add_files $FILE -# } -# } - -# proc fpga_include {PATH} { -# lappend INCLUDED $PATH -# # Verilog Included Files are NOT added -# set_property "include_dirs" $INCLUDED [current_fileset] -# } - -# proc fpga_params {} { -# if { [llength $PARAMS] == 0 } { return } -# set assigns [list] -# foreach PARAM $PARAMS { lappend assigns [join $PARAM "="] } -# set obj [get_filesets sources_1] -# set_property "generic" "[join $assigns]" -objects $obj -# } - -#--[ Project configuration ]--------------------------------------------------- - {% if CFG %} create_project -force $PROJECT set_property source_mgmt_mode None [current_project] @@ -74,8 +42,6 @@ set_property top_arch {{ ARCH }} [current_fileset] close_project {% endif %} -#--[ Design flow ]------------------------------------------------------------- - {% if SYN or PAR or BIT %} open_project $PROJECT diff --git a/pyfpga/vivado.py b/pyfpga/vivado.py index 7c7c88b8..60a7fcf8 100644 --- a/pyfpga/vivado.py +++ b/pyfpga/vivado.py @@ -29,11 +29,28 @@ def _make_prepare(self, steps): if 'hooks' in self.data: for stage in self.data['hooks']: context[stage.upper()] = '\n'.join(self.data['hooks'][stage]) - # FILES - # DEFINES - # INCLUDES - # PARAMS - # ARCH + # if { $LIBRARY != "work" } { + # add_files $FILE + # set_property library $LIBRARY [get_files $FILE] + # } else { + # add_files $FILE + if 'arch' in self.data: + context['ARCH'] = self.data['arch'] + if 'defines' in self.data: + defines = [] + for key,value in self.data['defines'].items(): + defines.append(f'{key}={value}') + context['DEFINES'] = ' '.join(defines) + if 'includes' in self.data: + includes = [] + for include in self.data['includes']: + includes.append(str(include)) + context['INCLUDES'] = ' '.join(includes) + if 'params' in self.data: + params = [] + for key,value in self.data['params'].items(): + params.append(f'{key}={value}') + context['PARAMS'] = ' '.join(params) self._create_file('vivado', 'tcl', context) def _prog_prepare(self): diff --git a/tests/test_vivado.py b/tests/test_vivado.py index 1aa74916..9d6a2e34 100644 --- a/tests/test_vivado.py +++ b/tests/test_vivado.py @@ -3,6 +3,14 @@ def test_vivado(): prj = Vivado() + + prj.set_arch('ARCHNAME') + prj.add_include('fakedata/dir1') + prj.add_include('fakedata/dir2') + prj.add_param('PARAM1', 'VALUE1') + prj.add_param('PARAM2', 'VALUE2') + prj.add_define('DEFINE1', 'VALUE1') + prj.add_define('DEFINE2', 'VALUE2') prj.add_hook('precfg', 'HOOK1') prj.add_hook('precfg', 'HOOK1') prj.add_hook('postcfg', 'HOOK2') From 6824d99b46861f4d55d6d6e561a0ee806112568e Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Mon, 17 Jun 2024 19:32:37 -0300 Subject: [PATCH 078/254] Fixed linter issues --- pyfpga/vivado.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyfpga/vivado.py b/pyfpga/vivado.py index 60a7fcf8..597afceb 100644 --- a/pyfpga/vivado.py +++ b/pyfpga/vivado.py @@ -38,7 +38,7 @@ def _make_prepare(self, steps): context['ARCH'] = self.data['arch'] if 'defines' in self.data: defines = [] - for key,value in self.data['defines'].items(): + for key, value in self.data['defines'].items(): defines.append(f'{key}={value}') context['DEFINES'] = ' '.join(defines) if 'includes' in self.data: @@ -48,7 +48,7 @@ def _make_prepare(self, steps): context['INCLUDES'] = ' '.join(includes) if 'params' in self.data: params = [] - for key,value in self.data['params'].items(): + for key, value in self.data['params'].items(): params.append(f'{key}={value}') context['PARAMS'] = ' '.join(params) self._create_file('vivado', 'tcl', context) From b178c922bce66bf27cfa0891cde9bb14dfe0d74d Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Mon, 17 Jun 2024 19:33:01 -0300 Subject: [PATCH 079/254] Added add_fileset, to be implemented --- pyfpga/project.py | 42 +++++++++++++++++++++++++++--------------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/pyfpga/project.py b/pyfpga/project.py index e71b550a..8fcd75ec 100644 --- a/pyfpga/project.py +++ b/pyfpga/project.py @@ -58,6 +58,21 @@ def set_part(self, name): self.logger.debug('Executing set_part') self.data['part'] = name + def add_include(self, path): + """Add an Include path. + + Specify where to search for Included Verilog Files, IP repos, etc. + + :param path: path of a directory + :type name: str + :raises NotADirectoryError: if path is not a directory + """ + self.logger.debug('Executing add_include') + path = Path(path).resolve() + if not path.is_dir(): + raise NotADirectoryError(path) + self.data.setdefault('includes', []).append(path) + def _add_file(self, pathname, filetype=None, library=None, options=None): files = glob.glob(pathname) if len(files) == 0: @@ -119,21 +134,6 @@ def add_vlog(self, pathname, options=None): self.logger.debug('Executing add_vlog') self._add_file(pathname, filetype='vlog', options=options) - def add_include(self, path): - """Add an Include path. - - Specify where to search for Included Verilog Files, IP repos, etc. - - :param path: path of a directory - :type name: str - :raises NotADirectoryError: if path is not a directory - """ - self.logger.debug('Executing add_include') - path = Path(path).resolve() - if not path.is_dir(): - raise NotADirectoryError(path) - self.data.setdefault('includes', []).append(path) - def add_param(self, name, value): """Add a Parameter/Generic Value. @@ -165,6 +165,18 @@ def set_arch(self, name): self.logger.debug('Executing set_arch') self.data['arch'] = name + def add_fileset(self, pathname): + """Add fileset file/s. + + :param pathname: path to a fileset file + :type pathname: str + :raises FileNotFoundError: when pathname is not found + """ + self.logger.debug('Executing add_fileset') + if not os.path.exists(pathname): + raise FileNotFoundError(pathname) + raise NotImplementedError() + def set_top(self, name): """Set the name of the top level component. From f77f21420e6bd4343b7388dfb5c511cc4f9e5433 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Mon, 17 Jun 2024 21:57:57 -0300 Subject: [PATCH 080/254] Slightly modified the internal data structure --- docs/internals.rst | 14 +++++--- pyfpga/project.py | 66 +++++++++++++++++++------------------ tests/fakedata/cons/all.xdc | 0 tests/fakedata/cons/par.xdc | 0 tests/fakedata/cons/syn.xdc | 0 tests/test_data.py | 40 +++++++++++----------- 6 files changed, 64 insertions(+), 56 deletions(-) create mode 100644 tests/fakedata/cons/all.xdc create mode 100644 tests/fakedata/cons/par.xdc create mode 100644 tests/fakedata/cons/syn.xdc diff --git a/docs/internals.rst b/docs/internals.rst index 0ec8ded5..6bf036ea 100644 --- a/docs/internals.rst +++ b/docs/internals.rst @@ -40,12 +40,15 @@ Internal data structure 'part': 'PARTNAME', 'includes': ['DIR1', 'DIR2', 'DIR3'], 'files': { - 'file1': {'type': 'vhdl', 'options': 'OPT1', 'library': 'LIB1'}, - 'file2': {'type': 'vlog', 'options': 'OPT2', 'library': None}, - 'file3': {'type': 'slog', 'options': 'OPT3', 'library': None}, - 'file4': {'type': 'cons', 'options': 'OPT4', 'library': None} + 'FILE1': ['vhdl', 'LIB1'], + 'FILE2': ['vlog'], + 'FILE3': ['slog'] + }, + 'constraints': { + 'FILE1': ['syn', 'par'], + 'FILE2': ['syn'], + 'FILE3': ['par'] }, - 'top': 'TOPNAME', 'params': { 'PAR1': 'VAL1', 'PAR2': 'VAL2', @@ -57,6 +60,7 @@ Internal data structure 'DEF3': 'VAL3' }, 'arch': 'ARCHNAME', + 'top': 'TOPNAME', 'hooks': { 'precfg': ['CMD1', 'CMD2'], 'postcfg': ['CMD1', 'CMD2'], diff --git a/pyfpga/project.py b/pyfpga/project.py index 8fcd75ec..b563ea38 100644 --- a/pyfpga/project.py +++ b/pyfpga/project.py @@ -73,66 +73,68 @@ def add_include(self, path): raise NotADirectoryError(path) self.data.setdefault('includes', []).append(path) - def _add_file(self, pathname, filetype=None, library=None, options=None): - files = glob.glob(pathname) + def _add_file(self, pathname, filetype=None, library=None): + files = glob.glob(pathname, recursive=True) if len(files) == 0: raise FileNotFoundError(pathname) for file in files: path = Path(file).resolve() - self.data.setdefault('files', {})[path] = { - 'type': filetype, 'options': options, 'library': library - } - - def add_cons(self, pathname): - """Add constraint file/s. - - :param pathname: path to a constraint file (glob compliant) - :type pathname: str - :raises FileNotFoundError: when pathname is not found - """ - self.logger.debug('Executing add_cons') - self._add_file(pathname, filetype='cons', options=None) - - def add_slog(self, pathname, options=None): + attr = [] + if filetype: + attr.append(filetype) + if library: + attr.append(library) + self.data.setdefault('files', {})[path] = attr + + def add_slog(self, pathname): """Add System Verilog file/s. :param pathname: path to a SV file (glob compliant) :type pathname: str - :param options: for the underlying tool - :type options: str, optional :raises FileNotFoundError: when pathname is not found """ self.logger.debug('Executing add_slog') - self._add_file(pathname, filetype='slog', options=options) + self._add_file(pathname, 'slog') - def add_vhdl(self, pathname, library=None, options=None): + def add_vhdl(self, pathname, library=None): """Add VHDL file/s. :param pathname: path to a SV file (glob compliant) :type pathname: str :param library: VHDL library name :type library: str, optional - :param options: for the underlying tool - :type options: str, optional :raises FileNotFoundError: when pathname is not found """ self.logger.debug('Executing add_vhdl') - self._add_file( - pathname, filetype='vhdl', - library=library, options=options - ) + self._add_file(pathname, 'vhdl', library) - def add_vlog(self, pathname, options=None): + def add_vlog(self, pathname): """Add Verilog file/s. :param pathname: path to a SV file (glob compliant) :type pathname: str - :param options: for the underlying tool - :type options: str, optional :raises FileNotFoundError: when pathname is not found """ self.logger.debug('Executing add_vlog') - self._add_file(pathname, filetype='vlog', options=options) + self._add_file(pathname, 'vlog') + + def add_constraint(self, path, syn=True, par=True): + """Add a constraint file. + + :param pathname: path of a file + :type pathname: str + :raises FileNotFoundError: if path is not found + """ + self.logger.debug('Executing add_constraint') + path = Path(path).resolve() + if not path.is_file(): + raise FileNotFoundError(path) + attr = [] + if syn: + attr.append('syn') + if par: + attr.append('par') + self.data.setdefault('constraints', {})[path] = attr def add_param(self, name, value): """Add a Parameter/Generic Value. @@ -213,7 +215,7 @@ def make(self, last='bit', first='cfg'): :param first: first step :type first: str, optional - .. note:: valid steps are ``cfg``, ``syn``, ``imp`` and ``bit``. + .. note:: valid steps are ``cfg``, ``syn``, ``par`` and ``bit``. """ steps = ['cfg', 'syn', 'par', 'bit'] if last not in steps: diff --git a/tests/fakedata/cons/all.xdc b/tests/fakedata/cons/all.xdc new file mode 100644 index 00000000..e69de29b diff --git a/tests/fakedata/cons/par.xdc b/tests/fakedata/cons/par.xdc new file mode 100644 index 00000000..e69de29b diff --git a/tests/fakedata/cons/syn.xdc b/tests/fakedata/cons/syn.xdc new file mode 100644 index 00000000..e69de29b diff --git a/tests/test_data.py b/tests/test_data.py index 0994d9db..2b2324a1 100644 --- a/tests/test_data.py +++ b/tests/test_data.py @@ -10,24 +10,23 @@ Path('fakedata/dir3').resolve() ], 'files': { - Path('fakedata/dir1/slog1.sv').resolve(): - {'type': 'slog', 'options': None, 'library': None}, - Path('fakedata/dir2/slog2.sv').resolve(): - {'type': 'slog', 'options': None, 'library': None}, - Path('fakedata/dir3/slog3.sv').resolve(): - {'type': 'slog', 'options': None, 'library': None}, - Path('fakedata/dir1/vhdl1.vhdl').resolve(): - {'type': 'vhdl', 'options': None, 'library': None}, - Path('fakedata/dir2/vhdl2.vhdl').resolve(): - {'type': 'vhdl', 'options': None, 'library': None}, - Path('fakedata/dir3/vhdl3.vhdl').resolve(): - {'type': 'vhdl', 'options': None, 'library': None}, - Path('fakedata/dir1/vlog1.v').resolve(): - {'type': 'vlog', 'options': None, 'library': None}, - Path('fakedata/dir2/vlog2.v').resolve(): - {'type': 'vlog', 'options': None, 'library': None}, - Path('fakedata/dir3/vlog3.v').resolve(): - {'type': 'vlog', 'options': None, 'library': None} + Path('fakedata/vhdl0.vhdl').resolve(): ['vhdl', 'LIB'], + Path('fakedata/dir1/vhdl1.vhdl').resolve(): ['vhdl', 'LIB'], + Path('fakedata/dir2/vhdl2.vhdl').resolve(): ['vhdl', 'LIB'], + Path('fakedata/dir3/vhdl3.vhdl').resolve(): ['vhdl', 'LIB'], + Path('fakedata/vlog0.v').resolve(): ['vlog'], + Path('fakedata/dir1/vlog1.v').resolve(): ['vlog'], + Path('fakedata/dir2/vlog2.v').resolve(): ['vlog'], + Path('fakedata/dir3/vlog3.v').resolve(): ['vlog'], + Path('fakedata/slog0.sv').resolve(): ['slog'], + Path('fakedata/dir1/slog1.sv').resolve(): ['slog'], + Path('fakedata/dir2/slog2.sv').resolve(): ['slog'], + Path('fakedata/dir3/slog3.sv').resolve(): ['slog'] + }, + 'constraints': { + Path('fakedata/cons/all.xdc').resolve(): ['syn', 'par'], + Path('fakedata/cons/syn.xdc').resolve(): ['syn'], + Path('fakedata/cons/par.xdc').resolve(): ['par'] }, 'top': 'TOPNAME', 'params': { @@ -63,8 +62,11 @@ def test_data(): prj.add_include('fakedata/dir2') prj.add_include('fakedata/dir3') prj.add_slog('fakedata/**/*.sv') - prj.add_vhdl('fakedata/**/*.vhdl') + prj.add_vhdl('fakedata/**/*.vhdl', 'LIB') prj.add_vlog('fakedata/**/*.v') + prj.add_constraint('fakedata/cons/all.xdc') + prj.add_constraint('fakedata/cons/syn.xdc', True, False) + prj.add_constraint('fakedata/cons/par.xdc', False, True) prj.add_param('PAR1', 'VAL1') prj.add_param('PAR2', 'VAL2') prj.add_param('PAR3', 'VAL3') From b17a2004318ee142590896c8e2794bfa2cc2a670 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Thu, 20 Jun 2024 17:52:37 -0300 Subject: [PATCH 081/254] More small adjustments in the internal data structure --- docs/internals.rst | 14 ++++++------- pyfpga/project.py | 50 +++++++++++++++++++++++++++------------------- tests/test_data.py | 42 +++++++++++++++++++++----------------- 3 files changed, 61 insertions(+), 45 deletions(-) diff --git a/docs/internals.rst b/docs/internals.rst index 6bf036ea..910df99a 100644 --- a/docs/internals.rst +++ b/docs/internals.rst @@ -40,14 +40,15 @@ Internal data structure 'part': 'PARTNAME', 'includes': ['DIR1', 'DIR2', 'DIR3'], 'files': { - 'FILE1': ['vhdl', 'LIB1'], - 'FILE2': ['vlog'], - 'FILE3': ['slog'] + 'FILE1': {'hdl': 'vhdl', 'lib': 'LIB1'} + 'FILE2': {'hdl': 'vlog'}, + 'FILE3': {'hdl': 'slog'} }, + 'top': 'TOPNAME', 'constraints': { - 'FILE1': ['syn', 'par'], - 'FILE2': ['syn'], - 'FILE3': ['par'] + 'FILE1': 'all', + 'FILE2': 'syn', + 'FILE3': 'par' }, 'params': { 'PAR1': 'VAL1', @@ -60,7 +61,6 @@ Internal data structure 'DEF3': 'VAL3' }, 'arch': 'ARCHNAME', - 'top': 'TOPNAME', 'hooks': { 'precfg': ['CMD1', 'CMD2'], 'postcfg': ['CMD1', 'CMD2'], diff --git a/pyfpga/project.py b/pyfpga/project.py index b563ea38..d7758ad0 100644 --- a/pyfpga/project.py +++ b/pyfpga/project.py @@ -23,12 +23,10 @@ class Project: """Base class to manage an FPGA project. - :param name: project name (tool name by default) + :param name: project name (tool name when nothing specified) :type name: str, optional :param odir: output directory :type odir: str, optional - :raises ValueError: when an invalid value is specified - :raises RuntimeError: when the needed underlying tool is not available """ tool = {} @@ -73,17 +71,17 @@ def add_include(self, path): raise NotADirectoryError(path) self.data.setdefault('includes', []).append(path) - def _add_file(self, pathname, filetype=None, library=None): + def _add_file(self, pathname, hdl=None, lib=None): files = glob.glob(pathname, recursive=True) if len(files) == 0: raise FileNotFoundError(pathname) for file in files: path = Path(file).resolve() - attr = [] - if filetype: - attr.append(filetype) - if library: - attr.append(library) + attr = {} + if hdl: + attr['hdl'] = hdl + if lib: + attr['lib'] = lib self.data.setdefault('files', {})[path] = attr def add_slog(self, pathname): @@ -96,17 +94,17 @@ def add_slog(self, pathname): self.logger.debug('Executing add_slog') self._add_file(pathname, 'slog') - def add_vhdl(self, pathname, library=None): + def add_vhdl(self, pathname, lib=None): """Add VHDL file/s. :param pathname: path to a SV file (glob compliant) :type pathname: str - :param library: VHDL library name - :type library: str, optional + :param lib: VHDL library name + :type lib: str, optional :raises FileNotFoundError: when pathname is not found """ self.logger.debug('Executing add_vhdl') - self._add_file(pathname, 'vhdl', library) + self._add_file(pathname, 'vhdl', lib) def add_vlog(self, pathname): """Add Verilog file/s. @@ -118,23 +116,22 @@ def add_vlog(self, pathname): self.logger.debug('Executing add_vlog') self._add_file(pathname, 'vlog') - def add_constraint(self, path, syn=True, par=True): + def add_constraint(self, path, when='all'): """Add a constraint file. :param pathname: path of a file :type pathname: str + :param when: always ('all'), synthesis ('syn') or P&R ('par') + :type only: str, optional :raises FileNotFoundError: if path is not found """ self.logger.debug('Executing add_constraint') path = Path(path).resolve() if not path.is_file(): raise FileNotFoundError(path) - attr = [] - if syn: - attr.append('syn') - if par: - attr.append('par') - self.data.setdefault('constraints', {})[path] = attr + if when not in ['all', 'syn', 'par']: + raise ValueError('Invalid only.') + self.data.setdefault('constraints', {})[path] = when def add_param(self, name, value): """Add a Parameter/Generic Value. @@ -199,6 +196,7 @@ def add_hook(self, stage, hook): :type hook: str :raises ValueError: when stage is invalid """ + self.logger.debug('Executing add_hook') stages = [ 'precfg', 'postcfg', 'presyn', 'postsyn', 'prepar', 'postpar', 'prebit', 'postbit' @@ -214,9 +212,18 @@ def make(self, last='bit', first='cfg'): :type last: str, optional :param first: first step :type first: str, optional + :raises ValueError: for missing or wrong values + :raises RuntimeError: when the needed underlying tool is not found .. note:: valid steps are ``cfg``, ``syn``, ``par`` and ``bit``. """ + self.logger.debug('Executing make') + if 'part' not in self.data: + self.logger.info('No part specified, using a default value') + if 'top' not in self.data: + self.logger.warning('No top specified') + if 'files' not in self.data: + self.logger.warning('No files specified') steps = ['cfg', 'syn', 'par', 'bit'] if last not in steps: raise ValueError('Invalid last step.') @@ -239,7 +246,10 @@ def prog(self, bitstream=None, position=1): :type bitstream: str, optional :param position: position of the device in the JTAG chain :type position: str, optional + :raises ValueError: for missing or wrong values + :raises RuntimeError: when the needed underlying tool is not found """ + self.logger.debug('Executing prog') if position not in range(1, 9): raise ValueError('Invalid position.') _ = bitstream diff --git a/tests/test_data.py b/tests/test_data.py index 2b2324a1..c4a355c0 100644 --- a/tests/test_data.py +++ b/tests/test_data.py @@ -10,25 +10,31 @@ Path('fakedata/dir3').resolve() ], 'files': { - Path('fakedata/vhdl0.vhdl').resolve(): ['vhdl', 'LIB'], - Path('fakedata/dir1/vhdl1.vhdl').resolve(): ['vhdl', 'LIB'], - Path('fakedata/dir2/vhdl2.vhdl').resolve(): ['vhdl', 'LIB'], - Path('fakedata/dir3/vhdl3.vhdl').resolve(): ['vhdl', 'LIB'], - Path('fakedata/vlog0.v').resolve(): ['vlog'], - Path('fakedata/dir1/vlog1.v').resolve(): ['vlog'], - Path('fakedata/dir2/vlog2.v').resolve(): ['vlog'], - Path('fakedata/dir3/vlog3.v').resolve(): ['vlog'], - Path('fakedata/slog0.sv').resolve(): ['slog'], - Path('fakedata/dir1/slog1.sv').resolve(): ['slog'], - Path('fakedata/dir2/slog2.sv').resolve(): ['slog'], - Path('fakedata/dir3/slog3.sv').resolve(): ['slog'] + Path('fakedata/vhdl0.vhdl').resolve(): {'hdl': 'vhdl', 'lib': 'LIB'}, + Path('fakedata/dir1/vhdl1.vhdl').resolve(): { + 'hdl': 'vhdl', 'lib': 'LIB' + }, + Path('fakedata/dir2/vhdl2.vhdl').resolve(): { + 'hdl': 'vhdl', 'lib': 'LIB' + }, + Path('fakedata/dir3/vhdl3.vhdl').resolve(): { + 'hdl': 'vhdl', 'lib': 'LIB' + }, + Path('fakedata/vlog0.v').resolve(): {'hdl': 'vlog'}, + Path('fakedata/dir1/vlog1.v').resolve(): {'hdl': 'vlog'}, + Path('fakedata/dir2/vlog2.v').resolve(): {'hdl': 'vlog'}, + Path('fakedata/dir3/vlog3.v').resolve(): {'hdl': 'vlog'}, + Path('fakedata/slog0.sv').resolve(): {'hdl': 'slog'}, + Path('fakedata/dir1/slog1.sv').resolve(): {'hdl': 'slog'}, + Path('fakedata/dir2/slog2.sv').resolve(): {'hdl': 'slog'}, + Path('fakedata/dir3/slog3.sv').resolve(): {'hdl': 'slog'} }, + 'top': 'TOPNAME', 'constraints': { - Path('fakedata/cons/all.xdc').resolve(): ['syn', 'par'], - Path('fakedata/cons/syn.xdc').resolve(): ['syn'], - Path('fakedata/cons/par.xdc').resolve(): ['par'] + Path('fakedata/cons/all.xdc').resolve(): 'all', + Path('fakedata/cons/syn.xdc').resolve(): 'syn', + Path('fakedata/cons/par.xdc').resolve(): 'par' }, - 'top': 'TOPNAME', 'params': { 'PAR1': 'VAL1', 'PAR2': 'VAL2', @@ -65,8 +71,8 @@ def test_data(): prj.add_vhdl('fakedata/**/*.vhdl', 'LIB') prj.add_vlog('fakedata/**/*.v') prj.add_constraint('fakedata/cons/all.xdc') - prj.add_constraint('fakedata/cons/syn.xdc', True, False) - prj.add_constraint('fakedata/cons/par.xdc', False, True) + prj.add_constraint('fakedata/cons/syn.xdc', 'syn') + prj.add_constraint('fakedata/cons/par.xdc', 'par') prj.add_param('PAR1', 'VAL1') prj.add_param('PAR2', 'VAL2') prj.add_param('PAR3', 'VAL3') From 2c087d6a74a71252f39a3984801624a586c4d341 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Thu, 20 Jun 2024 17:54:15 -0300 Subject: [PATCH 082/254] Finished implementation of _make_prepare for Vivado --- pyfpga/templates/vivado.jinja | 33 ++++++++++---------- pyfpga/vivado.py | 58 +++++++++++++++++++++++++---------- tests/test_vivado.py | 41 ++++++++++++------------- 3 files changed, 78 insertions(+), 54 deletions(-) diff --git a/pyfpga/templates/vivado.jinja b/pyfpga/templates/vivado.jinja index d12942e2..05b1f0fa 100644 --- a/pyfpga/templates/vivado.jinja +++ b/pyfpga/templates/vivado.jinja @@ -4,37 +4,36 @@ # SPDX-License-Identifier: GPL-3.0-or-later # -set PROJECT {{ PROJECT }} -set PART {{ PART }} -set TOP {{ TOP }} +#--[ Project configuration ]-------------------------------------------------- {% if CFG %} -create_project -force $PROJECT -set_property source_mgmt_mode None [current_project] +create_project -force {{ PROJECT }} +set_property SOURCE_MGMT_MODE None [current_project] set_property STEPS.SYNTH_DESIGN.ARGS.ASSERT true [get_runs synth_1] - -set_property part $PART [current_project] +set_property PART {{ PART }} [current_project] {{ PRECFG }} {{ FILES }} -set_property top $TOP [current_fileset] +{% if TOP %} +set_property TOP {{ TOP }} [current_fileset] +{% endif %} {% if DEFINES %} -set_property verilog_define { {{ DEFINES}} } [current_fileset] +set_property VERILOG_DEFINE { {{ DEFINES}} } [current_fileset] {% endif %} {% if INCLUDES %} -set_property include_dirs { {{ INCLUDES}} } [current_fileset] +set_property INCLUDE_DIRS { {{ INCLUDES}} } [current_fileset] {% endif %} {% if PARAMS %} -set_property generic { {{ PARAMS }} } -objects [get_filesets sources_1] +set_property GENERIC { {{ PARAMS }} } -objects [get_filesets sources_1] {% endif %} {% if ARCH %} -set_property top_arch {{ ARCH }} [current_fileset] +set_property TOP_ARCH {{ ARCH }} [current_fileset] {% endif %} {{ POSTCFG }} @@ -42,14 +41,16 @@ set_property top_arch {{ ARCH }} [current_fileset] close_project {% endif %} +#--[ Design flow ]------------------------------------------------------------- + {% if SYN or PAR or BIT %} -open_project $PROJECT +open_project {{ PROJECT }} {% if SYN %} {{ PRESYN }} # PRESYNTH -# set_property design_mode GateLvl [current_fileset] +# set_property DESIGN_MODE GateLvl [current_fileset] reset_run synth_1 launch_runs synth_1 wait_on_run synth_1 @@ -71,8 +72,8 @@ wait_on_run impl_1 {{ PREBIT }} open_run impl_1 -write_bitstream -force $PROJECT -write_debug_probes -force -quiet $PROJECT.ltx +write_bitstream -force {{ PROJECT }} +write_debug_probes -force -quiet {{ PROJECT }}.ltx {{ POSTBIT }} {% endif %} diff --git a/pyfpga/vivado.py b/pyfpga/vivado.py index 597afceb..d776b52a 100644 --- a/pyfpga/vivado.py +++ b/pyfpga/vivado.py @@ -8,6 +8,9 @@ Implements support for Vivado. """ +# pylint: disable=too-many-locals +# pylint: disable=too-many-branches + from pyfpga.project import Project @@ -21,36 +24,57 @@ def _make_prepare(self, steps): ) context = { 'PROJECT': self.name or 'vivado', - 'PART': self.data.get('part', 'xc7k160t-3-fbg484'), - 'TOP': self.data.get('top', 'top') + 'PART': self.data.get('part', 'xc7k160t-3-fbg484') } for step in steps: context[step] = 1 - if 'hooks' in self.data: - for stage in self.data['hooks']: - context[stage.upper()] = '\n'.join(self.data['hooks'][stage]) - # if { $LIBRARY != "work" } { - # add_files $FILE - # set_property library $LIBRARY [get_files $FILE] - # } else { - # add_files $FILE - if 'arch' in self.data: - context['ARCH'] = self.data['arch'] - if 'defines' in self.data: - defines = [] - for key, value in self.data['defines'].items(): - defines.append(f'{key}={value}') - context['DEFINES'] = ' '.join(defines) if 'includes' in self.data: includes = [] for include in self.data['includes']: includes.append(str(include)) context['INCLUDES'] = ' '.join(includes) + files = [] + if 'files' in self.data: + for file in self.data['files']: + files.append(f'add_file {file}') + for file in self.data['files']: + if 'lib' in self.data['files'][file]: + lib = self.data['files'][file]['lib'] + files.append( + f'set_property library {lib} [get_files {file}]' + ) + if 'constraints' in self.data: + for file in self.data['constraints']: + files.append(f'add_file -fileset constrs_1 {file}') + for file in self.data['constraints']: + if self.data['constraints'][file] == 'syn': + prop = 'USED_IN_IMPLEMENTATION FALSE' + if self.data['constraints'][file] == 'syn': + prop = 'USED_IN_SYNTHESIS FALSE' + if self.data['constraints'][file] != 'all': + files.append(f'set_property {prop} [get_files {file}]') + first = next(iter(self.data['constraints'])) + prop = f'TARGET_CONSTRS_FILE {first}' + files.append(f'set_property {prop} [current_fileset -constrset]') + if files: + context['FILES'] = '\n'.join(files) + if 'top' in self.data: + context['TOP'] = self.data['top'] + if 'defines' in self.data: + defines = [] + for key, value in self.data['defines'].items(): + defines.append(f'{key}={value}') + context['DEFINES'] = ' '.join(defines) if 'params' in self.data: params = [] for key, value in self.data['params'].items(): params.append(f'{key}={value}') context['PARAMS'] = ' '.join(params) + if 'arch' in self.data: + context['ARCH'] = self.data['arch'] + if 'hooks' in self.data: + for stage in self.data['hooks']: + context[stage.upper()] = '\n'.join(self.data['hooks'][stage]) self._create_file('vivado', 'tcl', context) def _prog_prepare(self): diff --git a/tests/test_vivado.py b/tests/test_vivado.py index 9d6a2e34..9afa353d 100644 --- a/tests/test_vivado.py +++ b/tests/test_vivado.py @@ -3,28 +3,27 @@ def test_vivado(): prj = Vivado() - + prj.set_part('PARTNAME') + prj.set_top('TOPNAME') prj.set_arch('ARCHNAME') prj.add_include('fakedata/dir1') prj.add_include('fakedata/dir2') - prj.add_param('PARAM1', 'VALUE1') - prj.add_param('PARAM2', 'VALUE2') - prj.add_define('DEFINE1', 'VALUE1') - prj.add_define('DEFINE2', 'VALUE2') - prj.add_hook('precfg', 'HOOK1') - prj.add_hook('precfg', 'HOOK1') - prj.add_hook('postcfg', 'HOOK2') - prj.add_hook('postcfg', 'HOOK2') - prj.add_hook('presyn', 'HOOK3') - prj.add_hook('presyn', 'HOOK3') - prj.add_hook('postsyn', 'HOOK4') - prj.add_hook('postsyn', 'HOOK4') - prj.add_hook('prepar', 'HOOK5') - prj.add_hook('prepar', 'HOOK5') - prj.add_hook('postpar', 'HOOK6') - prj.add_hook('postpar', 'HOOK6') - prj.add_hook('prebit', 'HOOK7') - prj.add_hook('prebit', 'HOOK7') - prj.add_hook('postbit', 'HOOK8') - prj.add_hook('postbit', 'HOOK8') + prj.add_slog('fakedata/**/*.sv') + prj.add_vhdl('fakedata/**/*.vhdl', 'LIB') + prj.add_vlog('fakedata/**/*.v') + prj.add_constraint('fakedata/cons/all.xdc') + prj.add_constraint('fakedata/cons/syn.xdc', 'syn') + prj.add_constraint('fakedata/cons/par.xdc', 'par') + prj.add_param('PAR1', 'VAL1') + prj.add_param('PAR2', 'VAL2') + prj.add_define('DEF1', 'VAL1') + prj.add_define('DEF2', 'VAL2') + prj.add_hook('precfg', 'PRECFG_HOOK') + prj.add_hook('postcfg', 'POSTCFG_HOOK') + prj.add_hook('presyn', 'PRESYN_HOOK') + prj.add_hook('postsyn', 'POSTSYN_HOOK') + prj.add_hook('prepar', 'PREPAR_HOOK') + prj.add_hook('postpar', 'POSTPAR_HOOK') + prj.add_hook('prebit', 'PREBIT_HOOK') + prj.add_hook('postbit', 'POSTBIT_HOOK') prj.make() From cf2011395d27146849a1ee392a74157d3fa79b35 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Thu, 20 Jun 2024 18:22:36 -0300 Subject: [PATCH 083/254] Moved submodule 'resources' to 'examples/resources' --- .gitmodules | 6 +++--- Makefile | 4 ++-- resources => examples/resources | 0 3 files changed, 5 insertions(+), 5 deletions(-) rename resources => examples/resources (100%) diff --git a/.gitmodules b/.gitmodules index 362bc8ae..cba0ef31 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ -[submodule "resources"] - path = resources - url = https://github.com/PyFPGA/resources +[submodule "examples/resources"] + path = examples/resources + url = git@github.com:PyFPGA/resources.git diff --git a/Makefile b/Makefile index 9c8032b2..f9eda4cb 100644 --- a/Makefile +++ b/Makefile @@ -19,7 +19,7 @@ clean: rm -fr build .pytest_cache submodule-init: - git submodule update --init + git submodule update --init --recursive submodule-update: - cd resources; git checkout main; git pull + cd examples/resources; git checkout main; git pull diff --git a/resources b/examples/resources similarity index 100% rename from resources rename to examples/resources From 0d6ad2acc173cca47374848a4d5b320cfaebb653 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Thu, 20 Jun 2024 18:36:11 -0300 Subject: [PATCH 084/254] Removed 'hdl', superseded by 'examples/resources' --- examples/resources | 2 +- hdl/blinking.v | 28 ------------------ hdl/blinking.vhdl | 33 --------------------- hdl/data/memory.dat | 64 ----------------------------------------- hdl/examples_pkg.vhdl | 17 ----------- hdl/fakes/generics.vhdl | 41 -------------------------- hdl/fakes/parameters.v | 51 -------------------------------- hdl/fakes/top.v | 10 ------- hdl/fakes/top.vhdl | 5 ---- hdl/headers1/freq.vh | 1 - hdl/headers2/secs.vh | 1 - hdl/ram.v | 21 -------------- hdl/ram.vhdl | 45 ----------------------------- hdl/top.v | 11 ------- hdl/top.vhdl | 21 -------------- 15 files changed, 1 insertion(+), 350 deletions(-) delete mode 100644 hdl/blinking.v delete mode 100644 hdl/blinking.vhdl delete mode 100644 hdl/data/memory.dat delete mode 100644 hdl/examples_pkg.vhdl delete mode 100644 hdl/fakes/generics.vhdl delete mode 100644 hdl/fakes/parameters.v delete mode 100644 hdl/fakes/top.v delete mode 100644 hdl/fakes/top.vhdl delete mode 100644 hdl/headers1/freq.vh delete mode 100644 hdl/headers2/secs.vh delete mode 100644 hdl/ram.v delete mode 100644 hdl/ram.vhdl delete mode 100644 hdl/top.v delete mode 100644 hdl/top.vhdl diff --git a/examples/resources b/examples/resources index 6a58bba3..53d36f1d 160000 --- a/examples/resources +++ b/examples/resources @@ -1 +1 @@ -Subproject commit 6a58bba3b1c36d238d1111e910db02f0b284aef7 +Subproject commit 53d36f1d9d49b26e42d09aaf1e0e6c6fac7736e2 diff --git a/hdl/blinking.v b/hdl/blinking.v deleted file mode 100644 index f8bf669c..00000000 --- a/hdl/blinking.v +++ /dev/null @@ -1,28 +0,0 @@ -`include "freq.vh" -`include "secs.vh" - -module Blinking #( - parameter FREQ = `DEFAULT_FREQ, - parameter SECS = `DEFAULT_SECS -)( - input wire clk_i, - output wire led_o -); - -localparam DIV = FREQ*SECS; - -reg led; -reg [$clog2(DIV)-1:0] cnt = 0; - -always @(posedge clk_i) begin - if (cnt == DIV-1) begin - cnt = 0; - led <= ~led; - end else begin - cnt = cnt + 1; - end -end - -assign led_o = led; - -endmodule diff --git a/hdl/blinking.vhdl b/hdl/blinking.vhdl deleted file mode 100644 index a50fbbbc..00000000 --- a/hdl/blinking.vhdl +++ /dev/null @@ -1,33 +0,0 @@ -library IEEE; -use IEEE.std_logic_1164.all; - -entity Blinking is - generic ( - FREQ : positive:=25e6; - SECS : positive:=1 - ); - port ( - clk_i : in std_logic; - led_o : out std_logic - ); -end entity Blinking; - -architecture RTL of Blinking is - constant DIV : positive:=FREQ*SECS-1; - signal led : std_logic; -begin - blink: - process (clk_i) - variable cnt: natural range 0 to DIV:=0; - begin - if rising_edge(clk_i) then - if cnt=DIV then - cnt:=0; - led <= not(led); - else - cnt:=cnt+1; - end if; - end if; - end process blink; - led_o <= led; -end architecture RTL; diff --git a/hdl/data/memory.dat b/hdl/data/memory.dat deleted file mode 100644 index a7dda8d9..00000000 --- a/hdl/data/memory.dat +++ /dev/null @@ -1,64 +0,0 @@ -00000000000000000000000000000000 -00000000000000000000000000000000 -00000000000000000000000000000000 -00000000000000000000000000000000 -00000000000000000000000000000000 -00000000000000000000000000000000 -00000000000000000000000000000000 -00000000000000000000000000000000 -00000000000000000000000000000000 -00000000000000000000000000000000 -00000000000000000000000000000000 -00000000000000000000000000000000 -00000000000000000000000000000000 -00000000000000000000000000000000 -00000000000000000000000000000000 -00000000000000000000000000000000 -00000000000000000000000000000000 -00000000000000000000000000000000 -00000000000000000000000000000000 -00000000000000000000000000000000 -00000000000000000000000000000000 -00000000000000000000000000000000 -00000000000000000000000000000000 -00000000000000000000000000000000 -00000000000000000000000000000000 -00000000000000000000000000000000 -00000000000000000000000000000000 -00000000000000000000000000000000 -00000000000000000000000000000000 -00000000000000000000000000000000 -00000000000000000000000000000000 -00000000000000000000000000000000 -00000000000000000000000000000000 -00000000000000000000000000000000 -00000000000000000000000000000000 -00000000000000000000000000000000 -00000000000000000000000000000000 -00000000000000000000000000000000 -00000000000000000000000000000000 -00000000000000000000000000000000 -00000000000000000000000000000000 -00000000000000000000000000000000 -00000000000000000000000000000000 -00000000000000000000000000000000 -00000000000000000000000000000000 -00000000000000000000000000000000 -00000000000000000000000000000000 -00000000000000000000000000000000 -00000000000000000000000000000000 -00000000000000000000000000000000 -00000000000000000000000000000000 -00000000000000000000000000000000 -00000000000000000000000000000000 -00000000000000000000000000000000 -00000000000000000000000000000000 -00000000000000000000000000000000 -00000000000000000000000000000000 -00000000000000000000000000000000 -00000000000000000000000000000000 -00000000000000000000000000000000 -00000000000000000000000000000000 -00000000000000000000000000000000 -00000000000000000000000000000000 -00000000000000000000000000000000 diff --git a/hdl/examples_pkg.vhdl b/hdl/examples_pkg.vhdl deleted file mode 100644 index 3f15f041..00000000 --- a/hdl/examples_pkg.vhdl +++ /dev/null @@ -1,17 +0,0 @@ -library IEEE; -use IEEE.std_logic_1164.all; - -package Examples is - - component Blinking is - generic ( - FREQ : positive:=25e6; - SECS : positive:=1 - ); - port ( - clk_i : in std_logic; - led_o : out std_logic - ); - end component Blinking; - -end package Examples; diff --git a/hdl/fakes/generics.vhdl b/hdl/fakes/generics.vhdl deleted file mode 100644 index 4e010ebc..00000000 --- a/hdl/fakes/generics.vhdl +++ /dev/null @@ -1,41 +0,0 @@ -library IEEE; -use IEEE.std_logic_1164.all; -use IEEE.numeric_std.all; - -entity Params is - generic ( - BOO : boolean:=FALSE; - INT : integer:=0; - LOG : std_logic:='0'; - VEC : std_logic_vector(7 downto 0):="00000000"; - STR : string:="ABCD"; - REA : real:=0.0 - ); - port ( - boo_o : out std_logic; - int_o : out std_logic_vector(7 downto 0); - log_o : out std_logic; - vec_o : out std_logic_vector(7 downto 0); - str_o : out std_logic; - rea_o : out std_logic - ); -end entity Params; - -architecture RTL of Params is -begin - - assert BOO=True report "The boolean is not True" severity failure; - assert INT=255 report "The integer is not 255" severity failure; - assert LOG='1' report "The std_logic is not '1'" severity failure; - assert VEC="11111111" report "The std_logic_vector is not 11111111" severity failure; - assert STR="WXYZ" report "The string is not WXYZ" severity failure; - assert REA=1.1 report "The real is not 1.1" severity failure; - - boo_o <= '1' when BOO else '0'; - int_o <= std_logic_vector(to_unsigned(INT, 8)); - log_o <= LOG; - vec_o <= VEC; - str_o <= '1' when STR="WXYZ" else '0'; - rea_o <= '1' when REA=1.1 else '0'; - -end architecture RTL; diff --git a/hdl/fakes/parameters.v b/hdl/fakes/parameters.v deleted file mode 100644 index 9dd81c51..00000000 --- a/hdl/fakes/parameters.v +++ /dev/null @@ -1,51 +0,0 @@ -module Params #( - parameter BOO = 0, - parameter INT = 0, - parameter LOG = 1'b0, - parameter VEC = 8'd0, - parameter STR = "ABCD", - parameter REA = 0.0 -)( - output wire boo_o, - output wire [7:0] int_o, - output wire log_o, - output wire [7:0] vec_o, - output wire str_o, - output wire rea_o -); - - initial begin - if (BOO != 1) begin - $display("The boolean is not True"); - $finish; - end - if (INT != 255) begin - $display("The integer is not 255"); - $finish; - end - if (LOG != 1) begin - $display("The std_logic is not '1'"); - $finish; - end - if (VEC != 8'b11111111) begin - $display("The std_logic_vector is not 11111111"); - $finish; - end - if (STR != "WXYZ") begin - $display("The string is not WXYZ"); - $finish; - end - if (REA != 1.1) begin - $display("The real is not 1.1"); - $finish; - end - end - - assign boo_o = BOO; - assign int_o = INT; - assign log_o = LOG; - assign vec_o = VEC; - assign str_o = (STR=="WXYZ") ? 1'b1 : 1'b0; - assign rea_o = (REA==1.1) ? 1'b1 : 1'b0; - -endmodule diff --git a/hdl/fakes/top.v b/hdl/fakes/top.v deleted file mode 100644 index 3bc0b7c1..00000000 --- a/hdl/fakes/top.v +++ /dev/null @@ -1,10 +0,0 @@ -module Top1 (); -endmodule - -// module Top2 (); -// endmodule - -/* -module Top3 (); -endmodule -*/ diff --git a/hdl/fakes/top.vhdl b/hdl/fakes/top.vhdl deleted file mode 100644 index 01063ae8..00000000 --- a/hdl/fakes/top.vhdl +++ /dev/null @@ -1,5 +0,0 @@ -entity Top1 is -end entity Top1; - --- entity Top2 is --- end entity Top1; diff --git a/hdl/headers1/freq.vh b/hdl/headers1/freq.vh deleted file mode 100644 index 2b27b844..00000000 --- a/hdl/headers1/freq.vh +++ /dev/null @@ -1 +0,0 @@ -`define DEFAULT_FREQ 25000000 diff --git a/hdl/headers2/secs.vh b/hdl/headers2/secs.vh deleted file mode 100644 index aa252d2f..00000000 --- a/hdl/headers2/secs.vh +++ /dev/null @@ -1 +0,0 @@ -`define DEFAULT_SECS 1 diff --git a/hdl/ram.v b/hdl/ram.v deleted file mode 100644 index 35be5b06..00000000 --- a/hdl/ram.v +++ /dev/null @@ -1,21 +0,0 @@ -// A RAM initialized with an external file - -module ram ( - input clk_i, - input we_i, - input [5:0] addr_i, - input [31:0] data_i, - output reg [31:0] data_o -); - -reg [31:0] ram [0:63]; - -initial $readmemb("data/memory.dat",ram); - -always @(posedge clk_i) begin - if (we_i) - ram[addr_i] <= data_i; - data_o <= ram[addr_i]; -end - -endmodule diff --git a/hdl/ram.vhdl b/hdl/ram.vhdl deleted file mode 100644 index 36d68bd1..00000000 --- a/hdl/ram.vhdl +++ /dev/null @@ -1,45 +0,0 @@ --- A RAM initialized with an external file - -library IEEE; -use IEEE.std_logic_1164.all; -use IEEE.numeric_std.all; -use STD.textio.all; - -entity ram is - port( - clk_i : in std_logic; - we_i : in std_logic; - addr_i : in std_logic_vector(5 downto 0); - data_i : in std_logic_vector(31 downto 0); - data_o : out std_logic_vector(31 downto 0) - ); -end ram; - -architecture RTL of ram is - type mem_t is array (0 to 63) of bit_vector(31 downto 0); - - impure function init(filename : in string) return mem_t is - file fh : text is filename; - variable l : line; - variable mem : mem_t; - begin - for i in mem_t'range loop - readline(fh, l); - read(l, mem(i)); - end loop; - return mem; - end function; - - signal ram : mem_t := init("data/memory.dat"); -begin - memory: - process(clk_i) - begin - if rising_edge(clk_i) then - if we_i = '1' then - ram(to_integer(unsigned(addr_i))) <= to_bitvector(data_i); - end if; - data_o <= to_stdlogicvector(ram(to_integer(unsigned(addr_i)))); - end if; - end process; -end architecture RTL; diff --git a/hdl/top.v b/hdl/top.v deleted file mode 100644 index 787fbdbd..00000000 --- a/hdl/top.v +++ /dev/null @@ -1,11 +0,0 @@ -module Top ( - input wire clk_i, - output wire led_o -); - -localparam FREQ = 50000000; - -Blinking #(.FREQ (FREQ), .SECS (1)) - dut (.clk_i (clk_i), .led_o (led_o)); - -endmodule diff --git a/hdl/top.vhdl b/hdl/top.vhdl deleted file mode 100644 index 80919fd6..00000000 --- a/hdl/top.vhdl +++ /dev/null @@ -1,21 +0,0 @@ -library IEEE; -use IEEE.std_logic_1164.all; -library Examples; -use Examples.examples.all; - -entity Top is - port ( - clk_i : in std_logic; - led_o : out std_logic - ); -end entity Top; - -architecture Structural of Top is - constant FREQ : positive := 50e6; -begin - - dut: Blinking - generic map (FREQ => FREQ, SECS => 1) - port map (clk_i => clk_i, led_o => led_o); - -end architecture Structural; From 293d1f02867a6569d379d70ed7aa6d7b83b78f7c Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Thu, 20 Jun 2024 19:27:40 -0300 Subject: [PATCH 085/254] Updated resources, removed zybo.xdc --- examples/resources | 2 +- examples/vivado/zybo.xdc | 10 ---------- 2 files changed, 1 insertion(+), 11 deletions(-) delete mode 100644 examples/vivado/zybo.xdc diff --git a/examples/resources b/examples/resources index 53d36f1d..7f34b14e 160000 --- a/examples/resources +++ b/examples/resources @@ -1 +1 @@ -Subproject commit 53d36f1d9d49b26e42d09aaf1e0e6c6fac7736e2 +Subproject commit 7f34b14e2cdcbcbd6f57053a033a2abdd3ff1900 diff --git a/examples/vivado/zybo.xdc b/examples/vivado/zybo.xdc deleted file mode 100644 index 9b6fe3d3..00000000 --- a/examples/vivado/zybo.xdc +++ /dev/null @@ -1,10 +0,0 @@ -# Xilinx Design Constraints -# -# Are based on the standard Synopsys Design Constraints (SDC) format. - -create_clock -name clk_i -period 8 [get_ports clk_i] - -set_property PACKAGE_PIN L16 [get_ports clk_i] -set_property IOSTANDARD LVCMOS33 [get_ports clk_i] -set_property PACKAGE_PIN M14 [get_ports led_o] -set_property IOSTANDARD LVCMOS33 [get_ports led_o] From 07e53be5e8b22bd3dabafc6a0f650e9dfe1f78a6 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Thu, 20 Jun 2024 19:28:08 -0300 Subject: [PATCH 086/254] Updated Vivado example to use the new PyFPGA --- examples/vivado/vivado.py | 39 +++++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/examples/vivado/vivado.py b/examples/vivado/vivado.py index bf1830a1..497734d3 100644 --- a/examples/vivado/vivado.py +++ b/examples/vivado/vivado.py @@ -1,30 +1,33 @@ -"""Vivado example project.""" +"""Vivado VHDL example project.""" import argparse -import logging -from fpga.project import Project - -logging.basicConfig() +from pyfpga.vivado import Vivado parser = argparse.ArgumentParser() parser.add_argument( - '--action', choices=['generate', 'transfer', 'all'], default='generate', + '--action', choices=['make', 'prog', 'all'], default='make' +) +parser.add_argument( + '--source', choices=['vhdl', 'vlog', 'design'], default='vhdl' ) args = parser.parse_args() -prj = Project('vivado') +prj = Vivado(odir=f'../build/vivado-{args.source}') prj.set_part('xc7z010-1-clg400') -prj.set_outdir('../../build/vivado') - prj.add_param('FREQ', '125000000') -prj.add_files('../../hdl/blinking.vhdl') -prj.add_files('zybo.xdc') -prj.set_top('Blinking') - -if args.action in ['generate', 'all']: - prj.generate() - -if args.action in ['transfer', 'all']: - prj.transfer('fpga') +if args.source == 'vhdl': + prj.add_vhdl('../resources/vhdl/blink.vhdl') +if args.source == 'vlog': + prj.add_vlog('../resources/vlog/blink.v') +prj.add_constraint('../resources/constraints/zybo/timing.xdc', 'syn') +prj.add_constraint('../resources/constraints/zybo/clk.xdc', 'par') +prj.add_constraint('../resources/constraints/zybo/led.xdc', 'par') +prj.set_top('Blink') + +if args.action in ['make', 'all']: + prj.make() + +if args.action in ['prog', 'all']: + prj.prog() From d3bb077ea6f1327397730424804949fd71da1cb9 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Thu, 20 Jun 2024 19:28:56 -0300 Subject: [PATCH 087/254] Fixed double timestamp --- pyfpga/project.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyfpga/project.py b/pyfpga/project.py index d7758ad0..7e381620 100644 --- a/pyfpga/project.py +++ b/pyfpga/project.py @@ -13,7 +13,6 @@ import os import subprocess -from datetime import datetime from pathlib import Path from shutil import which from time import time @@ -277,7 +276,9 @@ def _create_file(self, basename, extension, context): file.write(content) def _run(self, command): - self.logger.info('Running the underlying tool (%s)', datetime.now()) + self.logger.info( + 'Running the underlying tool (%s)', self.tool['make-app'] + ) run_error = 0 old_dir = Path.cwd() new_dir = Path(self.odir) @@ -299,7 +300,6 @@ def _run(self, command): finally: os.chdir(old_dir) end = time() - self.logger.info('Done (%s)', datetime.now()) elapsed = end - start self.logger.info( 'Elapsed time %dh %dm %.2fs', From 804bcc1dbd930c2fed0a7bb006ca64068cf250fc Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sun, 23 Jun 2024 16:35:16 -0300 Subject: [PATCH 088/254] Added a script to enable mock-ups --- tests/mocks/source-me.sh | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 tests/mocks/source-me.sh diff --git a/tests/mocks/source-me.sh b/tests/mocks/source-me.sh new file mode 100644 index 00000000..1f035b47 --- /dev/null +++ b/tests/mocks/source-me.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +MDIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &> /dev/null && pwd) + +export PATH=$PATH:$MDIR From 33429d04e0c7c232fea25d7cedadd22eaa172ac9 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sun, 23 Jun 2024 17:39:56 -0300 Subject: [PATCH 089/254] Improved some messages and how to indicate the command to run --- pyfpga/project.py | 57 ++++++++++++++++++++++------------------------- pyfpga/vivado.py | 10 ++------- 2 files changed, 29 insertions(+), 38 deletions(-) diff --git a/pyfpga/project.py b/pyfpga/project.py index 7e381620..52106923 100644 --- a/pyfpga/project.py +++ b/pyfpga/project.py @@ -14,7 +14,6 @@ import subprocess from pathlib import Path -from shutil import which from time import time from jinja2 import Environment, FileSystemLoader @@ -204,39 +203,41 @@ def add_hook(self, stage, hook): raise ValueError('Invalid stage.') self.data.setdefault('hooks', {}).setdefault(stage, []).append(hook) - def make(self, last='bit', first='cfg'): + def make(self, first='cfg', last='bit'): """Run the underlying tool. - :param last: last step - :type last: str, optional :param first: first step :type first: str, optional + :param last: last step + :type last: str, optional :raises ValueError: for missing or wrong values - :raises RuntimeError: when the needed underlying tool is not found + :raises RuntimeError: error running the needed underlying tool .. note:: valid steps are ``cfg``, ``syn``, ``par`` and ``bit``. """ self.logger.debug('Executing make') if 'part' not in self.data: - self.logger.info('No part specified, using a default value') - if 'top' not in self.data: - self.logger.warning('No top specified') - if 'files' not in self.data: - self.logger.warning('No files specified') - steps = ['cfg', 'syn', 'par', 'bit'] + self.logger.info('Using the default PART') + steps = { + 'cfg': 'Project Creation', + 'syn': 'Synthesis', + 'par': 'Place and Route', + 'bit': 'Bitstream generation' + } if last not in steps: raise ValueError('Invalid last step.') if first not in steps: raise ValueError('Invalid first step.') - first_index = steps.index(first) - last_index = steps.index(last) - if first_index > last_index: + keys = list(steps.keys()) + index = [keys.index(first), keys.index(last)] + if index[0] > index[1]: raise ValueError('Invalid steps combination.') - selected_steps = steps[first_index:last_index + 1] - self._make_prepare([step.upper() for step in selected_steps]) - if not which(self.tool['make-app']): - raise RuntimeError(f'{self.tool["make-app"]} not found.') - self._run(self.tool['make-cmd']) + message = f'from {steps[first]} to {steps[last]}' + if first == last: + message = steps[first] + self.logger.info('Running %s', message) + selected = [step.upper() for step in keys[index[0]:index[1]+1]] + self._run(self._make_prepare(selected)) def prog(self, bitstream=None, position=1): """Program the FPGA @@ -246,16 +247,15 @@ def prog(self, bitstream=None, position=1): :param position: position of the device in the JTAG chain :type position: str, optional :raises ValueError: for missing or wrong values - :raises RuntimeError: when the needed underlying tool is not found + :raises RuntimeError: error running the needed underlying tool """ self.logger.debug('Executing prog') if position not in range(1, 9): raise ValueError('Invalid position.') _ = bitstream self._prog_prepare() - if not which(self.tool['prog-app']): - raise RuntimeError(f'{self.tool["prog-app"]} not found.') - self._run(self.tool['prog-cmd']) + self.logger.info('Programming') + self._run(self._prog_prepare()) def _make_prepare(self, steps): raise NotImplementedError('Tool-dependent') @@ -276,10 +276,7 @@ def _create_file(self, basename, extension, context): file.write(content) def _run(self, command): - self.logger.info( - 'Running the underlying tool (%s)', self.tool['make-app'] - ) - run_error = 0 + error = 0 old_dir = Path.cwd() new_dir = Path(self.odir) start = time() @@ -296,7 +293,7 @@ def _run(self, command): last_lines = lines[-10:] if len(lines) >= 10 else lines for line in last_lines: self.logger.error(line.strip()) - run_error = 1 + error = 1 finally: os.chdir(old_dir) end = time() @@ -307,5 +304,5 @@ def _run(self, command): int((elapsed % 3600) // 60), elapsed % 60 ) - if run_error: - raise RuntimeError('Error running the underlying tool') + if error: + raise RuntimeError('Problem with the underlying tool') diff --git a/pyfpga/vivado.py b/pyfpga/vivado.py index d776b52a..9999f443 100644 --- a/pyfpga/vivado.py +++ b/pyfpga/vivado.py @@ -18,10 +18,6 @@ class Vivado(Project): """Class to support Vivado projects.""" def _make_prepare(self, steps): - self.tool['make-app'] = 'vivado' - self.tool['make-cmd'] = ( - 'vivado -mode batch -notrace -quiet -source vivado.tcl' - ) context = { 'PROJECT': self.name or 'vivado', 'PART': self.data.get('part', 'xc7k160t-3-fbg484') @@ -76,10 +72,8 @@ def _make_prepare(self, steps): for stage in self.data['hooks']: context[stage.upper()] = '\n'.join(self.data['hooks'][stage]) self._create_file('vivado', 'tcl', context) + return 'vivado -mode batch -notrace -quiet -source vivado.tcl' def _prog_prepare(self): # binaries = ['bit'] - self.tool['prog-app'] = 'vivado' - self.tool['prog-cmd'] = ( - 'vivado -mode batch -notrace -quiet -source vivado-prog.tcl' - ) + return 'vivado -mode batch -notrace -quiet -source vivado-prog.tcl' From a2cd4120112b41b33511b3f1a209b693b9f30a83 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sun, 23 Jun 2024 19:55:36 -0300 Subject: [PATCH 090/254] Modified to print the last 20 lines of the log file --- pyfpga/project.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pyfpga/project.py b/pyfpga/project.py index 52106923..5008f3e6 100644 --- a/pyfpga/project.py +++ b/pyfpga/project.py @@ -276,6 +276,7 @@ def _create_file(self, basename, extension, context): file.write(content) def _run(self, command): + NUM = 20 error = 0 old_dir = Path.cwd() new_dir = Path(self.odir) @@ -290,9 +291,11 @@ def _run(self, command): except subprocess.CalledProcessError: with open('run.log', 'r', encoding='utf-8') as file: lines = file.readlines() - last_lines = lines[-10:] if len(lines) >= 10 else lines + last_lines = lines[-NUM:] if len(lines) >= NUM else lines for line in last_lines: - self.logger.error(line.strip()) + message = line.strip() + if len(message): + print(f'>> {message}') error = 1 finally: os.chdir(old_dir) From bbcd7a1808f3e345d723a4530c429bbe69325fac Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sun, 23 Jun 2024 20:36:26 -0300 Subject: [PATCH 091/254] Added new sources, updated the Vivado example --- examples/sources/slog/blink.sv | 23 ++++++++++++++++ examples/sources/slog/include/header.svh | 1 + examples/sources/slog/top.sv | 24 +++++++++++++++++ examples/sources/vhdl/blink.vhdl | 34 ++++++++++++++++++++++++ examples/sources/vhdl/blink_pkg.vhdl | 16 +++++++++++ examples/sources/vhdl/top.vhdl | 25 +++++++++++++++++ examples/sources/vlog/blink.v | 23 ++++++++++++++++ examples/sources/vlog/include/header.vh | 1 + examples/sources/vlog/top.v | 24 +++++++++++++++++ examples/vivado/sources/zybo/clk.xdc | 2 ++ examples/vivado/sources/zybo/led.xdc | 2 ++ examples/vivado/sources/zybo/timing.xdc | 1 + examples/vivado/vivado.py | 20 +++++++++----- 13 files changed, 189 insertions(+), 7 deletions(-) create mode 100644 examples/sources/slog/blink.sv create mode 100644 examples/sources/slog/include/header.svh create mode 100644 examples/sources/slog/top.sv create mode 100644 examples/sources/vhdl/blink.vhdl create mode 100644 examples/sources/vhdl/blink_pkg.vhdl create mode 100644 examples/sources/vhdl/top.vhdl create mode 100644 examples/sources/vlog/blink.v create mode 100644 examples/sources/vlog/include/header.vh create mode 100644 examples/sources/vlog/top.v create mode 100644 examples/vivado/sources/zybo/clk.xdc create mode 100644 examples/vivado/sources/zybo/led.xdc create mode 100644 examples/vivado/sources/zybo/timing.xdc diff --git a/examples/sources/slog/blink.sv b/examples/sources/slog/blink.sv new file mode 100644 index 00000000..b74bfe08 --- /dev/null +++ b/examples/sources/slog/blink.sv @@ -0,0 +1,23 @@ +module Blink #( + parameter int FREQ = 25000000 +)( + input clk_i, + output led_o +); + + localparam int DIV = FREQ; + logic led = 0; + logic [$clog2(DIV)-1:0] cnt = 0; + + always_ff @(posedge clk_i) begin + if (cnt == DIV-1) begin + cnt <= 0; + led <= ~led; + end else begin + cnt <= cnt + 1; + end + end + + assign led_o = led; + +endmodule diff --git a/examples/sources/slog/include/header.svh b/examples/sources/slog/include/header.svh new file mode 100644 index 00000000..78aa9bfd --- /dev/null +++ b/examples/sources/slog/include/header.svh @@ -0,0 +1 @@ +`define INCLUDE diff --git a/examples/sources/slog/top.sv b/examples/sources/slog/top.sv new file mode 100644 index 00000000..24e780e5 --- /dev/null +++ b/examples/sources/slog/top.sv @@ -0,0 +1,24 @@ +`include "header.svh" + +module Top #( + parameter FREQ = 0 +)( + input clk_i, + output led_o +); + + initial begin + if (FREQ==0) begin + $stop("FREQ must be greater than 0"); + $error("FREQ must be greater than 0"); + $fatal("FREQ must be greater than 0"); + end + end + +`ifdef INCLUDE +`ifdef DEFINE + Blink #(.FREQ (FREQ)) dut (.clk_i (clk_i), .led_o (led_o)); +`endif +`endif + +endmodule diff --git a/examples/sources/vhdl/blink.vhdl b/examples/sources/vhdl/blink.vhdl new file mode 100644 index 00000000..9b9a4bc3 --- /dev/null +++ b/examples/sources/vhdl/blink.vhdl @@ -0,0 +1,34 @@ +library IEEE; +use IEEE.std_logic_1164.all; + +entity Blink is + generic ( + FREQ : positive := 25e6 + ); + port ( + clk_i : in std_logic; + led_o : out std_logic + ); +end entity Blink; + +architecture RTL of Blink is + constant DIV : positive := FREQ; + signal led : std_logic := '0'; + signal cnt : natural range 0 to DIV-1 := 0; +begin + + blink_p: process (clk_i) + begin + if rising_edge(clk_i) then + if cnt = DIV-1 then + cnt <= 0; + led <= not led; + else + cnt <= cnt + 1; + end if; + end if; + end process blink_p; + + led_o <= led; + +end architecture RTL; diff --git a/examples/sources/vhdl/blink_pkg.vhdl b/examples/sources/vhdl/blink_pkg.vhdl new file mode 100644 index 00000000..115f0899 --- /dev/null +++ b/examples/sources/vhdl/blink_pkg.vhdl @@ -0,0 +1,16 @@ +library IEEE; +use IEEE.std_logic_1164.all; + +package blink_pkg is + + component Blink is + generic ( + FREQ : natural:=25e6 + ); + port ( + clk_i : in std_logic; + led_o : out std_logic + ); + end component Blink; + +end package blink_pkg; diff --git a/examples/sources/vhdl/top.vhdl b/examples/sources/vhdl/top.vhdl new file mode 100644 index 00000000..0ff6d780 --- /dev/null +++ b/examples/sources/vhdl/top.vhdl @@ -0,0 +1,25 @@ +library IEEE; +use IEEE.std_logic_1164.all; +library blink_lib; +use blink_lib.blink_pkg.all; + +entity Top is + generic ( + FREQ : natural := 0 + ); + port ( + clk_i : in std_logic; + led_o : out std_logic + ); +end entity Top; + +architecture ARCH of Top is +begin + + assert FREQ > 0 report "FREQ must be greater than 0" severity failure; + + blink_i: Blink + generic map (FREQ => FREQ) + port map (clk_i => clk_i, led_o => led_o); + +end architecture ARCH; diff --git a/examples/sources/vlog/blink.v b/examples/sources/vlog/blink.v new file mode 100644 index 00000000..7522e0f7 --- /dev/null +++ b/examples/sources/vlog/blink.v @@ -0,0 +1,23 @@ +module Blink #( + parameter FREQ = 25000000 +)( + input clk_i, + output led_o +); + + localparam DIV = FREQ; + reg led = 0; + reg [$clog2(DIV)-1:0] cnt = 0; + + always @(posedge clk_i) begin + if (cnt == DIV-1) begin + cnt <= 0; + led <= ~led; + end else begin + cnt <= cnt + 1; + end + end + + assign led_o = led; + +endmodule diff --git a/examples/sources/vlog/include/header.vh b/examples/sources/vlog/include/header.vh new file mode 100644 index 00000000..78aa9bfd --- /dev/null +++ b/examples/sources/vlog/include/header.vh @@ -0,0 +1 @@ +`define INCLUDE diff --git a/examples/sources/vlog/top.v b/examples/sources/vlog/top.v new file mode 100644 index 00000000..6c63ebae --- /dev/null +++ b/examples/sources/vlog/top.v @@ -0,0 +1,24 @@ +`include "header.vh" + +module Top #( + parameter FREQ = 0 +)( + input clk_i, + output led_o +); + + initial begin + if (FREQ==0) begin + $stop("FREQ must be greater than 0"); + $error("FREQ must be greater than 0"); + $fatal("FREQ must be greater than 0"); + end + end + +`ifdef INCLUDE +`ifdef DEFINE + Blink #(.FREQ (FREQ)) dut (.clk_i (clk_i), .led_o (led_o)); +`endif +`endif + +endmodule diff --git a/examples/vivado/sources/zybo/clk.xdc b/examples/vivado/sources/zybo/clk.xdc new file mode 100644 index 00000000..877602c7 --- /dev/null +++ b/examples/vivado/sources/zybo/clk.xdc @@ -0,0 +1,2 @@ +set_property PACKAGE_PIN L16 [get_ports clk_i] +set_property IOSTANDARD LVCMOS33 [get_ports clk_i] diff --git a/examples/vivado/sources/zybo/led.xdc b/examples/vivado/sources/zybo/led.xdc new file mode 100644 index 00000000..8106c5cc --- /dev/null +++ b/examples/vivado/sources/zybo/led.xdc @@ -0,0 +1,2 @@ +set_property PACKAGE_PIN M14 [get_ports led_o] +set_property IOSTANDARD LVCMOS33 [get_ports led_o] diff --git a/examples/vivado/sources/zybo/timing.xdc b/examples/vivado/sources/zybo/timing.xdc new file mode 100644 index 00000000..be3d63fc --- /dev/null +++ b/examples/vivado/sources/zybo/timing.xdc @@ -0,0 +1 @@ +create_clock -name clk_i -period 8 [get_ports clk_i] diff --git a/examples/vivado/vivado.py b/examples/vivado/vivado.py index 497734d3..9f9ed802 100644 --- a/examples/vivado/vivado.py +++ b/examples/vivado/vivado.py @@ -9,7 +9,7 @@ '--action', choices=['make', 'prog', 'all'], default='make' ) parser.add_argument( - '--source', choices=['vhdl', 'vlog', 'design'], default='vhdl' + '--source', choices=['vlog', 'vhdl', 'slog', 'design'], default='vlog' ) args = parser.parse_args() @@ -18,13 +18,19 @@ prj.add_param('FREQ', '125000000') if args.source == 'vhdl': - prj.add_vhdl('../resources/vhdl/blink.vhdl') + prj.add_vhdl('../sources/vhdl/*.vhdl', 'blink_lib') if args.source == 'vlog': - prj.add_vlog('../resources/vlog/blink.v') -prj.add_constraint('../resources/constraints/zybo/timing.xdc', 'syn') -prj.add_constraint('../resources/constraints/zybo/clk.xdc', 'par') -prj.add_constraint('../resources/constraints/zybo/led.xdc', 'par') -prj.set_top('Blink') + prj.add_include('../sources/vlog/include') + prj.add_vlog('../sources/vlog/*.v') +if args.source == 'slog': + prj.add_include('../sources/slog/include') + prj.add_vlog('../sources/slog/*.sv') +if args.source in ['vlog', 'slog']: + prj.add_define('DEFINE', '1') +prj.add_constraint('sources/zybo/timing.xdc', 'syn') +prj.add_constraint('sources/zybo/clk.xdc', 'par') +prj.add_constraint('sources/zybo/led.xdc', 'par') +prj.set_top('Top') if args.action in ['make', 'all']: prj.make() From 3a4d7173d4dde4fe6a489110ddc3f67205346c05 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sun, 23 Jun 2024 21:14:01 -0300 Subject: [PATCH 092/254] Implemented programming with Vivado --- pyfpga/ise.py | 2 +- pyfpga/libero.py | 2 +- pyfpga/openflow.py | 2 +- pyfpga/project.py | 10 ++++------ pyfpga/quartus.py | 2 +- pyfpga/vivado.py | 10 ++++++++-- 6 files changed, 16 insertions(+), 12 deletions(-) diff --git a/pyfpga/ise.py b/pyfpga/ise.py index 72e577bc..d5289930 100644 --- a/pyfpga/ise.py +++ b/pyfpga/ise.py @@ -24,7 +24,7 @@ def _make_prepare(self, steps): self.tool['make-app'] = 'xtclsh' self.tool['make-cmd'] = 'xtclsh ise.tcl' - def _prog_prepare(self): + def _prog_prepare(self, bitstream, position): # binaries = ['bit'] self.tool['prog-app'] = 'impact' self.tool['prog-cmd'] = 'impact -batch impact-prog' diff --git a/pyfpga/libero.py b/pyfpga/libero.py index 35d4d89f..a717f1b7 100644 --- a/pyfpga/libero.py +++ b/pyfpga/libero.py @@ -24,7 +24,7 @@ def _make_prepare(self, steps): self.tool['make-app'] = 'libero' self.tool['make-cmd'] = 'libero SCRIPT:libero.tcl' - def _prog_prepare(self): + def _prog_prepare(self, bitstream, position): # binaries = ['bit'] self.tool['prog-app'] = '' self.tool['prog-cmd'] = '' diff --git a/pyfpga/openflow.py b/pyfpga/openflow.py index cb756727..0b7865ba 100644 --- a/pyfpga/openflow.py +++ b/pyfpga/openflow.py @@ -22,7 +22,7 @@ def _make_prepare(self, steps): self.tool['make-app'] = 'docker' self.tool['make-cmd'] = 'bash openflow.sh' - def _prog_prepare(self): + def _prog_prepare(self, bitstream, position): # binaries = ['bit'] self.tool['prog-app'] = 'docker' self.tool['prog-cmd'] = 'bash openflow-prog.sh' diff --git a/pyfpga/project.py b/pyfpga/project.py index 5008f3e6..951b1c3f 100644 --- a/pyfpga/project.py +++ b/pyfpga/project.py @@ -252,15 +252,13 @@ def prog(self, bitstream=None, position=1): self.logger.debug('Executing prog') if position not in range(1, 9): raise ValueError('Invalid position.') - _ = bitstream - self._prog_prepare() self.logger.info('Programming') - self._run(self._prog_prepare()) + self._run(self._prog_prepare(bitstream, position)) def _make_prepare(self, steps): raise NotImplementedError('Tool-dependent') - def _prog_prepare(self): + def _prog_prepare(self, bitstream, position): raise NotImplementedError('Tool-dependent') def _create_file(self, basename, extension, context): @@ -276,7 +274,7 @@ def _create_file(self, basename, extension, context): file.write(content) def _run(self, command): - NUM = 20 + num = 20 error = 0 old_dir = Path.cwd() new_dir = Path(self.odir) @@ -291,7 +289,7 @@ def _run(self, command): except subprocess.CalledProcessError: with open('run.log', 'r', encoding='utf-8') as file: lines = file.readlines() - last_lines = lines[-NUM:] if len(lines) >= NUM else lines + last_lines = lines[-num:] if len(lines) >= num else lines for line in last_lines: message = line.strip() if len(message): diff --git a/pyfpga/quartus.py b/pyfpga/quartus.py index c635ffa5..4ca2bf78 100644 --- a/pyfpga/quartus.py +++ b/pyfpga/quartus.py @@ -24,7 +24,7 @@ def _make_prepare(self, steps): self.tool['make-app'] = 'quartus_sh' self.tool['make-cmd'] = 'quartus_sh --script quartus.tcl' - def _prog_prepare(self): + def _prog_prepare(self, bitstream, position): # binaries = ['sof', 'pof'] self.tool['prog-app'] = 'quartus_pgm' self.tool['prog-cmd'] = 'bash quartus-prog.sh' diff --git a/pyfpga/vivado.py b/pyfpga/vivado.py index 9999f443..3470123e 100644 --- a/pyfpga/vivado.py +++ b/pyfpga/vivado.py @@ -11,6 +11,7 @@ # pylint: disable=too-many-locals # pylint: disable=too-many-branches +from pathlib import Path from pyfpga.project import Project @@ -74,6 +75,11 @@ def _make_prepare(self, steps): self._create_file('vivado', 'tcl', context) return 'vivado -mode batch -notrace -quiet -source vivado.tcl' - def _prog_prepare(self): - # binaries = ['bit'] + def _prog_prepare(self, bitstream, position): + _ = position # Not needed for Vivado + if not bitstream: + basename = self.name or 'vivado' + bitstream = Path(self.odir).resolve() / f'{basename}.bit' + context = {'BITSTREAM': bitstream} + self._create_file('vivado-prog', 'tcl', context) return 'vivado -mode batch -notrace -quiet -source vivado-prog.tcl' From 2bb960ce888a6f5fe43970e6a47281ed9cebf578 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sun, 23 Jun 2024 21:17:32 -0300 Subject: [PATCH 093/254] ci: added to install pyfpga and prepare mock-ups --- .github/workflows/test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 061381db..40699986 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,6 +22,6 @@ jobs: with: python-version: ${{ matrix.pyver }} - name: Install dependencies - run: pip install pytest + run: pip install . && pip install pytest - name: Run tests - run: make test + run: source tests/mocks/source-me.sh && make test From eb7c046f353094bd0242eaf199bf05d19c7ae8f3 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sun, 23 Jun 2024 21:21:29 -0300 Subject: [PATCH 094/254] Fixed/updated package name --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index e6b45d1b..9018d2d7 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,6 @@ from setuptools import setup, find_packages -import fpga +import pyfpga with open('README.md', 'r') as fh: long_description = fh.read() From 4177a8b70e1afe98f2c17840b384d7b2c8a6561c Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sun, 23 Jun 2024 21:31:11 -0300 Subject: [PATCH 095/254] Fixed/updated package name, added jinja2 as dependency, deprecated Python 3.7 --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 9018d2d7..a3eaab1d 100644 --- a/setup.py +++ b/setup.py @@ -30,7 +30,6 @@ 'Intended Audience :: Developers', 'License :: OSI Approved :: GNU General Public License v3 (GPLv3)', 'Operating System :: OS Independent', - 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', @@ -39,5 +38,6 @@ 'Topic :: Utilities', 'Topic :: Software Development :: Build Tools', "Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)" - ] + ], + install_requires=['jinja2'] ) From 80cfbce1aec28dfef14330ceb8dd63d312d2e13e Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sun, 23 Jun 2024 22:43:25 -0300 Subject: [PATCH 096/254] Updated the header of the mocks --- tests/mocks/impact | 21 +++++---------- tests/mocks/libero | 21 ++++----------- tests/mocks/quartus_pgm | 21 +++++---------- tests/mocks/quartus_sh | 21 ++++----------- tests/mocks/vivado | 57 ++++++++++++++++------------------------- tests/mocks/xtclsh | 21 ++++----------- 6 files changed, 51 insertions(+), 111 deletions(-) diff --git a/tests/mocks/impact b/tests/mocks/impact index 17eee4a6..0fbbacbc 100755 --- a/tests/mocks/impact +++ b/tests/mocks/impact @@ -1,23 +1,14 @@ #!/usr/bin/env python3 + # -# Copyright (C) 2022 Rodrigo A. Melo -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# Copyright (C) 2022-2024 Rodrigo A. Melo # -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . +# SPDX-License-Identifier: GPL-3.0-or-later # import argparse + parser = argparse.ArgumentParser() parser.add_argument('-batch', action='store_true', required=True) @@ -25,4 +16,6 @@ parser.add_argument('source') args = parser.parse_args() -print(f'INFO:the {parser.prog.upper()} mock has been executed') +tool = parser.prog + +print(f'INFO:the {tool.upper()} mock has been executed') diff --git a/tests/mocks/libero b/tests/mocks/libero index b0f0b691..a4663e9d 100755 --- a/tests/mocks/libero +++ b/tests/mocks/libero @@ -1,19 +1,9 @@ #!/usr/bin/env python3 + # -# Copyright (C) 2022 Rodrigo A. Melo -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# Copyright (C) 2022-2024 Rodrigo A. Melo # -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . +# SPDX-License-Identifier: GPL-3.0-or-later # import argparse @@ -34,7 +24,7 @@ if not args.source.startswith("SCRIPT:", 0): sys.exit(1) tcl = f''' -proc unknown {{ cmmd args }} {{ }} +proc unknown args {{ }} source {args.source.replace('SCRIPT:', '')} ''' @@ -46,8 +36,7 @@ subprocess.run( f'tclsh {tool}-mock.tcl', shell=True, check=True, - universal_newlines=True, - #stdout=output, stderr=subprocess.STDOUT + universal_newlines=True ) print(f'INFO:the {tool.upper()} mock has been executed') diff --git a/tests/mocks/quartus_pgm b/tests/mocks/quartus_pgm index 3353ee46..8993ca10 100755 --- a/tests/mocks/quartus_pgm +++ b/tests/mocks/quartus_pgm @@ -1,23 +1,14 @@ #!/usr/bin/env python3 + # -# Copyright (C) 2022 Rodrigo A. Melo -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# Copyright (C) 2022-2024 Rodrigo A. Melo # -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . +# SPDX-License-Identifier: GPL-3.0-or-later # import argparse + parser = argparse.ArgumentParser() parser.add_argument('-c', required=True) @@ -26,4 +17,6 @@ parser.add_argument('-o', required=True) args = parser.parse_args() -print(f'INFO:the {parser.prog.upper()} mock has been executed') +tool = parser.prog + +print(f'INFO:the {tool.upper()} mock has been executed') diff --git a/tests/mocks/quartus_sh b/tests/mocks/quartus_sh index a002e73d..64a8cf65 100755 --- a/tests/mocks/quartus_sh +++ b/tests/mocks/quartus_sh @@ -1,19 +1,9 @@ #!/usr/bin/env python3 + # -# Copyright (C) 2022 Rodrigo A. Melo -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# Copyright (C) 2022-2024 Rodrigo A. Melo # -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . +# SPDX-License-Identifier: GPL-3.0-or-later # import argparse @@ -32,7 +22,7 @@ tool = parser.prog tcl = f''' lappend auto_path pkg -proc unknown {{ cmmd args }} {{ }} +proc unknown args {{ }} source {args.script} ''' @@ -60,8 +50,7 @@ subprocess.run( f'tclsh {tool}-mock.tcl', shell=True, check=True, - universal_newlines=True, - #stdout=output, stderr=subprocess.STDOUT + universal_newlines=True ) print(f'INFO:the {tool.upper()} mock has been executed') diff --git a/tests/mocks/vivado b/tests/mocks/vivado index af5614d1..f6a47eff 100755 --- a/tests/mocks/vivado +++ b/tests/mocks/vivado @@ -1,19 +1,9 @@ #!/usr/bin/env python3 + # -# Copyright (C) 2022 Rodrigo A. Melo -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# Copyright (C) 2022-2024 Rodrigo A. Melo # -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . +# SPDX-License-Identifier: GPL-3.0-or-later # import argparse @@ -31,28 +21,26 @@ args = parser.parse_args() tool = parser.prog - -#proc create_project {{ args }} {{ }} -#proc open_project {{ args }} {{ }} -#proc current_project {{ args }} {{ }} -#proc current_fileset {{ args }} {{ }} -#proc get_filesets {{ args }} {{ }} -#proc set_property {{ args }} {{ }} -#proc add_files {{ args }} {{ }} -#proc get_files {{ args }} {{ }} -#proc reset_run {{ args }} {{ }} -#proc launch_runs {{ args }} {{ }} -#proc get_runs {{ args }} {{ }} -#proc wait_on_run {{ args }} {{ }} -#proc open_run {{ args }} {{ }} -#proc write_bitstream {{ args }} {{ }} -#proc close_project {{ args }} {{ }} -#proc current_bd_design {{ args }} {{ }} -#proc get_bd_cells {{ args }} {{ }} - +#proc create_project args {{ }} +#proc open_project args {{ }} +#proc current_project args {{ }} +#proc current_fileset args {{ }} +#proc get_filesets args {{ }} +#proc set_property args {{ }} +#proc add_files args {{ }} +#proc get_files args {{ }} +#proc reset_run args {{ }} +#proc launch_runs args {{ }} +#proc get_runs args {{ }} +#proc wait_on_run args {{ }} +#proc open_run args {{ }} +#proc write_bitstream args {{ }} +#proc close_project args {{ }} +#proc current_bd_design args {{ }} +#proc get_bd_cells args {{ }} tcl = f''' -proc unknown {{ cmmd args }} {{ }} +proc unknown args {{ }} source {args.source} ''' @@ -64,8 +52,7 @@ subprocess.run( f'tclsh {tool}-mock.tcl', shell=True, check=True, - universal_newlines=True, - #stdout=output, stderr=subprocess.STDOUT + universal_newlines=True ) print(f'INFO:the {tool.upper()} mock has been executed') diff --git a/tests/mocks/xtclsh b/tests/mocks/xtclsh index 12046fa3..7621c4d9 100755 --- a/tests/mocks/xtclsh +++ b/tests/mocks/xtclsh @@ -1,19 +1,9 @@ #!/usr/bin/env python3 + # -# Copyright (C) 2022 Rodrigo A. Melo -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# Copyright (C) 2022-2024 Rodrigo A. Melo # -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . +# SPDX-License-Identifier: GPL-3.0-or-later # import argparse @@ -29,7 +19,7 @@ args = parser.parse_args() tool = parser.prog tcl = f''' -proc unknown {{ cmmd args }} {{ }} +proc unknown args {{ }} source {args.source} ''' @@ -41,8 +31,7 @@ subprocess.run( f'tclsh {tool}-mock.tcl', shell=True, check=True, - universal_newlines=True, - #stdout=output, stderr=subprocess.STDOUT + universal_newlines=True ) print(f'INFO:the {tool.upper()} mock has been executed') From 57149f80b2f2ff8aeb30a68a3439721551427083 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sun, 23 Jun 2024 23:00:04 -0300 Subject: [PATCH 097/254] Updated README.md --- README.md | 29 +++++++++-------------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 3a60df40..1294d710 100644 --- a/README.md +++ b/README.md @@ -14,33 +14,22 @@ With PyFPGA, you can create your own FPGA development workflow tailored to your Some of its benefits are: * It provides a unified API between tools/devices. -* It's **Version Control Systems** and **Continuous Integration friendly**. +* It's **Version Control Systems** and **Continuous Integration** friendly. * It ensures reproducibility and repeatability. * It consumes fewer system resources than GUI-based workflows. ## Basic example ```py -from fpga import Project +from pyfpga import Vivado -# Specify the backend tool and an optional project name -prj = Project('vivado', 'example') - -# Set the device/part +prj = Vivado('example') prj.set_part('xc7z010-1-clg400') - -# Add HDL sources to the project -prj.add_files('location1/*.v') -prj.add_files('location2/top.v') - -# Optionally add constraint files to the project -prj.add_files('location3/example.xdc') - -# Set the top-level unit name +prj.add_vlog('location1/*.v') +prj.add_vlog('location2/top.v') +prj.add_constraint('location3/example.xdc') prj.set_top('Top') - -# Generate the bitstream running the tool -prj.generate() +prj.make() ``` The next steps are to read the [docs](https://pyfpga.github.io/pyfpga) or take a look at [examples](examples). @@ -54,11 +43,11 @@ For a comprehensive list of supported tools, features and limitations, please re > **NOTE:** > PyFPGA assumes that the underlying tools required for operation are ready to be executed from the running terminal. -> This includes having the tools installed, properly configured, and licensed (when needed). +> This includes having the tools installed, properly configured and licensed (when needed). ## Installation -PyFPGA requires Python>=3.7. +PyFPGA requires Python>=3.8. At the moment, it's only available as a git repository hosted on GitHub. It can be installed with pip: From 33ea70b889caa212d1f04fa5f888045709561df1 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sun, 23 Jun 2024 23:04:55 -0300 Subject: [PATCH 098/254] Renamed add_constraint as add_cons --- README.md | 2 +- examples/vivado/vivado.py | 6 +++--- pyfpga/project.py | 4 ++-- tests/test_data.py | 6 +++--- tests/test_vivado.py | 6 +++--- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 1294d710..aa17c849 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ prj = Vivado('example') prj.set_part('xc7z010-1-clg400') prj.add_vlog('location1/*.v') prj.add_vlog('location2/top.v') -prj.add_constraint('location3/example.xdc') +prj.add_cons('location3/example.xdc') prj.set_top('Top') prj.make() ``` diff --git a/examples/vivado/vivado.py b/examples/vivado/vivado.py index 9f9ed802..930e62cd 100644 --- a/examples/vivado/vivado.py +++ b/examples/vivado/vivado.py @@ -27,9 +27,9 @@ prj.add_vlog('../sources/slog/*.sv') if args.source in ['vlog', 'slog']: prj.add_define('DEFINE', '1') -prj.add_constraint('sources/zybo/timing.xdc', 'syn') -prj.add_constraint('sources/zybo/clk.xdc', 'par') -prj.add_constraint('sources/zybo/led.xdc', 'par') +prj.add_cons('sources/zybo/timing.xdc', 'syn') +prj.add_cons('sources/zybo/clk.xdc', 'par') +prj.add_cons('sources/zybo/led.xdc', 'par') prj.set_top('Top') if args.action in ['make', 'all']: diff --git a/pyfpga/project.py b/pyfpga/project.py index 951b1c3f..179ef179 100644 --- a/pyfpga/project.py +++ b/pyfpga/project.py @@ -114,7 +114,7 @@ def add_vlog(self, pathname): self.logger.debug('Executing add_vlog') self._add_file(pathname, 'vlog') - def add_constraint(self, path, when='all'): + def add_cons(self, path, when='all'): """Add a constraint file. :param pathname: path of a file @@ -123,7 +123,7 @@ def add_constraint(self, path, when='all'): :type only: str, optional :raises FileNotFoundError: if path is not found """ - self.logger.debug('Executing add_constraint') + self.logger.debug('Executing add_cons') path = Path(path).resolve() if not path.is_file(): raise FileNotFoundError(path) diff --git a/tests/test_data.py b/tests/test_data.py index c4a355c0..69446951 100644 --- a/tests/test_data.py +++ b/tests/test_data.py @@ -70,9 +70,9 @@ def test_data(): prj.add_slog('fakedata/**/*.sv') prj.add_vhdl('fakedata/**/*.vhdl', 'LIB') prj.add_vlog('fakedata/**/*.v') - prj.add_constraint('fakedata/cons/all.xdc') - prj.add_constraint('fakedata/cons/syn.xdc', 'syn') - prj.add_constraint('fakedata/cons/par.xdc', 'par') + prj.add_cons('fakedata/cons/all.xdc') + prj.add_cons('fakedata/cons/syn.xdc', 'syn') + prj.add_cons('fakedata/cons/par.xdc', 'par') prj.add_param('PAR1', 'VAL1') prj.add_param('PAR2', 'VAL2') prj.add_param('PAR3', 'VAL3') diff --git a/tests/test_vivado.py b/tests/test_vivado.py index 9afa353d..787e90cb 100644 --- a/tests/test_vivado.py +++ b/tests/test_vivado.py @@ -11,9 +11,9 @@ def test_vivado(): prj.add_slog('fakedata/**/*.sv') prj.add_vhdl('fakedata/**/*.vhdl', 'LIB') prj.add_vlog('fakedata/**/*.v') - prj.add_constraint('fakedata/cons/all.xdc') - prj.add_constraint('fakedata/cons/syn.xdc', 'syn') - prj.add_constraint('fakedata/cons/par.xdc', 'par') + prj.add_cons('fakedata/cons/all.xdc') + prj.add_cons('fakedata/cons/syn.xdc', 'syn') + prj.add_cons('fakedata/cons/par.xdc', 'par') prj.add_param('PAR1', 'VAL1') prj.add_param('PAR2', 'VAL2') prj.add_define('DEF1', 'VAL1') From cefcc9a18876bf7499d23f0bb07cbb94288a1108 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Mon, 24 Jun 2024 19:34:32 -0300 Subject: [PATCH 099/254] Removed support for VHDL architectures --- docs/internals.rst | 1 - pyfpga/project.py | 9 --------- pyfpga/templates/vivado.jinja | 4 ---- pyfpga/vivado.py | 2 -- tests/test_data.py | 2 -- tests/test_vivado.py | 1 - 6 files changed, 19 deletions(-) diff --git a/docs/internals.rst b/docs/internals.rst index 910df99a..bcaa371f 100644 --- a/docs/internals.rst +++ b/docs/internals.rst @@ -60,7 +60,6 @@ Internal data structure 'DEF2': 'VAL2', 'DEF3': 'VAL3' }, - 'arch': 'ARCHNAME', 'hooks': { 'precfg': ['CMD1', 'CMD2'], 'postcfg': ['CMD1', 'CMD2'], diff --git a/pyfpga/project.py b/pyfpga/project.py index 179ef179..faaacffe 100644 --- a/pyfpga/project.py +++ b/pyfpga/project.py @@ -153,15 +153,6 @@ def add_define(self, name, value): self.logger.debug('Executing add_define') self.data.setdefault('defines', {})[name] = value - def set_arch(self, name): - """Set the VHDL architecture. - - :param name: architecture name - :type name: str - """ - self.logger.debug('Executing set_arch') - self.data['arch'] = name - def add_fileset(self, pathname): """Add fileset file/s. diff --git a/pyfpga/templates/vivado.jinja b/pyfpga/templates/vivado.jinja index 05b1f0fa..2c593f69 100644 --- a/pyfpga/templates/vivado.jinja +++ b/pyfpga/templates/vivado.jinja @@ -32,10 +32,6 @@ set_property INCLUDE_DIRS { {{ INCLUDES}} } [current_fileset] set_property GENERIC { {{ PARAMS }} } -objects [get_filesets sources_1] {% endif %} -{% if ARCH %} -set_property TOP_ARCH {{ ARCH }} [current_fileset] -{% endif %} - {{ POSTCFG }} close_project diff --git a/pyfpga/vivado.py b/pyfpga/vivado.py index 3470123e..c0a625e6 100644 --- a/pyfpga/vivado.py +++ b/pyfpga/vivado.py @@ -67,8 +67,6 @@ def _make_prepare(self, steps): for key, value in self.data['params'].items(): params.append(f'{key}={value}') context['PARAMS'] = ' '.join(params) - if 'arch' in self.data: - context['ARCH'] = self.data['arch'] if 'hooks' in self.data: for stage in self.data['hooks']: context[stage.upper()] = '\n'.join(self.data['hooks'][stage]) diff --git a/tests/test_data.py b/tests/test_data.py index 69446951..250ea793 100644 --- a/tests/test_data.py +++ b/tests/test_data.py @@ -45,7 +45,6 @@ 'DEF2': 'VAL2', 'DEF3': 'VAL3' }, - 'arch': 'ARCHNAME', 'hooks': { 'precfg': ['CMD1', 'CMD2'], 'postcfg': ['CMD1', 'CMD2'], @@ -63,7 +62,6 @@ def test_data(): prj = Project() prj.set_part('PARTNAME') prj.set_top('TOPNAME') - prj.set_arch('ARCHNAME') prj.add_include('fakedata/dir1') prj.add_include('fakedata/dir2') prj.add_include('fakedata/dir3') diff --git a/tests/test_vivado.py b/tests/test_vivado.py index 787e90cb..57781a09 100644 --- a/tests/test_vivado.py +++ b/tests/test_vivado.py @@ -5,7 +5,6 @@ def test_vivado(): prj = Vivado() prj.set_part('PARTNAME') prj.set_top('TOPNAME') - prj.set_arch('ARCHNAME') prj.add_include('fakedata/dir1') prj.add_include('fakedata/dir2') prj.add_slog('fakedata/**/*.sv') From 9584c4936f4c40689e47acf9df24513696ff940f Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Mon, 24 Jun 2024 19:41:19 -0300 Subject: [PATCH 100/254] Removed unused files --- pyfpga/factory.py | 44 ------------------- pyfpga/tool.py | 105 ---------------------------------------------- 2 files changed, 149 deletions(-) delete mode 100644 pyfpga/factory.py delete mode 100644 pyfpga/tool.py diff --git a/pyfpga/factory.py b/pyfpga/factory.py deleted file mode 100644 index 6236730e..00000000 --- a/pyfpga/factory.py +++ /dev/null @@ -1,44 +0,0 @@ -# -# Copyright (C) 2024 Rodrigo A. Melo -# -# SPDX-License-Identifier: GPL-3.0-or-later -# - -""" -A factory class to create FPGA projects. -""" - -from pyfpga.ise import Ise -from pyfpga.libero import Libero -from pyfpga.openflow import Openflow -from pyfpga.quartus import Quartus -from pyfpga.vivado import Vivado - -# pylint: disable=return-in-init -# pylint: disable=no-else-return -# pylint: disable=too-many-return-statements -# pylint: disable=too-few-public-methods - - -class Factory: - """A factory class to create FPGA projects.""" - - def __init__( - self, tool='vivado', project=None): - """Class constructor.""" - if tool == 'ghdl': - return Openflow(project) # , frontend='ghdl', backend='vhdl') - elif tool == 'ise': - return Ise(project) - elif tool == 'libero': - return Libero(project) - elif tool == 'openflow': - return Openflow(project) - elif tool == 'quartus': - return Quartus(project) - elif tool == 'vivado': - return Vivado(project) - elif tool == 'yosys': - return Openflow(project) # , frontend='yosys', backend='verilog') - else: - raise NotImplementedError(tool) diff --git a/pyfpga/tool.py b/pyfpga/tool.py deleted file mode 100644 index 30309ad0..00000000 --- a/pyfpga/tool.py +++ /dev/null @@ -1,105 +0,0 @@ -# -# Copyright (C) 2019-2024 Rodrigo A. Melo -# -# SPDX-License-Identifier: GPL-3.0-or-later -# - -""" -Defines the interface to be inherited to support a tool. -""" - -# def tcl_path(path): -# """Returns a Tcl suitable path.""" -# return path.replace(os.path.sep, "/") - -# pylint: disable=too-few-public-methods - - -class Tool: - """Tool interface.""" - -# def _create_gen_script(self, tasks): -# """Create the script for generate execution.""" -# # Paths and files -# files = [] -# if self.presynth: -# files.append(f' fpga_file {self.project}.edif') -# else: -# for path in self.paths: -# files.append(f' fpga_include {tcl_path(path)}') -# for file in self.files['verilog']: -# files.append(f' fpga_file {tcl_path(file[0])}') -# for file in self.files['vhdl']: -# if file[1] is None: -# files.append(f' fpga_file {tcl_path(file[0])}') -# else: -# files.append( -# f' fpga_file {tcl_path(file[0])} {file[1]}' -# ) -# for file in self.files['design']: -# files.append(f' fpga_design {tcl_path(file[0])}') -# for file in self.files['constraint']: -# files.append(f' fpga_file {tcl_path(file[0])}') -# # Parameters -# params = [] -# for param in self.params: -# params.append(f'{{ {param[0]} {param[1]} }}') -# # Script creation -# template = os.path.join(os.path.dirname(__file__), 'template.tcl') -# with open(template, 'r', encoding='utf-8') as file: -# tcl = file.read() -# tcl = tcl.replace('#TOOL#', self._TOOL) -# tcl = tcl.replace('#PRESYNTH#', "True" if self.presynth else "False") -# tcl = tcl.replace('#PROJECT#', self.project) -# tcl = tcl.replace('#PART#', self.part['name']) -# tcl = tcl.replace('#FAMILY#', self.part['family']) -# tcl = tcl.replace('#DEVICE#', self.part['device']) -# tcl = tcl.replace('#PACKAGE#', self.part['package']) -# tcl = tcl.replace('#SPEED#', self.part['speed']) -# tcl = tcl.replace('#PARAMS#', ' '.join(params)) -# tcl = tcl.replace('#FILES#', '\n'.join(files)) -# tcl = tcl.replace('#TOP#', self.top) -# tcl = tcl.replace('#TASKS#', tasks) -# tcl = tcl.replace('#PREFILE_CMDS#', '\n'.join(self.cmds['prefile'])) -# tcl = tcl.replace('#PROJECT_CMDS#', '\n'.join(self.cmds['project'])) -# tcl = tcl.replace('#PREFLOW_CMDS#', '\n'.join(self.cmds['preflow'])) -# tcl = tcl.replace('#POSTSYN_CMDS#', '\n'.join(self.cmds['postsyn'])) -# tcl = tcl.replace('#POSTPAR_CMDS#', '\n'.join(self.cmds['postpar'])) -# tcl = tcl.replace('#POSTBIT_CMDS#', '\n'.join(self.cmds['postbit'])) -# with open(f'{self._TOOL}.tcl', 'w', encoding='utf-8') as file: -# file.write(tcl) - -# def generate(self, to_task, from_task, capture): -# """Run the FPGA tool.""" -# check_value(to_task, TASKS) -# check_value(from_task, TASKS) -# to_index = TASKS.index(to_task) -# from_index = TASKS.index(from_task) -# if from_index > to_index: -# raise ValueError( -# f'initial task "{from_task}" cannot be later than the ' + -# f'last task "{to_task}"' -# ) -# tasks = " ".join(TASKS[from_index:to_index+1]) -# self._create_gen_script(tasks) -# if not which(self._GEN_PROGRAM): -# raise RuntimeError(f'program "{self._GEN_PROGRAM}" not found') -# return run(self._GEN_COMMAND, capture) - -# def transfer(self, devtype, position, part, width, capture): -# """Transfer a bitstream.""" -# if not which(self._TRF_PROGRAM): -# raise RuntimeError(f'program "{self._TRF_PROGRAM}" not found') -# check_value(devtype, self._DEVTYPES) -# check_value(position, range(10)) -# isinstance(part, str) -# check_value(width, MEMWIDTHS) -# isinstance(capture, bool) -# # Bitstream autodiscovery -# if not self.bitstream and devtype not in ['detect', 'unlock']: -# bitstream = [] -# for ext in self._BIT_EXT: -# bitstream.extend(glob(f'**/*.{ext}', recursive=True)) -# if len(bitstream) == 0: -# raise FileNotFoundError('bitStream not found') -# self.bitstream = bitstream[0] From b04a22290fc811f210b3416186bfbff9a714f5d9 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Mon, 24 Jun 2024 19:43:54 -0300 Subject: [PATCH 101/254] Moved the zybo constraints from the Vivado example to sources --- examples/{vivado => }/sources/zybo/clk.xdc | 0 examples/{vivado => }/sources/zybo/led.xdc | 0 examples/{vivado => }/sources/zybo/timing.xdc | 0 examples/vivado/vivado.py | 6 +++--- 4 files changed, 3 insertions(+), 3 deletions(-) rename examples/{vivado => }/sources/zybo/clk.xdc (100%) rename examples/{vivado => }/sources/zybo/led.xdc (100%) rename examples/{vivado => }/sources/zybo/timing.xdc (100%) diff --git a/examples/vivado/sources/zybo/clk.xdc b/examples/sources/zybo/clk.xdc similarity index 100% rename from examples/vivado/sources/zybo/clk.xdc rename to examples/sources/zybo/clk.xdc diff --git a/examples/vivado/sources/zybo/led.xdc b/examples/sources/zybo/led.xdc similarity index 100% rename from examples/vivado/sources/zybo/led.xdc rename to examples/sources/zybo/led.xdc diff --git a/examples/vivado/sources/zybo/timing.xdc b/examples/sources/zybo/timing.xdc similarity index 100% rename from examples/vivado/sources/zybo/timing.xdc rename to examples/sources/zybo/timing.xdc diff --git a/examples/vivado/vivado.py b/examples/vivado/vivado.py index 930e62cd..99ec2745 100644 --- a/examples/vivado/vivado.py +++ b/examples/vivado/vivado.py @@ -27,9 +27,9 @@ prj.add_vlog('../sources/slog/*.sv') if args.source in ['vlog', 'slog']: prj.add_define('DEFINE', '1') -prj.add_cons('sources/zybo/timing.xdc', 'syn') -prj.add_cons('sources/zybo/clk.xdc', 'par') -prj.add_cons('sources/zybo/led.xdc', 'par') +prj.add_cons('../sources/zybo/timing.xdc', 'syn') +prj.add_cons('../sources/zybo/clk.xdc', 'par') +prj.add_cons('../sources/zybo/led.xdc', 'par') prj.set_top('Top') if args.action in ['make', 'all']: From 8fcf3d700767f8713d18c051c2ad1d68ec687fc7 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Mon, 24 Jun 2024 20:28:50 -0300 Subject: [PATCH 102/254] Simplified Vivado example content --- examples/vivado/Makefile | 6 - examples/vivado/README.md | 31 -- examples/vivado/design.py | 33 -- examples/vivado/design.tcl | 608 -------------------------- examples/vivado/{vivado.py => run.py} | 0 examples/vivado/run.sh | 10 + 6 files changed, 10 insertions(+), 678 deletions(-) delete mode 100644 examples/vivado/Makefile delete mode 100644 examples/vivado/README.md delete mode 100644 examples/vivado/design.py delete mode 100644 examples/vivado/design.tcl rename examples/vivado/{vivado.py => run.py} (100%) create mode 100644 examples/vivado/run.sh diff --git a/examples/vivado/Makefile b/examples/vivado/Makefile deleted file mode 100644 index f166a9f9..00000000 --- a/examples/vivado/Makefile +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/make - -SCRIPTS = $(wildcard *.py) - -all: - @$(foreach SCRIPT, $(SCRIPTS), python3 $(SCRIPT) || exit;) diff --git a/examples/vivado/README.md b/examples/vivado/README.md deleted file mode 100644 index 42c3fbf0..00000000 --- a/examples/vivado/README.md +++ /dev/null @@ -1,31 +0,0 @@ -# Vivado examples - -The Zybo related things are the FPGA part and the constraints file. -Modify them to make this example compatible with another board. - -* `python3 vivado.py` generates a bitstream based on VHDL files. -* `python3 vivado.py --action transfer` to transfer the generated bitstream to the board. -* `python3 design.py` generates the bitstream based on a Vivado Block design. - -## How to get a compatible Vivado Block Design - -* In the *Flow Navigator* panel, under *IP INTEGRATOR*, click on *Create Block Design*. -* Work on your design. -* *File* -> *Export* -> *Export Block Design* to create a Tcl file to re-generate your design. - -> Tip: open the generated Tcl file and remove the following code (which is generally a problem instead of help): - -```tcl -################################################################ -# Check if script is running in correct Vivado version. -################################################################ -set scripts_vivado_version 2019.2 -set current_vivado_version [version -short] - -if { [string first $scripts_vivado_version $current_vivado_version] == -1 } { - puts "" - catch {common::send_msg_id "BD_TCL-109" "ERROR" "This script was generated using Vivado <$scripts_vivado_version> and is being run in <$current_vivado_version> of Vivado. Please run the script in Vivado <$scripts_vivado_version> then open the design in Vivado <$current_vivado_version>. Upgrade the design by running \"Tools => Report => Report IP Status...\", then run write_bd_tcl to create an updated script."} - - return 1 -} -``` diff --git a/examples/vivado/design.py b/examples/vivado/design.py deleted file mode 100644 index 7ac4eef3..00000000 --- a/examples/vivado/design.py +++ /dev/null @@ -1,33 +0,0 @@ -"""Zybo block design example project.""" - -import logging - -from fpga.project import Project - -logging.basicConfig() - -prj = Project('vivado', 'zybo-design') -prj.set_part('xc7z010-1-clg400') - -prj.set_outdir('../../build/zybo-design') - -prj.add_files('../../hdl/blinking.vhdl') -prj.add_files('zybo.xdc') -prj.add_files('design.tcl', filetype='design') - -export = """ -set PROJECT %s -if { [ catch { - # Vitis - write_hw_platform -fixed -force -include_bit -file ${PROJECT}.xsa -} ] } { - # SDK - write_hwdef -force -file ${PROJECT}.hwdef - write_sysdef -force -hwdef [glob -nocomplain *.hwdef] \\ - -bitfile [glob -nocomplain *.bit] -file ${PROJECT}.hdf -} -""" % ('zybo-design') - -prj.add_hook(export, 'postbit') - -prj.generate() diff --git a/examples/vivado/design.tcl b/examples/vivado/design.tcl deleted file mode 100644 index 4b10a0d4..00000000 --- a/examples/vivado/design.tcl +++ /dev/null @@ -1,608 +0,0 @@ -################################################################ -# START -################################################################ - -# CHANGE DESIGN NAME HERE -variable design_name -set design_name design_1 - -# If you do not already have an existing IP Integrator design open, -# you can create a design using the following command: -# create_bd_design $design_name - -# Creating design if needed -set errMsg "" -set nRet 0 - -set cur_design [current_bd_design -quiet] -set list_cells [get_bd_cells -quiet] - -if { ${design_name} eq "" } { - # USE CASES: - # 1) Design_name not set - - set errMsg "Please set the variable to a non-empty value." - set nRet 1 - -} elseif { ${cur_design} ne "" && ${list_cells} eq "" } { - # USE CASES: - # 2): Current design opened AND is empty AND names same. - # 3): Current design opened AND is empty AND names diff; design_name NOT in project. - # 4): Current design opened AND is empty AND names diff; design_name exists in project. - - if { $cur_design ne $design_name } { - common::send_msg_id "BD_TCL-001" "INFO" "Changing value of from <$design_name> to <$cur_design> since current design is empty." - set design_name [get_property NAME $cur_design] - } - common::send_msg_id "BD_TCL-002" "INFO" "Constructing design in IPI design <$cur_design>..." - -} elseif { ${cur_design} ne "" && $list_cells ne "" && $cur_design eq $design_name } { - # USE CASES: - # 5) Current design opened AND has components AND same names. - - set errMsg "Design <$design_name> already exists in your project, please set the variable to another value." - set nRet 1 -} elseif { [get_files -quiet ${design_name}.bd] ne "" } { - # USE CASES: - # 6) Current opened design, has components, but diff names, design_name exists in project. - # 7) No opened design, design_name exists in project. - - set errMsg "Design <$design_name> already exists in your project, please set the variable to another value." - set nRet 2 - -} else { - # USE CASES: - # 8) No opened design, design_name not in project. - # 9) Current opened design, has components, but diff names, design_name not in project. - - common::send_msg_id "BD_TCL-003" "INFO" "Currently there is no design <$design_name> in project, so creating one..." - - create_bd_design $design_name - - common::send_msg_id "BD_TCL-004" "INFO" "Making design <$design_name> as current_bd_design." - current_bd_design $design_name - -} - -common::send_msg_id "BD_TCL-005" "INFO" "Currently the variable is equal to \"$design_name\"." - -if { $nRet != 0 } { - catch {common::send_msg_id "BD_TCL-114" "ERROR" $errMsg} - return $nRet -} - -set bCheckIPsPassed 1 -################################################################## -# CHECK IPs -################################################################## -set bCheckIPs 1 -if { $bCheckIPs == 1 } { - set list_check_ips "\ -xilinx.com:ip:processing_system7:5.5\ -" - - set list_ips_missing "" - common::send_msg_id "BD_TCL-006" "INFO" "Checking if the following IPs exist in the project's IP catalog: $list_check_ips ." - - foreach ip_vlnv $list_check_ips { - set ip_obj [get_ipdefs -all $ip_vlnv] - if { $ip_obj eq "" } { - lappend list_ips_missing $ip_vlnv - } - } - - if { $list_ips_missing ne "" } { - catch {common::send_msg_id "BD_TCL-115" "ERROR" "The following IPs are not found in the IP Catalog:\n $list_ips_missing\n\nResolution: Please add the repository containing the IP(s) to the project." } - set bCheckIPsPassed 0 - } - -} - -################################################################## -# CHECK Modules -################################################################## -set bCheckModules 1 -if { $bCheckModules == 1 } { - set list_check_mods "\ -Blinking\ -" - - set list_mods_missing "" - common::send_msg_id "BD_TCL-006" "INFO" "Checking if the following modules exist in the project's sources: $list_check_mods ." - - foreach mod_vlnv $list_check_mods { - if { [can_resolve_reference $mod_vlnv] == 0 } { - lappend list_mods_missing $mod_vlnv - } - } - - if { $list_mods_missing ne "" } { - catch {common::send_msg_id "BD_TCL-115" "ERROR" "The following module(s) are not found in the project: $list_mods_missing" } - common::send_msg_id "BD_TCL-008" "INFO" "Please add source files for the missing module(s) above." - set bCheckIPsPassed 0 - } -} - -if { $bCheckIPsPassed != 1 } { - common::send_msg_id "BD_TCL-1003" "WARNING" "Will not continue with creation of design due to the error(s) above." - return 3 -} - -################################################################## -# DESIGN PROCs -################################################################## - - - -# Procedure to create entire design; Provide argument to make -# procedure reusable. If parentCell is "", will use root. -proc create_root_design { parentCell } { - - variable design_name - - if { $parentCell eq "" } { - set parentCell [get_bd_cells /] - } - - # Get object for parentCell - set parentObj [get_bd_cells $parentCell] - if { $parentObj == "" } { - catch {common::send_msg_id "BD_TCL-100" "ERROR" "Unable to find parent cell <$parentCell>!"} - return - } - - # Make sure parentObj is hier blk - set parentType [get_property TYPE $parentObj] - if { $parentType ne "hier" } { - catch {common::send_msg_id "BD_TCL-101" "ERROR" "Parent <$parentObj> has TYPE = <$parentType>. Expected to be ."} - return - } - - # Save current instance; Restore later - set oldCurInst [current_bd_instance .] - - # Set parent object as current - current_bd_instance $parentObj - - - # Create interface ports - set DDR [ create_bd_intf_port -mode Master -vlnv xilinx.com:interface:ddrx_rtl:1.0 DDR ] - - set FIXED_IO [ create_bd_intf_port -mode Master -vlnv xilinx.com:display_processing_system7:fixedio_rtl:1.0 FIXED_IO ] - - - # Create ports - set clk_i [ create_bd_port -dir I clk_i ] - set led_o [ create_bd_port -dir O led_o ] - - # Create instance: Blinking_0, and set properties - set block_name Blinking - set block_cell_name Blinking_0 - if { [catch {set Blinking_0 [create_bd_cell -type module -reference $block_name $block_cell_name] } errmsg] } { - catch {common::send_msg_id "BD_TCL-105" "ERROR" "Unable to add referenced block <$block_name>. Please add the files for ${block_name}'s definition into the project."} - return 1 - } elseif { $Blinking_0 eq "" } { - catch {common::send_msg_id "BD_TCL-106" "ERROR" "Unable to referenced block <$block_name>. Please add the files for ${block_name}'s definition into the project."} - return 1 - } - set_property -dict [ list \ - CONFIG.FREQ {125000000} \ - ] $Blinking_0 - - # Create instance: processing_system7_0, and set properties - set processing_system7_0 [ create_bd_cell -type ip -vlnv xilinx.com:ip:processing_system7:5.5 processing_system7_0 ] - set_property -dict [ list \ - CONFIG.PCW_ACT_APU_PERIPHERAL_FREQMHZ {650} \ - CONFIG.PCW_ACT_CAN_PERIPHERAL_FREQMHZ {10} \ - CONFIG.PCW_ACT_DCI_PERIPHERAL_FREQMHZ {10} \ - CONFIG.PCW_ACT_ENET0_PERIPHERAL_FREQMHZ {10} \ - CONFIG.PCW_ACT_ENET1_PERIPHERAL_FREQMHZ {10} \ - CONFIG.PCW_ACT_FPGA0_PERIPHERAL_FREQMHZ {10} \ - CONFIG.PCW_ACT_FPGA1_PERIPHERAL_FREQMHZ {10} \ - CONFIG.PCW_ACT_FPGA2_PERIPHERAL_FREQMHZ {10} \ - CONFIG.PCW_ACT_FPGA3_PERIPHERAL_FREQMHZ {10} \ - CONFIG.PCW_ACT_PCAP_PERIPHERAL_FREQMHZ {200} \ - CONFIG.PCW_ACT_QSPI_PERIPHERAL_FREQMHZ {200} \ - CONFIG.PCW_ACT_SDIO_PERIPHERAL_FREQMHZ {50} \ - CONFIG.PCW_ACT_SMC_PERIPHERAL_FREQMHZ {10} \ - CONFIG.PCW_ACT_SPI_PERIPHERAL_FREQMHZ {10} \ - CONFIG.PCW_ACT_TPIU_PERIPHERAL_FREQMHZ {200} \ - CONFIG.PCW_ACT_TTC0_CLK0_PERIPHERAL_FREQMHZ {108} \ - CONFIG.PCW_ACT_TTC0_CLK1_PERIPHERAL_FREQMHZ {108} \ - CONFIG.PCW_ACT_TTC0_CLK2_PERIPHERAL_FREQMHZ {108} \ - CONFIG.PCW_ACT_TTC1_CLK0_PERIPHERAL_FREQMHZ {108} \ - CONFIG.PCW_ACT_TTC1_CLK1_PERIPHERAL_FREQMHZ {108} \ - CONFIG.PCW_ACT_TTC1_CLK2_PERIPHERAL_FREQMHZ {108} \ - CONFIG.PCW_ACT_UART_PERIPHERAL_FREQMHZ {100} \ - CONFIG.PCW_ACT_WDT_PERIPHERAL_FREQMHZ {108} \ - CONFIG.PCW_APU_PERIPHERAL_FREQMHZ {650} \ - CONFIG.PCW_ARMPLL_CTRL_FBDIV {26} \ - CONFIG.PCW_CAN_PERIPHERAL_DIVISOR0 {1} \ - CONFIG.PCW_CAN_PERIPHERAL_DIVISOR1 {1} \ - CONFIG.PCW_CLK0_FREQ {10000000} \ - CONFIG.PCW_CLK1_FREQ {10000000} \ - CONFIG.PCW_CLK2_FREQ {10000000} \ - CONFIG.PCW_CLK3_FREQ {10000000} \ - CONFIG.PCW_CPU_CPU_PLL_FREQMHZ {1300} \ - CONFIG.PCW_CPU_PERIPHERAL_DIVISOR0 {2} \ - CONFIG.PCW_CRYSTAL_PERIPHERAL_FREQMHZ {50.000000} \ - CONFIG.PCW_DCI_PERIPHERAL_DIVISOR0 {15} \ - CONFIG.PCW_DCI_PERIPHERAL_DIVISOR1 {7} \ - CONFIG.PCW_DDRPLL_CTRL_FBDIV {21} \ - CONFIG.PCW_DDR_DDR_PLL_FREQMHZ {1050} \ - CONFIG.PCW_DDR_PERIPHERAL_DIVISOR0 {2} \ - CONFIG.PCW_DDR_RAM_HIGHADDR {0x1FFFFFFF} \ - CONFIG.PCW_ENET0_ENET0_IO {} \ - CONFIG.PCW_ENET0_PERIPHERAL_DIVISOR0 {1} \ - CONFIG.PCW_ENET0_PERIPHERAL_DIVISOR1 {1} \ - CONFIG.PCW_ENET0_PERIPHERAL_ENABLE {0} \ - CONFIG.PCW_ENET0_PERIPHERAL_FREQMHZ {1000 Mbps} \ - CONFIG.PCW_ENET0_RESET_ENABLE {0} \ - CONFIG.PCW_ENET1_PERIPHERAL_DIVISOR0 {1} \ - CONFIG.PCW_ENET1_PERIPHERAL_DIVISOR1 {1} \ - CONFIG.PCW_ENET1_RESET_ENABLE {0} \ - CONFIG.PCW_ENET_RESET_ENABLE {0} \ - CONFIG.PCW_ENET_RESET_SELECT {} \ - CONFIG.PCW_I2C0_RESET_ENABLE {0} \ - CONFIG.PCW_I2C1_RESET_ENABLE {0} \ - CONFIG.PCW_I2C_PERIPHERAL_FREQMHZ {25} \ - CONFIG.PCW_I2C_RESET_ENABLE {0} \ - CONFIG.PCW_IOPLL_CTRL_FBDIV {32} \ - CONFIG.PCW_IO_IO_PLL_FREQMHZ {1600} \ - CONFIG.PCW_MIO_0_DIRECTION {} \ - CONFIG.PCW_MIO_0_PULLUP {} \ - CONFIG.PCW_MIO_10_DIRECTION {} \ - CONFIG.PCW_MIO_10_PULLUP {} \ - CONFIG.PCW_MIO_11_DIRECTION {} \ - CONFIG.PCW_MIO_11_PULLUP {} \ - CONFIG.PCW_MIO_12_DIRECTION {} \ - CONFIG.PCW_MIO_12_PULLUP {} \ - CONFIG.PCW_MIO_13_DIRECTION {} \ - CONFIG.PCW_MIO_13_PULLUP {} \ - CONFIG.PCW_MIO_14_DIRECTION {} \ - CONFIG.PCW_MIO_14_PULLUP {} \ - CONFIG.PCW_MIO_15_DIRECTION {} \ - CONFIG.PCW_MIO_15_PULLUP {} \ - CONFIG.PCW_MIO_16_DIRECTION {} \ - CONFIG.PCW_MIO_16_PULLUP {} \ - CONFIG.PCW_MIO_17_DIRECTION {} \ - CONFIG.PCW_MIO_17_PULLUP {} \ - CONFIG.PCW_MIO_18_DIRECTION {} \ - CONFIG.PCW_MIO_18_PULLUP {} \ - CONFIG.PCW_MIO_19_DIRECTION {} \ - CONFIG.PCW_MIO_19_PULLUP {} \ - CONFIG.PCW_MIO_1_DIRECTION {out} \ - CONFIG.PCW_MIO_1_IOTYPE {LVCMOS 3.3V} \ - CONFIG.PCW_MIO_1_PULLUP {disabled} \ - CONFIG.PCW_MIO_1_SLEW {fast} \ - CONFIG.PCW_MIO_20_DIRECTION {} \ - CONFIG.PCW_MIO_20_PULLUP {} \ - CONFIG.PCW_MIO_21_DIRECTION {} \ - CONFIG.PCW_MIO_21_PULLUP {} \ - CONFIG.PCW_MIO_22_DIRECTION {} \ - CONFIG.PCW_MIO_22_PULLUP {} \ - CONFIG.PCW_MIO_23_DIRECTION {} \ - CONFIG.PCW_MIO_23_PULLUP {} \ - CONFIG.PCW_MIO_24_DIRECTION {} \ - CONFIG.PCW_MIO_24_PULLUP {} \ - CONFIG.PCW_MIO_25_DIRECTION {} \ - CONFIG.PCW_MIO_25_PULLUP {} \ - CONFIG.PCW_MIO_26_DIRECTION {} \ - CONFIG.PCW_MIO_26_PULLUP {} \ - CONFIG.PCW_MIO_27_DIRECTION {} \ - CONFIG.PCW_MIO_27_PULLUP {} \ - CONFIG.PCW_MIO_28_DIRECTION {} \ - CONFIG.PCW_MIO_28_PULLUP {} \ - CONFIG.PCW_MIO_29_DIRECTION {} \ - CONFIG.PCW_MIO_29_PULLUP {} \ - CONFIG.PCW_MIO_2_DIRECTION {inout} \ - CONFIG.PCW_MIO_2_IOTYPE {LVCMOS 3.3V} \ - CONFIG.PCW_MIO_2_PULLUP {disabled} \ - CONFIG.PCW_MIO_2_SLEW {fast} \ - CONFIG.PCW_MIO_30_DIRECTION {} \ - CONFIG.PCW_MIO_30_PULLUP {} \ - CONFIG.PCW_MIO_31_DIRECTION {} \ - CONFIG.PCW_MIO_31_PULLUP {} \ - CONFIG.PCW_MIO_32_DIRECTION {} \ - CONFIG.PCW_MIO_32_PULLUP {} \ - CONFIG.PCW_MIO_33_DIRECTION {} \ - CONFIG.PCW_MIO_33_PULLUP {} \ - CONFIG.PCW_MIO_34_DIRECTION {} \ - CONFIG.PCW_MIO_34_PULLUP {} \ - CONFIG.PCW_MIO_35_DIRECTION {} \ - CONFIG.PCW_MIO_35_PULLUP {} \ - CONFIG.PCW_MIO_36_DIRECTION {} \ - CONFIG.PCW_MIO_36_PULLUP {} \ - CONFIG.PCW_MIO_37_DIRECTION {} \ - CONFIG.PCW_MIO_37_PULLUP {} \ - CONFIG.PCW_MIO_38_DIRECTION {} \ - CONFIG.PCW_MIO_38_PULLUP {} \ - CONFIG.PCW_MIO_39_DIRECTION {} \ - CONFIG.PCW_MIO_39_PULLUP {} \ - CONFIG.PCW_MIO_3_DIRECTION {inout} \ - CONFIG.PCW_MIO_3_IOTYPE {LVCMOS 3.3V} \ - CONFIG.PCW_MIO_3_PULLUP {disabled} \ - CONFIG.PCW_MIO_3_SLEW {fast} \ - CONFIG.PCW_MIO_40_DIRECTION {inout} \ - CONFIG.PCW_MIO_40_IOTYPE {LVCMOS 1.8V} \ - CONFIG.PCW_MIO_40_PULLUP {disabled} \ - CONFIG.PCW_MIO_40_SLEW {fast} \ - CONFIG.PCW_MIO_41_DIRECTION {inout} \ - CONFIG.PCW_MIO_41_IOTYPE {LVCMOS 1.8V} \ - CONFIG.PCW_MIO_41_PULLUP {disabled} \ - CONFIG.PCW_MIO_41_SLEW {fast} \ - CONFIG.PCW_MIO_42_DIRECTION {inout} \ - CONFIG.PCW_MIO_42_IOTYPE {LVCMOS 1.8V} \ - CONFIG.PCW_MIO_42_PULLUP {disabled} \ - CONFIG.PCW_MIO_42_SLEW {fast} \ - CONFIG.PCW_MIO_43_DIRECTION {inout} \ - CONFIG.PCW_MIO_43_IOTYPE {LVCMOS 1.8V} \ - CONFIG.PCW_MIO_43_PULLUP {disabled} \ - CONFIG.PCW_MIO_43_SLEW {fast} \ - CONFIG.PCW_MIO_44_DIRECTION {inout} \ - CONFIG.PCW_MIO_44_IOTYPE {LVCMOS 1.8V} \ - CONFIG.PCW_MIO_44_PULLUP {disabled} \ - CONFIG.PCW_MIO_44_SLEW {fast} \ - CONFIG.PCW_MIO_45_DIRECTION {inout} \ - CONFIG.PCW_MIO_45_IOTYPE {LVCMOS 1.8V} \ - CONFIG.PCW_MIO_45_PULLUP {disabled} \ - CONFIG.PCW_MIO_45_SLEW {fast} \ - CONFIG.PCW_MIO_46_DIRECTION {} \ - CONFIG.PCW_MIO_46_PULLUP {} \ - CONFIG.PCW_MIO_47_DIRECTION {in} \ - CONFIG.PCW_MIO_47_IOTYPE {LVCMOS 1.8V} \ - CONFIG.PCW_MIO_47_PULLUP {disabled} \ - CONFIG.PCW_MIO_47_SLEW {slow} \ - CONFIG.PCW_MIO_48_DIRECTION {out} \ - CONFIG.PCW_MIO_48_IOTYPE {LVCMOS 1.8V} \ - CONFIG.PCW_MIO_48_PULLUP {disabled} \ - CONFIG.PCW_MIO_48_SLEW {slow} \ - CONFIG.PCW_MIO_49_DIRECTION {in} \ - CONFIG.PCW_MIO_49_IOTYPE {LVCMOS 1.8V} \ - CONFIG.PCW_MIO_49_PULLUP {disabled} \ - CONFIG.PCW_MIO_49_SLEW {slow} \ - CONFIG.PCW_MIO_4_DIRECTION {inout} \ - CONFIG.PCW_MIO_4_IOTYPE {LVCMOS 3.3V} \ - CONFIG.PCW_MIO_4_PULLUP {disabled} \ - CONFIG.PCW_MIO_4_SLEW {fast} \ - CONFIG.PCW_MIO_50_DIRECTION {} \ - CONFIG.PCW_MIO_50_PULLUP {} \ - CONFIG.PCW_MIO_51_DIRECTION {} \ - CONFIG.PCW_MIO_51_PULLUP {} \ - CONFIG.PCW_MIO_52_DIRECTION {} \ - CONFIG.PCW_MIO_52_PULLUP {} \ - CONFIG.PCW_MIO_53_DIRECTION {} \ - CONFIG.PCW_MIO_53_PULLUP {} \ - CONFIG.PCW_MIO_5_DIRECTION {inout} \ - CONFIG.PCW_MIO_5_IOTYPE {LVCMOS 3.3V} \ - CONFIG.PCW_MIO_5_PULLUP {disabled} \ - CONFIG.PCW_MIO_5_SLEW {fast} \ - CONFIG.PCW_MIO_6_DIRECTION {out} \ - CONFIG.PCW_MIO_6_IOTYPE {LVCMOS 3.3V} \ - CONFIG.PCW_MIO_6_PULLUP {disabled} \ - CONFIG.PCW_MIO_6_SLEW {fast} \ - CONFIG.PCW_MIO_7_DIRECTION {} \ - CONFIG.PCW_MIO_7_PULLUP {} \ - CONFIG.PCW_MIO_8_DIRECTION {out} \ - CONFIG.PCW_MIO_8_IOTYPE {LVCMOS 3.3V} \ - CONFIG.PCW_MIO_8_PULLUP {disabled} \ - CONFIG.PCW_MIO_8_SLEW {fast} \ - CONFIG.PCW_MIO_9_DIRECTION {} \ - CONFIG.PCW_MIO_9_PULLUP {} \ - CONFIG.PCW_MIO_TREE_PERIPHERALS {unassigned#Quad SPI Flash#Quad SPI Flash#Quad SPI Flash#Quad SPI Flash#Quad SPI Flash#Quad SPI Flash#unassigned#Quad SPI Flash#unassigned#unassigned#unassigned#unassigned#unassigned#unassigned#unassigned#unassigned#unassigned#unassigned#unassigned#unassigned#unassigned#unassigned#unassigned#unassigned#unassigned#unassigned#unassigned#unassigned#unassigned#unassigned#unassigned#unassigned#unassigned#unassigned#unassigned#unassigned#unassigned#unassigned#unassigned#SD 0#SD 0#SD 0#SD 0#SD 0#SD 0#unassigned#SD 0#UART 1#UART 1#unassigned#unassigned#unassigned#unassigned} \ - CONFIG.PCW_MIO_TREE_SIGNALS {unassigned#qspi0_ss_b#qspi0_io[0]#qspi0_io[1]#qspi0_io[2]#qspi0_io[3]/HOLD_B#qspi0_sclk#unassigned#qspi_fbclk#unassigned#unassigned#unassigned#unassigned#unassigned#unassigned#unassigned#unassigned#unassigned#unassigned#unassigned#unassigned#unassigned#unassigned#unassigned#unassigned#unassigned#unassigned#unassigned#unassigned#unassigned#unassigned#unassigned#unassigned#unassigned#unassigned#unassigned#unassigned#unassigned#unassigned#unassigned#clk#cmd#data[0]#data[1]#data[2]#data[3]#unassigned#cd#tx#rx#unassigned#unassigned#unassigned#unassigned} \ - CONFIG.PCW_NAND_GRP_D8_ENABLE {0} \ - CONFIG.PCW_NAND_PERIPHERAL_ENABLE {0} \ - CONFIG.PCW_NOR_GRP_A25_ENABLE {0} \ - CONFIG.PCW_NOR_GRP_CS0_ENABLE {0} \ - CONFIG.PCW_NOR_GRP_CS1_ENABLE {0} \ - CONFIG.PCW_NOR_GRP_SRAM_CS0_ENABLE {0} \ - CONFIG.PCW_NOR_GRP_SRAM_CS1_ENABLE {0} \ - CONFIG.PCW_NOR_GRP_SRAM_INT_ENABLE {0} \ - CONFIG.PCW_NOR_PERIPHERAL_ENABLE {0} \ - CONFIG.PCW_PCAP_PERIPHERAL_DIVISOR0 {8} \ - CONFIG.PCW_PRESET_BANK1_VOLTAGE {LVCMOS 1.8V} \ - CONFIG.PCW_QSPI_GRP_FBCLK_ENABLE {1} \ - CONFIG.PCW_QSPI_GRP_FBCLK_IO {MIO 8} \ - CONFIG.PCW_QSPI_GRP_IO1_ENABLE {0} \ - CONFIG.PCW_QSPI_GRP_SINGLE_SS_ENABLE {1} \ - CONFIG.PCW_QSPI_GRP_SINGLE_SS_IO {MIO 1 .. 6} \ - CONFIG.PCW_QSPI_GRP_SS1_ENABLE {0} \ - CONFIG.PCW_QSPI_PERIPHERAL_DIVISOR0 {8} \ - CONFIG.PCW_QSPI_PERIPHERAL_ENABLE {1} \ - CONFIG.PCW_QSPI_PERIPHERAL_FREQMHZ {200} \ - CONFIG.PCW_QSPI_QSPI_IO {MIO 1 .. 6} \ - CONFIG.PCW_SD0_GRP_CD_ENABLE {1} \ - CONFIG.PCW_SD0_GRP_CD_IO {MIO 47} \ - CONFIG.PCW_SD0_GRP_POW_ENABLE {0} \ - CONFIG.PCW_SD0_GRP_WP_ENABLE {1} \ - CONFIG.PCW_SD0_GRP_WP_IO {EMIO} \ - CONFIG.PCW_SD0_PERIPHERAL_ENABLE {1} \ - CONFIG.PCW_SD0_SD0_IO {MIO 40 .. 45} \ - CONFIG.PCW_SDIO_PERIPHERAL_DIVISOR0 {32} \ - CONFIG.PCW_SDIO_PERIPHERAL_FREQMHZ {50} \ - CONFIG.PCW_SDIO_PERIPHERAL_VALID {1} \ - CONFIG.PCW_SINGLE_QSPI_DATA_MODE {x4} \ - CONFIG.PCW_SMC_PERIPHERAL_DIVISOR0 {1} \ - CONFIG.PCW_SPI_PERIPHERAL_DIVISOR0 {1} \ - CONFIG.PCW_TPIU_PERIPHERAL_DIVISOR0 {1} \ - CONFIG.PCW_TTC0_CLK0_PERIPHERAL_FREQMHZ {133.333333} \ - CONFIG.PCW_TTC0_CLK1_PERIPHERAL_FREQMHZ {133.333333} \ - CONFIG.PCW_TTC0_CLK2_PERIPHERAL_FREQMHZ {133.333333} \ - CONFIG.PCW_TTC0_PERIPHERAL_ENABLE {0} \ - CONFIG.PCW_TTC0_TTC0_IO {} \ - CONFIG.PCW_USB0_USB0_IO {} \ - CONFIG.PCW_USE_M_AXI_GP0 {0} \ - ] $processing_system7_0 - - # Create interface connections - connect_bd_intf_net -intf_net processing_system7_0_DDR [get_bd_intf_ports DDR] [get_bd_intf_pins processing_system7_0/DDR] - connect_bd_intf_net -intf_net processing_system7_0_FIXED_IO [get_bd_intf_ports FIXED_IO] [get_bd_intf_pins processing_system7_0/FIXED_IO] - - # Create port connections - connect_bd_net -net Blinking_0_led_o [get_bd_ports led_o] [get_bd_pins Blinking_0/led_o] - connect_bd_net -net clk_i_1 [get_bd_ports clk_i] [get_bd_pins Blinking_0/clk_i] - - # Create address segments - - - # Restore current instance - current_bd_instance $oldCurInst - - validate_bd_design - save_bd_design -} -# End of create_root_design() - - -################################################################## -# MAIN FLOW -################################################################## - -create_root_design "" diff --git a/examples/vivado/vivado.py b/examples/vivado/run.py similarity index 100% rename from examples/vivado/vivado.py rename to examples/vivado/run.py diff --git a/examples/vivado/run.sh b/examples/vivado/run.sh new file mode 100644 index 00000000..f85dcc03 --- /dev/null +++ b/examples/vivado/run.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +ACTIONS=("make" "prog" "all") +SOURCES=("vlog" "vhdl" "slog" "design") + +for ACTION in "${ACTIONS[@]}"; do + for SOURCE in "${SOURCES[@]}"; do + python3 run.py --action $ACTION --source $SOURCE + done +done From 6c68cafcb668e2bf921b0dbc0abd2ec79a9fedde Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Mon, 24 Jun 2024 21:19:17 -0300 Subject: [PATCH 103/254] Added support for the Arty A7 35T in the Vivado example --- examples/sources/arty/a7-35t/clk.xdc | 2 ++ examples/sources/arty/a7-35t/led.xdc | 2 ++ examples/sources/arty/a7-35t/timing.xdc | 1 + examples/sources/zybo/{ => ZYBO}/clk.xdc | 0 examples/sources/zybo/{ => ZYBO}/led.xdc | 0 examples/sources/zybo/{ => ZYBO}/timing.xdc | 0 examples/vivado/run.py | 31 +++++++++++++++------ examples/vivado/run.sh | 12 ++++++-- 8 files changed, 36 insertions(+), 12 deletions(-) create mode 100644 examples/sources/arty/a7-35t/clk.xdc create mode 100644 examples/sources/arty/a7-35t/led.xdc create mode 100644 examples/sources/arty/a7-35t/timing.xdc rename examples/sources/zybo/{ => ZYBO}/clk.xdc (100%) rename examples/sources/zybo/{ => ZYBO}/led.xdc (100%) rename examples/sources/zybo/{ => ZYBO}/timing.xdc (100%) diff --git a/examples/sources/arty/a7-35t/clk.xdc b/examples/sources/arty/a7-35t/clk.xdc new file mode 100644 index 00000000..ee01dcb3 --- /dev/null +++ b/examples/sources/arty/a7-35t/clk.xdc @@ -0,0 +1,2 @@ +set_property PACKAGE_PIN E3 [get_ports clk_i] +set_property IOSTANDARD LVCMOS33 [get_ports clk_i] diff --git a/examples/sources/arty/a7-35t/led.xdc b/examples/sources/arty/a7-35t/led.xdc new file mode 100644 index 00000000..bf6d2304 --- /dev/null +++ b/examples/sources/arty/a7-35t/led.xdc @@ -0,0 +1,2 @@ +set_property PACKAGE_PIN H5 [get_ports led_o] +set_property IOSTANDARD LVCMOS33 [get_ports led_o] diff --git a/examples/sources/arty/a7-35t/timing.xdc b/examples/sources/arty/a7-35t/timing.xdc new file mode 100644 index 00000000..4bfaa72f --- /dev/null +++ b/examples/sources/arty/a7-35t/timing.xdc @@ -0,0 +1 @@ +create_clock -name clk_i -period 10.0 [get_ports clk_i] diff --git a/examples/sources/zybo/clk.xdc b/examples/sources/zybo/ZYBO/clk.xdc similarity index 100% rename from examples/sources/zybo/clk.xdc rename to examples/sources/zybo/ZYBO/clk.xdc diff --git a/examples/sources/zybo/led.xdc b/examples/sources/zybo/ZYBO/led.xdc similarity index 100% rename from examples/sources/zybo/led.xdc rename to examples/sources/zybo/ZYBO/led.xdc diff --git a/examples/sources/zybo/timing.xdc b/examples/sources/zybo/ZYBO/timing.xdc similarity index 100% rename from examples/sources/zybo/timing.xdc rename to examples/sources/zybo/ZYBO/timing.xdc diff --git a/examples/vivado/run.py b/examples/vivado/run.py index 99ec2745..2431466e 100644 --- a/examples/vivado/run.py +++ b/examples/vivado/run.py @@ -1,22 +1,37 @@ -"""Vivado VHDL example project.""" +"""Vivado examples.""" import argparse from pyfpga.vivado import Vivado + parser = argparse.ArgumentParser() parser.add_argument( - '--action', choices=['make', 'prog', 'all'], default='make' + '--board', choices=['zybo', 'arty'], default='zybo' ) parser.add_argument( - '--source', choices=['vlog', 'vhdl', 'slog', 'design'], default='vlog' + '--source', choices=['vlog', 'vhdl', 'slog'], default='vlog' +) +parser.add_argument( + '--action', choices=['make', 'prog', 'all'], default='make' ) args = parser.parse_args() -prj = Vivado(odir=f'../build/vivado-{args.source}') -prj.set_part('xc7z010-1-clg400') +prj = Vivado(odir=f'../build/vivado') + +if args.board == 'zybo': + prj.set_part('xc7z010-1-clg400') + prj.add_param('FREQ', '125000000') + prj.add_cons('../sources/zybo/ZYBO/timing.xdc', 'syn') + prj.add_cons('../sources/zybo/ZYBO/clk.xdc', 'par') + prj.add_cons('../sources/zybo/ZYBO/led.xdc', 'par') +if args.board == 'arty': + prj.set_part('xc7a35ticsg324-1L') + prj.add_param('FREQ', '100000000') + prj.add_cons('../sources/arty/a7-35t/timing.xdc', 'syn') + prj.add_cons('../sources/arty/a7-35t/clk.xdc', 'par') + prj.add_cons('../sources/arty/a7-35t/led.xdc', 'par') -prj.add_param('FREQ', '125000000') if args.source == 'vhdl': prj.add_vhdl('../sources/vhdl/*.vhdl', 'blink_lib') if args.source == 'vlog': @@ -27,9 +42,7 @@ prj.add_vlog('../sources/slog/*.sv') if args.source in ['vlog', 'slog']: prj.add_define('DEFINE', '1') -prj.add_cons('../sources/zybo/timing.xdc', 'syn') -prj.add_cons('../sources/zybo/clk.xdc', 'par') -prj.add_cons('../sources/zybo/led.xdc', 'par') + prj.set_top('Top') if args.action in ['make', 'all']: diff --git a/examples/vivado/run.sh b/examples/vivado/run.sh index f85dcc03..bd894b91 100644 --- a/examples/vivado/run.sh +++ b/examples/vivado/run.sh @@ -1,10 +1,16 @@ #!/bin/bash +set -e + +BOARDS=("zybo" "arty") +SOURCES=("vlog" "vhdl" "slog") ACTIONS=("make" "prog" "all") -SOURCES=("vlog" "vhdl" "slog" "design") -for ACTION in "${ACTIONS[@]}"; do +for BOARD in "${BOARDS[@]}"; do for SOURCE in "${SOURCES[@]}"; do - python3 run.py --action $ACTION --source $SOURCE + for ACTION in "${ACTIONS[@]}"; do + echo "> $BOARD - $SOURCE - $ACTION" + python3 run.py --board $BOARD --source $SOURCE --action $ACTION + done done done From c0b02c6680811185da203e470d4c7a5712565302 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Tue, 25 Jun 2024 22:08:00 -0300 Subject: [PATCH 104/254] Added initial (very early stage) support for Openflow --- examples/openflow/run.py | 37 ++++++ pyfpga/openflow.py | 76 +++++++++++-- pyfpga/templates/openflow.jinja | 194 ++++++-------------------------- 3 files changed, 141 insertions(+), 166 deletions(-) create mode 100644 examples/openflow/run.py diff --git a/examples/openflow/run.py b/examples/openflow/run.py new file mode 100644 index 00000000..8677a8a4 --- /dev/null +++ b/examples/openflow/run.py @@ -0,0 +1,37 @@ +"""Vivado examples.""" + +import argparse + +from pyfpga.openflow import Openflow + + +parser = argparse.ArgumentParser() +parser.add_argument( + '--board', choices=['icestick', 'edu-ciaa', 'orangecrab', 'ecp5evn'], + default='icestick' +) +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 = Openflow(odir='../build/openflow') + +prj.add_param('FREQ', '100000000') + +if args.source == 'vlog': + prj.add_include('../sources/vlog/include') + prj.add_vlog('../sources/vlog/*.v') +if args.source in ['vlog', 'slog']: + prj.add_define('DEFINE', '1') + +prj.set_top('Top') + +if args.action in ['make', 'all']: + prj.make() + +if args.action in ['prog', 'all']: + prj.prog() diff --git a/pyfpga/openflow.py b/pyfpga/openflow.py index 0b7865ba..2849b4ba 100644 --- a/pyfpga/openflow.py +++ b/pyfpga/openflow.py @@ -8,19 +8,81 @@ Implements support for an Open Source development flow. """ +# pylint: disable=too-many-locals +# pylint: disable=too-many-branches +# pylint: disable=duplicate-code + from pyfpga.project import Project class Openflow(Project): - """Class to support Openflow.""" - - def __init__(self, name='openflow', odir='results'): - super().__init__(name=name, odir=odir) - self.set_part('hx8k-ct256') + """Class to support Open Source tools.""" def _make_prepare(self, steps): - self.tool['make-app'] = 'docker' - self.tool['make-cmd'] = 'bash openflow.sh' + context = { + 'PROJECT': self.name or 'openflow', + 'PART': self.data.get('part', 'hx8k-ct256') + } + for step in steps: + context[step] = 1 + if 'includes' in self.data: + includes = [] + for include in self.data['includes']: + includes.append(f'-I{str(include)}') + context['INCLUDES'] = ' '.join(includes) + files = [] + if 'files' in self.data: + for file in self.data['files']: + files.append(f'read_verilog -defer {file}') + if files: + context['VLOGS'] = '\n'.join(files) +# for file in self.data['files']: +# if 'lib' in self.data['files'][file]: +# lib = self.data['files'][file]['lib'] +# files.append( +# f'set_property library {lib} [get_files {file}]' +# ) +# if 'constraints' in self.data: +# for file in self.data['constraints']: +# files.append(f'add_file -fileset constrs_1 {file}') +# for file in self.data['constraints']: +# if self.data['constraints'][file] == 'syn': +# prop = 'USED_IN_IMPLEMENTATION FALSE' +# if self.data['constraints'][file] == 'syn': +# prop = 'USED_IN_SYNTHESIS FALSE' +# if self.data['constraints'][file] != 'all': +# files.append(f'set_property {prop} [get_files {file}]') +# first = next(iter(self.data['constraints'])) +# prop = f'TARGET_CONSTRS_FILE {first}' +# files.append(f'set_property {prop} [current_fileset -constrset]') +# if files: +# context['FILES'] = '\n'.join(files) + if 'top' in self.data: + context['TOP'] = self.data['top'] + if 'defines' in self.data: + defines = [] + for key, value in self.data['defines'].items(): + defines.append(f'-D{key}={value}') + context['DEFINES'] = ' '.join(defines) + if 'params' in self.data: + params = [] + for key, value in self.data['params'].items(): + params.append(f'-set {key} {value}') + context['PARAMS'] = ' '.join(params) + if 'hooks' in self.data: + for stage in self.data['hooks']: + context[stage.upper()] = '\n'.join(self.data['hooks'][stage]) + self._create_file('openflow', 'sh', context) + return 'bash openflow.sh' + +# def _prog_prepare(self, bitstream, position): +# _ = position # Not needed for Vivado +# if not bitstream: +# basename = self.name or 'vivado' +# bitstream = Path(self.odir).resolve() / f'{basename}.bit' +# context = {'BITSTREAM': bitstream} +# self._create_file('vivado-prog', 'tcl', context) +# return 'vivado -mode batch -notrace -quiet -source vivado-prog.tcl' def _prog_prepare(self, bitstream, position): # binaries = ['bit'] diff --git a/pyfpga/templates/openflow.jinja b/pyfpga/templates/openflow.jinja index d3a0a703..6b4580c2 100644 --- a/pyfpga/templates/openflow.jinja +++ b/pyfpga/templates/openflow.jinja @@ -6,162 +6,38 @@ set -e -############################################################################### -# Things to tuneup -############################################################################### - -FRONTEND={frontend} -BACKEND={backend} -PROJECT={project} -FAMILY={family} -DEVICE={device} -PACKAGE={package} -TOP={top} - -PARAMS="{params}" -FLAGS="--std=08 -fsynopsys -fexplicit -frelaxed" -VHDLS="{vhdls}" -INCLUDES="{includes}" -VERILOGS="{verilogs}" -CONSTRAINTS="{constraints}" - -# taks = prj syn par bit -TASKS="{tasks}" - -# -# Tools configuration -# - -OCI_ENGINE="{oci_engine}" - -CONT_GHDL="{cont_ghdl}" -CONT_YOSYS="{cont_yosys}" -CONT_NEXTPNR_ICE40="{cont_nextpnr_ice40}" -CONT_ICETIME="{cont_icetime}" -CONT_ICEPACK="{cont_icepack}" -CONT_NEXTPNR_ECP5="{cont_nextpnr_ecp5}" -CONT_ECPPACK="{cont_ecppack}" - -TOOL_GHDL="{tool_ghdl}" -TOOL_YOSYS="{tool_yosys}" -TOOL_NEXTPNR_ICE40="{tool_nextpnr_ice40}" -TOOL_ICETIME="{tool_icetime}" -TOOL_ICEPACK="{tool_icepack}" -TOOL_NEXTPNR_ECP5="{tool_nextpnr_ecp5}" -TOOL_ECPPACK="{tool_ecppack}" - -############################################################################### -# Support -############################################################################### - -MODULE= -[ -n "$VHDLS" ] && MODULE="-m ghdl" - -function print () {{ - # tput setaf 6; echo ">>> PyFPGA ($1): $2"; tput sgr0; - echo ">>> PyFPGA ($1): $2" -}} - -############################################################################### -# Synthesis -############################################################################### - -if [[ $TASKS == *"syn"* ]]; then - -print "$FRONTEND" "running 'synthesis'" - -### GHDL - -if [[ $FRONTEND == "ghdl" ]]; then - -$OCI_ENGINE $CONT_GHDL /bin/bash -c " -$VHDLS -$TOOL_GHDL --synth $FLAGS $TOP -" > $PROJECT.vhdl - -fi - -### Yosys (with ghdl-yosys-plugin) - -if [[ $FRONTEND == "yosys" ]]; then - -SYNTH= -WRITE= -if [[ $BACKEND == "vivado" ]]; then - SYNTH="synth_xilinx -top $TOP -family $FAMILY" - WRITE="write_edif -pvector bra $PROJECT.edif" -elif [[ $BACKEND == "ise" ]]; then - SYNTH="synth_xilinx -top $TOP -family $FAMILY -ise" - WRITE="write_edif -pvector bra $PROJECT.edif" -elif [[ $BACKEND == "nextpnr" ]]; then - SYNTH="synth_$FAMILY -top $TOP -json $PROJECT.json" -elif [[ $BACKEND == "verilog-nosynth" ]]; then - WRITE="write_verilog $PROJECT.v" -else - SYNTH="synth -top $TOP" - WRITE="write_verilog $PROJECT.v" -fi - -$OCI_ENGINE $CONT_YOSYS /bin/bash -c " -$VHDLS -$TOOL_YOSYS -Q $MODULE -p ' -$INCLUDES; -$VERILOGS; -$PARAMS; -$SYNTH; -$WRITE -'" - -fi - -### - -fi - -############################################################################### -# Place and Route -############################################################################### - -if [[ $TASKS == *"par"* ]]; then - -print "nextpnr-$FAMILY" "running 'place and route'" - -INPUT="--json $PROJECT.json" - -if [[ $FAMILY == "ice40" ]]; then - CONSTRAINT="--pcf $CONSTRAINTS" - OUTPUT="--asc $PROJECT.asc" - $OCI_ENGINE $CONT_NEXTPNR_ICE40 $TOOL_NEXTPNR_ICE40 \ - --$DEVICE --package $PACKAGE $CONSTRAINT $INPUT $OUTPUT - $OCI_ENGINE $CONT_ICETIME $TOOL_ICETIME \ - -d $DEVICE -mtr $PROJECT.rpt $PROJECT.asc -fi - -if [[ $FAMILY == "ecp5" ]]; then - CONSTRAINT="--lpf $CONSTRAINTS" - OUTPUT="--textcfg $PROJECT.config" - $OCI_ENGINE $CONT_NEXTPNR_ECP5 $TOOL_NEXTPNR_ECP5 \ - --$DEVICE --package $PACKAGE $CONSTRAINT $INPUT $OUTPUT -fi - -fi - -############################################################################### -# Bitstream generation -############################################################################### - -if [[ $TASKS == *"bit"* ]]; then - -if [[ $FAMILY == "ice40" ]]; then - print "icepack" "running 'bitstream generation'" - $OCI_ENGINE $CONT_ICEPACK $TOOL_ICEPACK \ - $PROJECT.asc $PROJECT.bit -fi - -if [[ $FAMILY == "ecp5" ]]; then - print "eccpack" "running 'bitstream generation'" - $OCI_ENGINE $CONT_ECPPACK $TOOL_ECPPACK \ - --svf $PROJECT.svf $PROJECT.config $PROJECT.bit -fi - -fi +DOCKER="docker run --rm -v $HOME:$HOME -w $PWD" + +{% if SYN %} +$DOCKER hdlc/ghdl:yosys /bin/bash -c " +{{ PRESYN }} +{{ VHDLS }} +yosys -Q -m ghdl -p ' +{% if INCLUDES %} +verilog_defaults -add {{ INCLUDES }} +{% endif %} +{% if DEFINES %} +verilog_defines {{ DEFINES }} +{% endif %} +{{ VLOGS }} +{{ SLOGS }} +{% if PARAMS %} +chparam {{ PARAMS }} {{ TOP }} +{% endif %} +synth -top {{ TOP }} +' +{{ POSTSYN }} +" +{% endif %} + +{% if PAR %} +{{ PREPAR }} + +{{ POSTPAR }} +{% endif %} + +{% if BIT %} +{{ PREBIT }} + +{{ POSTBIT }} +{% endif %} From aaef6c1b2b63369152bdbd51fa54c9e40490c6e3 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Tue, 25 Jun 2024 22:22:44 -0300 Subject: [PATCH 105/254] Removed resources submodule --- .gitmodules | 3 --- examples/resources | 1 - examples/sources/de10nano/clk.sdc | 1 + examples/sources/de10nano/clk.tcl | 1 + examples/sources/de10nano/led.tcl | 1 + examples/sources/ecp5evn/clk.lpf | 2 ++ examples/sources/ecp5evn/led.lpf | 2 ++ examples/sources/edu-ciaa/clk.pcf | 1 + examples/sources/edu-ciaa/led.pcf | 1 + examples/sources/icestick/clk.pcf | 1 + examples/sources/icestick/led.pcf | 1 + examples/sources/maker-board/clk.pdc | 1 + examples/sources/maker-board/clk.sdc | 1 + examples/sources/maker-board/led.pdc | 1 + examples/sources/nexys3/clk.ucf | 1 + examples/sources/nexys3/clk.xcf | 2 ++ examples/sources/nexys3/led.ucf | 1 + examples/sources/orangecrab/clk.lpf | 2 ++ examples/sources/orangecrab/led.lpf | 2 ++ examples/sources/s6micro/clk.ucf | 1 + examples/sources/s6micro/clk.xcf | 2 ++ examples/sources/s6micro/led.ucf | 1 + 22 files changed, 26 insertions(+), 4 deletions(-) delete mode 100644 .gitmodules delete mode 160000 examples/resources create mode 100644 examples/sources/de10nano/clk.sdc create mode 100644 examples/sources/de10nano/clk.tcl create mode 100644 examples/sources/de10nano/led.tcl create mode 100644 examples/sources/ecp5evn/clk.lpf create mode 100644 examples/sources/ecp5evn/led.lpf create mode 100644 examples/sources/edu-ciaa/clk.pcf create mode 100644 examples/sources/edu-ciaa/led.pcf create mode 100644 examples/sources/icestick/clk.pcf create mode 100644 examples/sources/icestick/led.pcf create mode 100644 examples/sources/maker-board/clk.pdc create mode 100644 examples/sources/maker-board/clk.sdc create mode 100644 examples/sources/maker-board/led.pdc create mode 100644 examples/sources/nexys3/clk.ucf create mode 100644 examples/sources/nexys3/clk.xcf create mode 100644 examples/sources/nexys3/led.ucf create mode 100644 examples/sources/orangecrab/clk.lpf create mode 100644 examples/sources/orangecrab/led.lpf create mode 100644 examples/sources/s6micro/clk.ucf create mode 100644 examples/sources/s6micro/clk.xcf create mode 100644 examples/sources/s6micro/led.ucf diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index cba0ef31..00000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "examples/resources"] - path = examples/resources - url = git@github.com:PyFPGA/resources.git diff --git a/examples/resources b/examples/resources deleted file mode 160000 index 7f34b14e..00000000 --- a/examples/resources +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 7f34b14e2cdcbcbd6f57053a033a2abdd3ff1900 diff --git a/examples/sources/de10nano/clk.sdc b/examples/sources/de10nano/clk.sdc new file mode 100644 index 00000000..00363616 --- /dev/null +++ b/examples/sources/de10nano/clk.sdc @@ -0,0 +1 @@ +create_clock -period 20 [get_ports clk_i] diff --git a/examples/sources/de10nano/clk.tcl b/examples/sources/de10nano/clk.tcl new file mode 100644 index 00000000..2d72bdd4 --- /dev/null +++ b/examples/sources/de10nano/clk.tcl @@ -0,0 +1 @@ +set_location_assignment PIN_V11 -to clk_i diff --git a/examples/sources/de10nano/led.tcl b/examples/sources/de10nano/led.tcl new file mode 100644 index 00000000..4159ac36 --- /dev/null +++ b/examples/sources/de10nano/led.tcl @@ -0,0 +1 @@ +set_location_assignment PIN_W15 -to led_o diff --git a/examples/sources/ecp5evn/clk.lpf b/examples/sources/ecp5evn/clk.lpf new file mode 100644 index 00000000..c6c70332 --- /dev/null +++ b/examples/sources/ecp5evn/clk.lpf @@ -0,0 +1,2 @@ +LOCATE COMP "clk_i" SITE "A10"; +IOBUF PORT "clk_i" IO_TYPE=LVCMOS33; diff --git a/examples/sources/ecp5evn/led.lpf b/examples/sources/ecp5evn/led.lpf new file mode 100644 index 00000000..529c0b89 --- /dev/null +++ b/examples/sources/ecp5evn/led.lpf @@ -0,0 +1,2 @@ +LOCATE COMP "led_o" SITE "A13"; +IOBUF PORT "led_o" IO_TYPE=LVCMOS25; diff --git a/examples/sources/edu-ciaa/clk.pcf b/examples/sources/edu-ciaa/clk.pcf new file mode 100644 index 00000000..cfd62a35 --- /dev/null +++ b/examples/sources/edu-ciaa/clk.pcf @@ -0,0 +1 @@ +set_io clk_i 94 diff --git a/examples/sources/edu-ciaa/led.pcf b/examples/sources/edu-ciaa/led.pcf new file mode 100644 index 00000000..92e61517 --- /dev/null +++ b/examples/sources/edu-ciaa/led.pcf @@ -0,0 +1 @@ +set_io led_o 4 diff --git a/examples/sources/icestick/clk.pcf b/examples/sources/icestick/clk.pcf new file mode 100644 index 00000000..8b246ac7 --- /dev/null +++ b/examples/sources/icestick/clk.pcf @@ -0,0 +1 @@ +set_io clk_i 21 diff --git a/examples/sources/icestick/led.pcf b/examples/sources/icestick/led.pcf new file mode 100644 index 00000000..9a56ae6f --- /dev/null +++ b/examples/sources/icestick/led.pcf @@ -0,0 +1 @@ +set_io led_o 95 diff --git a/examples/sources/maker-board/clk.pdc b/examples/sources/maker-board/clk.pdc new file mode 100644 index 00000000..c0070c73 --- /dev/null +++ b/examples/sources/maker-board/clk.pdc @@ -0,0 +1 @@ +set_io clk_i -DIRECTION INPUT -pinname 23 -fixed yes diff --git a/examples/sources/maker-board/clk.sdc b/examples/sources/maker-board/clk.sdc new file mode 100644 index 00000000..00363616 --- /dev/null +++ b/examples/sources/maker-board/clk.sdc @@ -0,0 +1 @@ +create_clock -period 20 [get_ports clk_i] diff --git a/examples/sources/maker-board/led.pdc b/examples/sources/maker-board/led.pdc new file mode 100644 index 00000000..703bd95a --- /dev/null +++ b/examples/sources/maker-board/led.pdc @@ -0,0 +1 @@ +set_io led_o -DIRECTION OUTPUT -pinname 117 -fixed yes diff --git a/examples/sources/nexys3/clk.ucf b/examples/sources/nexys3/clk.ucf new file mode 100644 index 00000000..c0230e2d --- /dev/null +++ b/examples/sources/nexys3/clk.ucf @@ -0,0 +1 @@ +NET "clk_i" LOC = "V10"; diff --git a/examples/sources/nexys3/clk.xcf b/examples/sources/nexys3/clk.xcf new file mode 100644 index 00000000..a200fdeb --- /dev/null +++ b/examples/sources/nexys3/clk.xcf @@ -0,0 +1,2 @@ +NET "clk_i" TNM_NET = "clk_i"; +TIMESPEC "TS_clk_i" = PERIOD "clk_i" 10.00 ns HIGH 50%; diff --git a/examples/sources/nexys3/led.ucf b/examples/sources/nexys3/led.ucf new file mode 100644 index 00000000..c210831b --- /dev/null +++ b/examples/sources/nexys3/led.ucf @@ -0,0 +1 @@ +NET "led_o" LOC = "U16"; diff --git a/examples/sources/orangecrab/clk.lpf b/examples/sources/orangecrab/clk.lpf new file mode 100644 index 00000000..ed801503 --- /dev/null +++ b/examples/sources/orangecrab/clk.lpf @@ -0,0 +1,2 @@ +LOCATE COMP "clk_i" SITE "A9"; +IOBUF PORT "clk_i" IO_TYPE=LVCMOS33; diff --git a/examples/sources/orangecrab/led.lpf b/examples/sources/orangecrab/led.lpf new file mode 100644 index 00000000..cad54d2b --- /dev/null +++ b/examples/sources/orangecrab/led.lpf @@ -0,0 +1,2 @@ +LOCATE COMP "led_o" SITE "K4"; +IOBUF PORT "led_o" IO_TYPE=LVCMOS33; diff --git a/examples/sources/s6micro/clk.ucf b/examples/sources/s6micro/clk.ucf new file mode 100644 index 00000000..c0230e2d --- /dev/null +++ b/examples/sources/s6micro/clk.ucf @@ -0,0 +1 @@ +NET "clk_i" LOC = "V10"; diff --git a/examples/sources/s6micro/clk.xcf b/examples/sources/s6micro/clk.xcf new file mode 100644 index 00000000..2f24ee45 --- /dev/null +++ b/examples/sources/s6micro/clk.xcf @@ -0,0 +1,2 @@ +NET "clk_i" TNM_NET = "clk_i"; +TIMESPEC "TS_clk_i" = PERIOD "clk_i" 20.00 ns HIGH 50%; diff --git a/examples/sources/s6micro/led.ucf b/examples/sources/s6micro/led.ucf new file mode 100644 index 00000000..c557e8c5 --- /dev/null +++ b/examples/sources/s6micro/led.ucf @@ -0,0 +1 @@ +NET "led_o" LOC = "C2"; From ed239f9526d88ffeccdeaf7f15dd1fc9b993f3bb Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Tue, 25 Jun 2024 23:57:28 -0300 Subject: [PATCH 106/254] Modified HDL to depends on more than one param/include/define --- examples/openflow/run.py | 9 ++-- examples/sources/slog/blink.sv | 5 ++- examples/sources/slog/include/header.svh | 1 - examples/sources/slog/include1/header1.svh | 1 + examples/sources/slog/include2/header2.svh | 1 + examples/sources/slog/top.sv | 49 ++++++++++++++++++---- examples/sources/vhdl/blink.vhdl | 5 ++- examples/sources/vhdl/blink_pkg.vhdl | 3 +- examples/sources/vhdl/top.vhdl | 8 ++-- examples/sources/vlog/blink.v | 5 ++- examples/sources/vlog/include/header.vh | 1 - examples/sources/vlog/include1/header1.vh | 1 + examples/sources/vlog/include2/header2.vh | 1 + examples/sources/vlog/top.v | 49 ++++++++++++++++++---- examples/vivado/run.py | 10 +++-- 15 files changed, 113 insertions(+), 36 deletions(-) delete mode 100644 examples/sources/slog/include/header.svh create mode 100644 examples/sources/slog/include1/header1.svh create mode 100644 examples/sources/slog/include2/header2.svh delete mode 100644 examples/sources/vlog/include/header.vh create mode 100644 examples/sources/vlog/include1/header1.vh create mode 100644 examples/sources/vlog/include2/header2.vh diff --git a/examples/openflow/run.py b/examples/openflow/run.py index 8677a8a4..d591b195 100644 --- a/examples/openflow/run.py +++ b/examples/openflow/run.py @@ -1,4 +1,4 @@ -"""Vivado examples.""" +"""Openflow examples.""" import argparse @@ -21,12 +21,15 @@ prj = Openflow(odir='../build/openflow') prj.add_param('FREQ', '100000000') +prj.add_param('SECS', '1') if args.source == 'vlog': - prj.add_include('../sources/vlog/include') + prj.add_include('../sources/vlog/include1') + prj.add_include('../sources/vlog/include2') prj.add_vlog('../sources/vlog/*.v') if args.source in ['vlog', 'slog']: - prj.add_define('DEFINE', '1') + prj.add_define('DEFINE1', '1') + prj.add_define('DEFINE2', '1') prj.set_top('Top') diff --git a/examples/sources/slog/blink.sv b/examples/sources/slog/blink.sv index b74bfe08..4e2679e9 100644 --- a/examples/sources/slog/blink.sv +++ b/examples/sources/slog/blink.sv @@ -1,11 +1,12 @@ module Blink #( - parameter int FREQ = 25000000 + parameter int FREQ = 25000000, + parameter int SECS = 1 )( input clk_i, output led_o ); - localparam int DIV = FREQ; + localparam int DIV = FREQ*SECS; logic led = 0; logic [$clog2(DIV)-1:0] cnt = 0; diff --git a/examples/sources/slog/include/header.svh b/examples/sources/slog/include/header.svh deleted file mode 100644 index 78aa9bfd..00000000 --- a/examples/sources/slog/include/header.svh +++ /dev/null @@ -1 +0,0 @@ -`define INCLUDE diff --git a/examples/sources/slog/include1/header1.svh b/examples/sources/slog/include1/header1.svh new file mode 100644 index 00000000..ed066ea9 --- /dev/null +++ b/examples/sources/slog/include1/header1.svh @@ -0,0 +1 @@ +`define INCLUDE1 diff --git a/examples/sources/slog/include2/header2.svh b/examples/sources/slog/include2/header2.svh new file mode 100644 index 00000000..c45793ec --- /dev/null +++ b/examples/sources/slog/include2/header2.svh @@ -0,0 +1 @@ +`define INCLUDE2 diff --git a/examples/sources/slog/top.sv b/examples/sources/slog/top.sv index 24e780e5..4665011a 100644 --- a/examples/sources/slog/top.sv +++ b/examples/sources/slog/top.sv @@ -1,23 +1,54 @@ -`include "header.svh" +`include "header1.svh" +`include "header2.svh" module Top #( - parameter FREQ = 0 + parameter int FREQ = 0, + parameter int SECS = 0 )( input clk_i, output led_o ); initial begin - if (FREQ==0) begin - $stop("FREQ must be greater than 0"); - $error("FREQ must be greater than 0"); - $fatal("FREQ must be greater than 0"); + if (!FREQ) begin + $stop("FREQ not set"); + $error("FREQ not set"); + $fatal("FREQ not set"); end + if (!SECS) begin + $stop("SECS not set"); + $error("SECS not set"); + $fatal("SECS not set"); + end + `ifndef INCLUDE1 + $stop("INCLUDE1 not defined"); + $error("INCLUDE1 not defined"); + $fatal("INCLUDE1 not defined"); + `endif + `ifndef INCLUDE2 + $stop("INCLUDE2 not defined"); + $error("INCLUDE2 not defined"); + $fatal("INCLUDE2 not defined"); + `endif + `ifndef DEFINE1 + $stop("DEFINE1 not defined"); + $error("DEFINE1 not defined"); + $fatal("DEFINE1 not defined"); + `endif + `ifndef DEFINE2 + $stop("DEFINE2 not defined"); + $error("DEFINE2 not defined"); + $fatal("DEFINE2 not defined"); + `endif end -`ifdef INCLUDE -`ifdef DEFINE - Blink #(.FREQ (FREQ)) dut (.clk_i (clk_i), .led_o (led_o)); +`ifdef INCLUDE1 +`ifdef INCLUDE2 +`ifdef DEFINE1 +`ifdef DEFINE2 + Blink #(.FREQ (FREQ), .SECS (SECS)) dut (.clk_i (clk_i), .led_o (led_o)); +`endif +`endif `endif `endif diff --git a/examples/sources/vhdl/blink.vhdl b/examples/sources/vhdl/blink.vhdl index 9b9a4bc3..156f553c 100644 --- a/examples/sources/vhdl/blink.vhdl +++ b/examples/sources/vhdl/blink.vhdl @@ -3,7 +3,8 @@ use IEEE.std_logic_1164.all; entity Blink is generic ( - FREQ : positive := 25e6 + FREQ : positive := 25e6; + SECS : positive := 1 ); port ( clk_i : in std_logic; @@ -12,7 +13,7 @@ entity Blink is end entity Blink; architecture RTL of Blink is - constant DIV : positive := FREQ; + constant DIV : positive := FREQ*SECS; signal led : std_logic := '0'; signal cnt : natural range 0 to DIV-1 := 0; begin diff --git a/examples/sources/vhdl/blink_pkg.vhdl b/examples/sources/vhdl/blink_pkg.vhdl index 115f0899..3f6ee86a 100644 --- a/examples/sources/vhdl/blink_pkg.vhdl +++ b/examples/sources/vhdl/blink_pkg.vhdl @@ -5,7 +5,8 @@ package blink_pkg is component Blink is generic ( - FREQ : natural:=25e6 + FREQ : positive := 25e6; + SECS : positive := 1 ); port ( clk_i : in std_logic; diff --git a/examples/sources/vhdl/top.vhdl b/examples/sources/vhdl/top.vhdl index 0ff6d780..24977d6c 100644 --- a/examples/sources/vhdl/top.vhdl +++ b/examples/sources/vhdl/top.vhdl @@ -5,7 +5,8 @@ use blink_lib.blink_pkg.all; entity Top is generic ( - FREQ : natural := 0 + FREQ : natural := 0; + SECS : natural := 0 ); port ( clk_i : in std_logic; @@ -16,10 +17,11 @@ end entity Top; architecture ARCH of Top is begin - assert FREQ > 0 report "FREQ must be greater than 0" severity failure; + assert FREQ > 0 report "FREQ not set" severity failure; + assert SECS > 0 report "SECS not set" severity failure; blink_i: Blink - generic map (FREQ => FREQ) + generic map (FREQ => FREQ, SECS => SECS) port map (clk_i => clk_i, led_o => led_o); end architecture ARCH; diff --git a/examples/sources/vlog/blink.v b/examples/sources/vlog/blink.v index 7522e0f7..120b8c9f 100644 --- a/examples/sources/vlog/blink.v +++ b/examples/sources/vlog/blink.v @@ -1,11 +1,12 @@ module Blink #( - parameter FREQ = 25000000 + parameter FREQ = 25000000, + parameter SECS = 1 )( input clk_i, output led_o ); - localparam DIV = FREQ; + localparam DIV = FREQ*SECS; reg led = 0; reg [$clog2(DIV)-1:0] cnt = 0; diff --git a/examples/sources/vlog/include/header.vh b/examples/sources/vlog/include/header.vh deleted file mode 100644 index 78aa9bfd..00000000 --- a/examples/sources/vlog/include/header.vh +++ /dev/null @@ -1 +0,0 @@ -`define INCLUDE diff --git a/examples/sources/vlog/include1/header1.vh b/examples/sources/vlog/include1/header1.vh new file mode 100644 index 00000000..ed066ea9 --- /dev/null +++ b/examples/sources/vlog/include1/header1.vh @@ -0,0 +1 @@ +`define INCLUDE1 diff --git a/examples/sources/vlog/include2/header2.vh b/examples/sources/vlog/include2/header2.vh new file mode 100644 index 00000000..c45793ec --- /dev/null +++ b/examples/sources/vlog/include2/header2.vh @@ -0,0 +1 @@ +`define INCLUDE2 diff --git a/examples/sources/vlog/top.v b/examples/sources/vlog/top.v index 6c63ebae..fb6dff0b 100644 --- a/examples/sources/vlog/top.v +++ b/examples/sources/vlog/top.v @@ -1,23 +1,54 @@ -`include "header.vh" +`include "header1.vh" +`include "header2.vh" module Top #( - parameter FREQ = 0 + parameter FREQ = 0, + parameter SECS = 0 )( input clk_i, output led_o ); initial begin - if (FREQ==0) begin - $stop("FREQ must be greater than 0"); - $error("FREQ must be greater than 0"); - $fatal("FREQ must be greater than 0"); + if (!FREQ) begin + $stop("FREQ not set"); + $error("FREQ not set"); + $fatal("FREQ not set"); end + if (!SECS) begin + $stop("SECS not set"); + $error("SECS not set"); + $fatal("SECS not set"); + end + `ifndef INCLUDE1 + $stop("INCLUDE1 not defined"); + $error("INCLUDE1 not defined"); + $fatal("INCLUDE1 not defined"); + `endif + `ifndef INCLUDE2 + $stop("INCLUDE2 not defined"); + $error("INCLUDE2 not defined"); + $fatal("INCLUDE2 not defined"); + `endif + `ifndef DEFINE1 + $stop("DEFINE1 not defined"); + $error("DEFINE1 not defined"); + $fatal("DEFINE1 not defined"); + `endif + `ifndef DEFINE2 + $stop("DEFINE2 not defined"); + $error("DEFINE2 not defined"); + $fatal("DEFINE2 not defined"); + `endif end -`ifdef INCLUDE -`ifdef DEFINE - Blink #(.FREQ (FREQ)) dut (.clk_i (clk_i), .led_o (led_o)); +`ifdef INCLUDE1 +`ifdef INCLUDE2 +`ifdef DEFINE1 +`ifdef DEFINE2 + Blink #(.FREQ (FREQ), .SECS (SECS)) dut (.clk_i (clk_i), .led_o (led_o)); +`endif +`endif `endif `endif diff --git a/examples/vivado/run.py b/examples/vivado/run.py index 2431466e..cf110c2f 100644 --- a/examples/vivado/run.py +++ b/examples/vivado/run.py @@ -31,17 +31,21 @@ prj.add_cons('../sources/arty/a7-35t/timing.xdc', 'syn') prj.add_cons('../sources/arty/a7-35t/clk.xdc', 'par') prj.add_cons('../sources/arty/a7-35t/led.xdc', 'par') +prj.add_param('SECS', '1') if args.source == 'vhdl': prj.add_vhdl('../sources/vhdl/*.vhdl', 'blink_lib') if args.source == 'vlog': - prj.add_include('../sources/vlog/include') + 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/include') + prj.add_include('../sources/slog/include1') + prj.add_include('../sources/slog/include2') prj.add_vlog('../sources/slog/*.sv') if args.source in ['vlog', 'slog']: - prj.add_define('DEFINE', '1') + prj.add_define('DEFINE1', '1') + prj.add_define('DEFINE2', '1') prj.set_top('Top') From 12ab0a46edf30dac01bee4916675b37e00d11d5d Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Wed, 26 Jun 2024 00:16:30 -0300 Subject: [PATCH 107/254] Small fix --- examples/vivado/run.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/vivado/run.py b/examples/vivado/run.py index cf110c2f..8ae97d6f 100644 --- a/examples/vivado/run.py +++ b/examples/vivado/run.py @@ -17,7 +17,7 @@ ) args = parser.parse_args() -prj = Vivado(odir=f'../build/vivado') +prj = Vivado(odir='../build/vivado') if args.board == 'zybo': prj.set_part('xc7z010-1-clg400') From e61cf5b979197350c8d3e509ea29b4a304e83543 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Wed, 26 Jun 2024 00:17:07 -0300 Subject: [PATCH 108/254] Simplified Openflow example content --- examples/openflow/Makefile | 7 ---- examples/openflow/ecp5evn.lpf | 5 --- examples/openflow/edu-ciaa-fpga.pcf | 2 -- examples/openflow/icestick.pcf | 2 -- examples/openflow/icestorm.py | 50 --------------------------- examples/openflow/orangecrab_r0.2.lpf | 5 --- examples/openflow/prjtrellis.py | 50 --------------------------- examples/openflow/run.py | 27 +++++++++++++++ examples/openflow/run.sh | 16 +++++++++ 9 files changed, 43 insertions(+), 121 deletions(-) delete mode 100644 examples/openflow/Makefile delete mode 100644 examples/openflow/ecp5evn.lpf delete mode 100644 examples/openflow/edu-ciaa-fpga.pcf delete mode 100644 examples/openflow/icestick.pcf delete mode 100644 examples/openflow/icestorm.py delete mode 100644 examples/openflow/orangecrab_r0.2.lpf delete mode 100644 examples/openflow/prjtrellis.py create mode 100644 examples/openflow/run.sh diff --git a/examples/openflow/Makefile b/examples/openflow/Makefile deleted file mode 100644 index 05df0f94..00000000 --- a/examples/openflow/Makefile +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/make - -all: - python3 icestorm.py --lang verilog - python3 icestorm.py --lang vhdl - python3 prjtrellis.py --board ecp5evn - python3 prjtrellis.py --lang vhdl diff --git a/examples/openflow/ecp5evn.lpf b/examples/openflow/ecp5evn.lpf deleted file mode 100644 index 1b0195e3..00000000 --- a/examples/openflow/ecp5evn.lpf +++ /dev/null @@ -1,5 +0,0 @@ -LOCATE COMP "clk_i" SITE "A10"; -IOBUF PORT "clk_i" IO_TYPE=LVCMOS33; - -LOCATE COMP "led_o" SITE "A13"; -IOBUF PORT "led_o" IO_TYPE=LVCMOS25; diff --git a/examples/openflow/edu-ciaa-fpga.pcf b/examples/openflow/edu-ciaa-fpga.pcf deleted file mode 100644 index 17fb1134..00000000 --- a/examples/openflow/edu-ciaa-fpga.pcf +++ /dev/null @@ -1,2 +0,0 @@ -set_io clk_i 94 -set_io led_o 4 diff --git a/examples/openflow/icestick.pcf b/examples/openflow/icestick.pcf deleted file mode 100644 index caa6e413..00000000 --- a/examples/openflow/icestick.pcf +++ /dev/null @@ -1,2 +0,0 @@ -set_io clk_i 21 -set_io led_o 95 diff --git a/examples/openflow/icestorm.py b/examples/openflow/icestorm.py deleted file mode 100644 index a71bec78..00000000 --- a/examples/openflow/icestorm.py +++ /dev/null @@ -1,50 +0,0 @@ -"""Icestorm example project.""" - -import argparse -import logging - -from fpga.project import Project - -logging.basicConfig() - -parser = argparse.ArgumentParser() -parser.add_argument( - '--action', choices=['generate', 'transfer', 'all'], default='generate' -) -parser.add_argument( - '--lang', choices=['verilog', 'vhdl'], default='verilog' -) -parser.add_argument( - '--board', - choices=['icestick', 'edu-ciaa-fpga'], - default='icestick' -) -args = parser.parse_args() - -BOARDS = { - 'icestick': ['hx1k-tq144', 'icestick.pcf'], - 'edu-ciaa-fpga': ['hx4k-tq144', 'edu-ciaa-fpga.pcf'] -} - -prj = Project('openflow') -prj.set_outdir('../../build/icestorm-{}-{}'.format(args.board, args.lang)) -prj.set_part(BOARDS[args.board][0]) - -if args.lang == 'verilog': - prj.add_vlog_include('../../hdl/headers1') - prj.add_vlog_include('../../hdl/headers2') - prj.add_files('../../hdl/blinking.v') - prj.add_files('../../hdl/top.v') -else: # args.lang == 'vhdl' - prj.add_files('../../hdl/blinking.vhdl', library='examples') - prj.add_files('../../hdl/examples_pkg.vhdl', library='examples') - prj.add_files('../../hdl/top.vhdl') - -prj.add_files(BOARDS[args.board][1]) -prj.set_top('Top') - -if args.action in ['generate', 'all']: - prj.generate() - -if args.action in ['transfer', 'all']: - prj.transfer() diff --git a/examples/openflow/orangecrab_r0.2.lpf b/examples/openflow/orangecrab_r0.2.lpf deleted file mode 100644 index 573a3e2a..00000000 --- a/examples/openflow/orangecrab_r0.2.lpf +++ /dev/null @@ -1,5 +0,0 @@ -LOCATE COMP "clk_i" SITE "A9"; -IOBUF PORT "clk_i" IO_TYPE=LVCMOS33; - -LOCATE COMP "led_o" SITE "K4"; -IOBUF PORT "led_o" IO_TYPE=LVCMOS33; diff --git a/examples/openflow/prjtrellis.py b/examples/openflow/prjtrellis.py deleted file mode 100644 index bece88a9..00000000 --- a/examples/openflow/prjtrellis.py +++ /dev/null @@ -1,50 +0,0 @@ -"""Trellis example project.""" - -import argparse -import logging - -from fpga.project import Project - -logging.basicConfig() - -parser = argparse.ArgumentParser() -parser.add_argument( - '--action', choices=['generate', 'transfer', 'all'], default='generate' -) -parser.add_argument( - '--lang', choices=['verilog', 'vhdl'], default='verilog' -) -parser.add_argument( - '--board', - choices=['orangecrab', 'ecp5evn'], - default='orangecrab' -) -args = parser.parse_args() - -BOARDS = { - 'orangecrab': ['25k-CSFBGA285', 'orangecrab_r0.2.lpf'], - 'ecp5evn': ['um5g-85k-CABGA381', 'ecp5evn.lpf'] -} - -prj = Project('openflow') -prj.set_outdir('../../build/prjtrellis-{}-{}'.format(args.board, args.lang)) -prj.set_part(BOARDS[args.board][0]) - -if args.lang == 'verilog': - prj.add_vlog_include('../../hdl/headers1') - prj.add_vlog_include('../../hdl/headers2') - prj.add_files('../../hdl/blinking.v') - prj.add_files('../../hdl/top.v') -else: # args.lang == 'vhdl' - prj.add_files('../../hdl/blinking.vhdl', library='examples') - prj.add_files('../../hdl/examples_pkg.vhdl', library='examples') - prj.add_files('../../hdl/top.vhdl') - -prj.add_files(BOARDS[args.board][1]) -prj.set_top('Top') - -if args.action in ['generate', 'all']: - prj.generate() - -if args.action in ['transfer', 'all']: - prj.transfer() diff --git a/examples/openflow/run.py b/examples/openflow/run.py index d591b195..b01153c3 100644 --- a/examples/openflow/run.py +++ b/examples/openflow/run.py @@ -20,13 +20,40 @@ prj = Openflow(odir='../build/openflow') +if args.board == 'icestick': + prj.set_part('hx1k-tq144') + prj.add_param('FREQ', '100000000') + prj.add_cons('../sources/icestick/clk.pcf', 'par') + prj.add_cons('../sources/icestick/led.pcf', 'par') +if args.board == 'edu-ciaa': + prj.set_part('hx1k-tq144') + prj.add_param('FREQ', '100000000') + prj.add_cons('../sources/edu-ciaa/clk.pcf', 'par') + prj.add_cons('../sources/edu-ciaa/led.pcf', 'par') +if args.board == 'orangecrab': + prj.set_part('25k-CSFBGA285') + prj.add_param('FREQ', '100000000') + prj.add_cons('../sources/orangecrab/clk.lpf', 'par') + prj.add_cons('../sources/orangecrab/led.lpf', 'par') +if args.board == 'ecp5evn': + prj.set_part('um5g-85k-CABGA381') + prj.add_param('FREQ', '100000000') + prj.add_cons('../sources/ecp5evn/clk.lpf', 'par') + prj.add_cons('../sources/ecp5evn/led.lpf', 'par') + prj.add_param('FREQ', '100000000') prj.add_param('SECS', '1') +if args.source == 'vhdl': + prj.add_vhdl('../sources/vhdl/*.vhdl', 'blink_lib') 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_vlog('../sources/slog/*.sv') if args.source in ['vlog', 'slog']: prj.add_define('DEFINE1', '1') prj.add_define('DEFINE2', '1') diff --git a/examples/openflow/run.sh b/examples/openflow/run.sh new file mode 100644 index 00000000..4e213f40 --- /dev/null +++ b/examples/openflow/run.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +set -e + +BOARDS=("icestick" "edu-ciaa" "orangecrab" "ecp5evn") +SOURCES=("vlog" "vhdl" "slog") +ACTIONS=("make" "prog" "all") + +for BOARD in "${BOARDS[@]}"; do + for SOURCE in "${SOURCES[@]}"; do + for ACTION in "${ACTIONS[@]}"; do + echo "> $BOARD - $SOURCE - $ACTION" + python3 run.py --board $BOARD --source $SOURCE --action $ACTION + done + done +done From b52435e41b0d7bed910c2c0344697519db57972a Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Wed, 26 Jun 2024 00:29:30 -0300 Subject: [PATCH 109/254] Removed deprecated files --- examples/ghdl/Makefile | 6 ------ examples/ghdl/ghdl.py | 17 ----------------- examples/hooks/Makefile | 6 ------ examples/ise/Makefile | 7 ------- examples/ise/nexys3.ucf | 7 ------- examples/ise/nexys3.xcf | 6 ------ examples/ise/s6micro.ucf | 7 ------- examples/ise/s6micro.xcf | 6 ------ examples/libero/Makefile | 6 ------ examples/libero/mkr.pdc | 4 ---- examples/libero/mkr.sdc | 6 ------ examples/misc/Makefile | 6 ------ examples/misc/capture.py | 23 ----------------------- examples/misc/logger.py | 25 ------------------------- examples/multi/Makefile | 6 ------ examples/quartus/Makefile | 6 ------ examples/quartus/de10nano.sdc | 6 ------ examples/quartus/de10nano.tcl | 2 -- examples/yosys/Makefile | 6 ------ examples/yosys/README.md | 26 -------------------------- examples/yosys/yosys.py | 31 ------------------------------- 21 files changed, 215 deletions(-) delete mode 100644 examples/ghdl/Makefile delete mode 100644 examples/ghdl/ghdl.py delete mode 100644 examples/hooks/Makefile delete mode 100644 examples/ise/Makefile delete mode 100644 examples/ise/nexys3.ucf delete mode 100644 examples/ise/nexys3.xcf delete mode 100644 examples/ise/s6micro.ucf delete mode 100644 examples/ise/s6micro.xcf delete mode 100644 examples/libero/Makefile delete mode 100644 examples/libero/mkr.pdc delete mode 100644 examples/libero/mkr.sdc delete mode 100644 examples/misc/Makefile delete mode 100644 examples/misc/capture.py delete mode 100644 examples/misc/logger.py delete mode 100644 examples/multi/Makefile delete mode 100644 examples/quartus/Makefile delete mode 100644 examples/quartus/de10nano.sdc delete mode 100644 examples/quartus/de10nano.tcl delete mode 100644 examples/yosys/Makefile delete mode 100644 examples/yosys/README.md delete mode 100644 examples/yosys/yosys.py diff --git a/examples/ghdl/Makefile b/examples/ghdl/Makefile deleted file mode 100644 index f166a9f9..00000000 --- a/examples/ghdl/Makefile +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/make - -SCRIPTS = $(wildcard *.py) - -all: - @$(foreach SCRIPT, $(SCRIPTS), python3 $(SCRIPT) || exit;) diff --git a/examples/ghdl/ghdl.py b/examples/ghdl/ghdl.py deleted file mode 100644 index da2ef275..00000000 --- a/examples/ghdl/ghdl.py +++ /dev/null @@ -1,17 +0,0 @@ -"""Generic GHDL example project.""" - -import logging - -from fpga.project import Project - -logging.basicConfig() - -prj = Project('ghdl') -prj.set_outdir('../../build/ghdl') - -prj.add_files('../../hdl/blinking.vhdl', library='examples') -prj.add_files('../../hdl/examples_pkg.vhdl', library='examples') -prj.add_files('../../hdl/top.vhdl') -prj.set_top('Top') - -prj.generate() diff --git a/examples/hooks/Makefile b/examples/hooks/Makefile deleted file mode 100644 index f166a9f9..00000000 --- a/examples/hooks/Makefile +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/make - -SCRIPTS = $(wildcard *.py) - -all: - @$(foreach SCRIPT, $(SCRIPTS), python3 $(SCRIPT) || exit;) diff --git a/examples/ise/Makefile b/examples/ise/Makefile deleted file mode 100644 index 230d1cf7..00000000 --- a/examples/ise/Makefile +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/make - -SCRIPTS = $(wildcard *.py) - -all: - python3 ise.py - python3 ise.py --board s6micro diff --git a/examples/ise/nexys3.ucf b/examples/ise/nexys3.ucf deleted file mode 100644 index 298bd77a..00000000 --- a/examples/ise/nexys3.ucf +++ /dev/null @@ -1,7 +0,0 @@ -# User Constraints File -# -# Used during the implementation to specify timing, placement and pinout -# constraints. - -NET "clk_i" LOC = "V10"; -NET "led_o" LOC = "U16"; diff --git a/examples/ise/nexys3.xcf b/examples/ise/nexys3.xcf deleted file mode 100644 index d8b19371..00000000 --- a/examples/ise/nexys3.xcf +++ /dev/null @@ -1,6 +0,0 @@ -# Xilinx Constraint File -# -# Used during the synthesis (XST) to specify timing and synthesis constraints. - -NET "clk_i" TNM_NET = "clk_i"; -TIMESPEC "TS_clk_i" = PERIOD "clk_i" 10.00 ns HIGH 50%; diff --git a/examples/ise/s6micro.ucf b/examples/ise/s6micro.ucf deleted file mode 100644 index 18a5cc0f..00000000 --- a/examples/ise/s6micro.ucf +++ /dev/null @@ -1,7 +0,0 @@ -# User Constraints File -# -# Used during the implementation to specify timing, placement and pinout -# constraints. - -NET "clk_i" LOC = "V10"; -NET "led_o" LOC = "C2"; diff --git a/examples/ise/s6micro.xcf b/examples/ise/s6micro.xcf deleted file mode 100644 index 6849968d..00000000 --- a/examples/ise/s6micro.xcf +++ /dev/null @@ -1,6 +0,0 @@ -# Xilinx Constraint File -# -# Used during the synthesis (XST) to specify timing and synthesis constraints. - -NET "clk_i" TNM_NET = "clk_i"; -TIMESPEC "TS_clk_i" = PERIOD "clk_i" 20.00 ns HIGH 50%; diff --git a/examples/libero/Makefile b/examples/libero/Makefile deleted file mode 100644 index f166a9f9..00000000 --- a/examples/libero/Makefile +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/make - -SCRIPTS = $(wildcard *.py) - -all: - @$(foreach SCRIPT, $(SCRIPTS), python3 $(SCRIPT) || exit;) diff --git a/examples/libero/mkr.pdc b/examples/libero/mkr.pdc deleted file mode 100644 index 78acfe06..00000000 --- a/examples/libero/mkr.pdc +++ /dev/null @@ -1,4 +0,0 @@ -# Physical Design Constraints - -set_io clk_i -DIRECTION INPUT -pinname 23 -fixed yes -set_io led_o -DIRECTION OUTPUT -pinname 117 -fixed yes diff --git a/examples/libero/mkr.sdc b/examples/libero/mkr.sdc deleted file mode 100644 index db18e27b..00000000 --- a/examples/libero/mkr.sdc +++ /dev/null @@ -1,6 +0,0 @@ -# Synopsys Design Constraint -# -# Is a Tcl-based format used by Synopsys tools to specify the design intent -# and timing constraints. - -create_clock -period 20 [get_ports clk_i] diff --git a/examples/misc/Makefile b/examples/misc/Makefile deleted file mode 100644 index f166a9f9..00000000 --- a/examples/misc/Makefile +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/make - -SCRIPTS = $(wildcard *.py) - -all: - @$(foreach SCRIPT, $(SCRIPTS), python3 $(SCRIPT) || exit;) diff --git a/examples/misc/capture.py b/examples/misc/capture.py deleted file mode 100644 index 4284b212..00000000 --- a/examples/misc/capture.py +++ /dev/null @@ -1,23 +0,0 @@ -"""PyFPGA capture example. - -Example about how to capture the execution messages to be post-processed -or saved into a file. -""" - -import logging - -from fpga.project import Project - -logging.basicConfig() - -PRJ = Project('ise', 'capture') -PRJ.set_outdir('../../build/capture') - -PRJ.add_files('../../hdl/*.vhdl', library='examples') -PRJ.set_top('Top') - -output = PRJ.generate(to_task='syn', capture=True) -print(output) - -output = PRJ.transfer(devtype='detect', capture=True) -print(output) diff --git a/examples/misc/logger.py b/examples/misc/logger.py deleted file mode 100644 index 7f34ca1c..00000000 --- a/examples/misc/logger.py +++ /dev/null @@ -1,25 +0,0 @@ -""" -This script demonstrates how to utilize the logging functionality within the -pyfpga package. The following steps are covered: - -1. Creating an instance of the Project class. -2. Testing logging with the default INFO level. -3. Setting the logging level to DEBUG to capture more detailed information. -4. Disabling logging by removing all handlers. - -Usage: -- By default, the logger captures messages with level INFO and higher. -- To see more detailed debug information, set the logger level to DEBUG. -- To disable logging, remove all handlers from the logger. -""" - -import logging - -from pyfpga.project import Project - -prj = Project() -prj.set_part('EXAMPLE') -prj.logger.setLevel(logging.DEBUG) -prj.set_part('EXAMPLE') -prj.logger.handlers = [] -prj.set_part('EXAMPLE') diff --git a/examples/multi/Makefile b/examples/multi/Makefile deleted file mode 100644 index f166a9f9..00000000 --- a/examples/multi/Makefile +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/make - -SCRIPTS = $(wildcard *.py) - -all: - @$(foreach SCRIPT, $(SCRIPTS), python3 $(SCRIPT) || exit;) diff --git a/examples/quartus/Makefile b/examples/quartus/Makefile deleted file mode 100644 index f166a9f9..00000000 --- a/examples/quartus/Makefile +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/make - -SCRIPTS = $(wildcard *.py) - -all: - @$(foreach SCRIPT, $(SCRIPTS), python3 $(SCRIPT) || exit;) diff --git a/examples/quartus/de10nano.sdc b/examples/quartus/de10nano.sdc deleted file mode 100644 index db18e27b..00000000 --- a/examples/quartus/de10nano.sdc +++ /dev/null @@ -1,6 +0,0 @@ -# Synopsys Design Constraint -# -# Is a Tcl-based format used by Synopsys tools to specify the design intent -# and timing constraints. - -create_clock -period 20 [get_ports clk_i] diff --git a/examples/quartus/de10nano.tcl b/examples/quartus/de10nano.tcl deleted file mode 100644 index 19494e68..00000000 --- a/examples/quartus/de10nano.tcl +++ /dev/null @@ -1,2 +0,0 @@ -set_location_assignment PIN_V11 -to clk_i -set_location_assignment PIN_W15 -to led_o diff --git a/examples/yosys/Makefile b/examples/yosys/Makefile deleted file mode 100644 index f07825c7..00000000 --- a/examples/yosys/Makefile +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/make - -SCRIPTS = $(wildcard *.py) - -all: - @$(foreach SCRIPT, $(SCRIPTS), python3 $(SCRIPT) || exit; python3 $(SCRIPT) --lang vhdl || exit; ) diff --git a/examples/yosys/README.md b/examples/yosys/README.md deleted file mode 100644 index 44649f92..00000000 --- a/examples/yosys/README.md +++ /dev/null @@ -1,26 +0,0 @@ -# Yosys examples - -## Yosys synthesis - -* Verilog project: `python3 yosys.py` -* VHDL project: `python3 yosys.py --lang vhdl` - -## Yosys using ISE as backend - -Bitstream generation: -* Verilog project: `python3 ise.py` -* VHDL project: `python3 ise.py --lang vhdl` - -Spartan-6 FPGA LX9 MicroBoard programming: -* Verilog project: `python3 ise.py --action transfer` -* VHDL project: `python3 ise.py --lang vhdl --action transfer` - -## Yosys using Vivado as backend - -Bitstream generation: -* Verilog project: `python3 vivado.py` -* VHDL project: `python3 vivado.py --lang vhdl` - -Zybo programming: -* Verilog project: `python3 vivado.py --action transfer` -* VHDL project: `python3 vivado.py --lang vhdl --action transfer` diff --git a/examples/yosys/yosys.py b/examples/yosys/yosys.py deleted file mode 100644 index 3553d515..00000000 --- a/examples/yosys/yosys.py +++ /dev/null @@ -1,31 +0,0 @@ -"""Yosys example project.""" - -import argparse -import logging - -from fpga.project import Project - -logging.basicConfig() - -parser = argparse.ArgumentParser() -parser.add_argument( - '--lang', choices=['verilog', 'vhdl'], default='verilog', -) -args = parser.parse_args() - -prj = Project('yosys') -prj.set_outdir('../../build/yosys-{}'.format(args.lang)) - -if args.lang == 'verilog': - prj.add_vlog_include('../../hdl/headers1') - prj.add_vlog_include('../../hdl/headers2') - prj.add_files('../../hdl/blinking.v') - prj.add_files('../../hdl/top.v') -else: # args.lang == 'vhdl' - prj.add_files('../../hdl/blinking.vhdl', library='examples') - prj.add_files('../../hdl/examples_pkg.vhdl', library='examples') - prj.add_files('../../hdl/top.vhdl') - -prj.set_top('Top') - -prj.generate() From 548d7ab4a9a4e972d1c8e1b30260dd9c8430f695 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Thu, 27 Jun 2024 22:58:09 -0300 Subject: [PATCH 110/254] Implemented par and bit for Openflow --- pyfpga/openflow.py | 120 +++++--------------------------- pyfpga/templates/openflow.jinja | 67 +++++++++++++++++- 2 files changed, 83 insertions(+), 104 deletions(-) diff --git a/pyfpga/openflow.py b/pyfpga/openflow.py index 2849b4ba..10321b4c 100644 --- a/pyfpga/openflow.py +++ b/pyfpga/openflow.py @@ -12,6 +12,7 @@ # pylint: disable=too-many-branches # pylint: disable=duplicate-code +from pathlib import Path from pyfpga.project import Project @@ -21,7 +22,10 @@ class Openflow(Project): def _make_prepare(self, steps): context = { 'PROJECT': self.name or 'openflow', - 'PART': self.data.get('part', 'hx8k-ct256') + 'PART': self.data.get('part', 'hx8k-ct256'), + 'FAMILY': 'ice40', + 'DEVICE': 'hx8k', + 'PACKAGE': 'tq144:4k' } for step in steps: context[step] = 1 @@ -42,21 +46,11 @@ def _make_prepare(self, steps): # files.append( # f'set_property library {lib} [get_files {file}]' # ) -# if 'constraints' in self.data: -# for file in self.data['constraints']: -# files.append(f'add_file -fileset constrs_1 {file}') -# for file in self.data['constraints']: -# if self.data['constraints'][file] == 'syn': -# prop = 'USED_IN_IMPLEMENTATION FALSE' -# if self.data['constraints'][file] == 'syn': -# prop = 'USED_IN_SYNTHESIS FALSE' -# if self.data['constraints'][file] != 'all': -# files.append(f'set_property {prop} [get_files {file}]') -# first = next(iter(self.data['constraints'])) -# prop = f'TARGET_CONSTRS_FILE {first}' -# files.append(f'set_property {prop} [current_fileset -constrset]') -# if files: -# context['FILES'] = '\n'.join(files) + if 'constraints' in self.data: + constraints = [] + for constraint in self.data['constraints']: + constraints.append(str(constraint)) + context['CONSTRAINTS'] = ' '.join(constraints) if 'top' in self.data: context['TOP'] = self.data['top'] if 'defines' in self.data: @@ -75,57 +69,14 @@ def _make_prepare(self, steps): self._create_file('openflow', 'sh', context) return 'bash openflow.sh' -# def _prog_prepare(self, bitstream, position): -# _ = position # Not needed for Vivado -# if not bitstream: -# basename = self.name or 'vivado' -# bitstream = Path(self.odir).resolve() / f'{basename}.bit' -# context = {'BITSTREAM': bitstream} -# self._create_file('vivado-prog', 'tcl', context) -# return 'vivado -mode batch -notrace -quiet -source vivado-prog.tcl' - def _prog_prepare(self, bitstream, position): - # binaries = ['bit'] - self.tool['prog-app'] = 'docker' - self.tool['prog-cmd'] = 'bash openflow-prog.sh' - -# def __init__(self, project, frontend='yosys', backend='nextpnr'): -# # The valid frontends are be ghdl and yosys -# # The valid backends are: -# # * For ghdl -> vhdl -# # * For yosys -> ise, nextpnr, verilog, verilog-nosynth and vivado -# super().__init__(project) -# self.backend = backend -# self.frontend = frontend - -# def _configure(self): -# super()._configure() -# # OCI ENGINE -# engine = self.configs.get('oci', {}).get('engine', {}) -# command = engine.get('command', 'docker') + ' run --rm' -# volumes = -# '-v ' + ('-v ').join(engine.get('volumes', ['$HOME:$HOME'])) -# work = '-w ' + engine.get('work', '$PWD') -# self.oci_engine = f'{command} {volumes} {work}' -# # Containers -# defaults = { -# 'ghdl': 'ghdl/synth:beta', -# 'yosys': 'ghdl/synth:beta', -# 'nextpnr-ice40': 'ghdl/synth:nextpnr-ice40', -# 'icetime': 'ghdl/synth:icestorm', -# 'icepack': 'ghdl/synth:icestorm', -# 'iceprog': '--device /dev/bus/usb ghdl/synth:prog', -# 'nextpnr-ecp5': 'ghdl/synth:nextpnr-ecp5', -# 'ecppack': 'ghdl/synth:trellis', -# 'openocd': '--device /dev/bus/usb ghdl/synth:prog' -# } -# self.tools = {} -# self.conts = {} -# tools = self.configs.get('tools', {}) -# containers = self.configs.get('oci', {}).get('containers', {}) -# for tool, container in defaults.items(): -# self.tools[tool] = tools.get(tool, tool) -# self.conts[tool] = containers.get(tool, container) + _ = position + if not bitstream: + basename = self.name or 'openflow' + bitstream = Path(self.odir).resolve() / f'{basename}.bit' + context = {'BITSTREAM': bitstream} + self._create_file('openflow-prog', 'sh', context) + return 'bash openflow-prog.sh' # def set_part(self, part): # self.part['name'] = part @@ -172,43 +123,6 @@ def _prog_prepare(self, bitstream, position): # params = [] # for param in self.params: # params.append(f'chparam -set {param[0]} {param[1]} {self.top}') -# # Script creation -# template = os.path.join(os.path.dirname(__file__), 'template.sh') -# with open(template, 'r', encoding='utf-8') as file: -# text = file.read() -# text = text.format( -# backend=self.backend, -# constraints='\\\n'+'\n'.join(constraints), -# device=self.part['device'], -# includes='\\\n'+'\n'.join(paths), -# family=self.part['family'], -# frontend=self.frontend, -# package=self.part['package'], -# params='\\\n'+'\n'.join(params), -# project=self.project, -# tasks=tasks, -# top=self.top, -# verilogs='\\\n'+'\n'.join(verilogs), -# vhdls='\\\n'+'\n'.join(vhdls), -# # -# oci_engine=self.oci_engine, -# cont_ghdl=self.conts['ghdl'], -# cont_yosys=self.conts['yosys'], -# cont_nextpnr_ice40=self.conts['nextpnr-ice40'], -# cont_icetime=self.conts['icetime'], -# cont_icepack=self.conts['icepack'], -# cont_nextpnr_ecp5=self.conts['nextpnr-ecp5'], -# cont_ecppack=self.conts['ecppack'], -# tool_ghdl=self.tools['ghdl'], -# tool_yosys=self.tools['yosys'], -# tool_nextpnr_ice40=self.tools['nextpnr-ice40'], -# tool_icetime=self.tools['icetime'], -# tool_icepack=self.tools['icepack'], -# tool_nextpnr_ecp5=self.tools['nextpnr-ecp5'], -# tool_ecppack=self.tools['ecppack'] -# ) -# with open(f'{self._TOOL}.sh', 'w', encoding='utf-8') as file: -# file.write(text) # def generate(self, to_task, from_task, capture): # if self.frontend == 'ghdl' or 'verilog' in self.backend: diff --git a/pyfpga/templates/openflow.jinja b/pyfpga/templates/openflow.jinja index 6b4580c2..971149be 100644 --- a/pyfpga/templates/openflow.jinja +++ b/pyfpga/templates/openflow.jinja @@ -6,7 +6,9 @@ set -e -DOCKER="docker run --rm -v $HOME:$HOME -w $PWD" +DOCKER="docker run --user $(id -u):$(id -g) --rm -v $HOME:$HOME -w $PWD" + +# Synthesis ------------------------------------------------------------------- {% if SYN %} $DOCKER hdlc/ghdl:yosys /bin/bash -c " @@ -25,19 +27,82 @@ verilog_defines {{ DEFINES }} chparam {{ PARAMS }} {{ TOP }} {% endif %} synth -top {{ TOP }} +synth_{{ FAMILY }} -top {{ TOP }} -json {{ PROJECT }}.json ' {{ POSTSYN }} " {% endif %} +#SYNTH= +#WRITE= +#if [[ $BACKEND == "vivado" ]]; then +# SYNTH="synth_xilinx -top $TOP -family $FAMILY" +# WRITE="write_edif -pvector bra $PROJECT.edif" +#elif [[ $BACKEND == "ise" ]]; then +# SYNTH="synth_xilinx -top $TOP -family $FAMILY -ise" +# WRITE="write_edif -pvector bra $PROJECT.edif" +#elif [[ $BACKEND == "nextpnr" ]]; then +# SYNTH="synth_$FAMILY -top $TOP -json $PROJECT.json" +#elif [[ $BACKEND == "verilog-nosynth" ]]; then +# WRITE="write_verilog $PROJECT.v" +#else +# SYNTH="synth -top $TOP" +# WRITE="write_verilog $PROJECT.v" +#fi + +# Place and Route ------------------------------------------------------------- + {% if PAR %} + +CONSTRAINTS="{{ CONSTRAINTS }}" + +{% if FAMILY == 'ice40' %} +if [ -n "$CONSTRAINTS" ]; then + cat $CONSTRAINTS > constraints.pcf + CONSTRAINT="--pcf constraints.pcf" +fi +$DOCKER hdlc/nextpnr:ice40 /bin/bash -c " {{ PREPAR }} +nextpnr-ice40 --{{ DEVICE }} --package {{ PACKAGE }} $CONSTRAINT --json {{ PROJECT }}.json --asc {{ PROJECT }}.asc +{{ POSTPAR }} +" +$DOCKER hdlc/icestorm /bin/bash -c " +icetime -d {{ DEVICE }} -mtr {{ PROJECT }}.rpt {{ PROJECT }}.asc +" +{% endif %} +{% if FAMILY == 'ecp5' %} +if [ -n "$CONSTRAINTS" ]; then + cat $CONSTRAINTS > constraints.lpf + CONSTRAINT="--lpf constraints.lpf" +fi +$DOCKER hdlc/nextpnr:ecp5 /bin/bash -c " +{{ PREPAR }} +nextpnr-ecp5 --{{ DEVICE }} --package {{ PACKAGE }} $CONSTRAINT --json {{ PROJECT }}.json --textcfg {{ PROJECT }}.config {{ POSTPAR }} +" {% endif %} +{% endif %} + +# Bitstream ------------------------------------------------------------------- + {% if BIT %} + +{% if FAMILY == 'ice40' %} +$DOCKER hdlc/icestorm /bin/bash -c " {{ PREBIT }} +icepack {{ PROJECT }}.asc {{ PROJECT }}.bit +{{ POSTBIT }} +" +{% endif %} +{% if FAMILY == 'ecp5' %} +$DOCKER hdlc/icestorm /bin/bash -c " +{{ PREBIT }} +ecppack --svf {{ PROJECT }}.svf {{ PROJECT }}.config {{ PROJECT }}.bit {{ POSTBIT }} +" +{% endif %} + {% endif %} From 69ae00b474feaa58abd78ad6f16c5910323e460f Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Thu, 27 Jun 2024 23:22:29 -0300 Subject: [PATCH 111/254] Prepared template for Openflow programming --- pyfpga/templates/openflow-prog.jinja | 37 ++++++---------------------- 1 file changed, 7 insertions(+), 30 deletions(-) diff --git a/pyfpga/templates/openflow-prog.jinja b/pyfpga/templates/openflow-prog.jinja index 027eb388..fc1c1105 100644 --- a/pyfpga/templates/openflow-prog.jinja +++ b/pyfpga/templates/openflow-prog.jinja @@ -6,35 +6,12 @@ set -e -############################################################################### -# Things to tuneup -############################################################################### +DOCKER="docker run --user $(id -u):$(id -g) --rm -v $HOME:$HOME -w $PWD" -FAMILY={family} -PROJECT={project} +{% if FAMILY == 'ice40' %} +$DOCKER --device /dev/bus/usb hdlc/prog iceprog {{ PROJECT }}.bit +{% endif %} -# -# Tools configuration -# - -OCI_ENGINE="{oci_engine}" - -CONT_ICEPROG="{cont_iceprog}" -CONT_OPENOCD="{cont_openocd}" - -TOOL_ICEPROG="{tool_iceprog}" -TOOL_OPENOCD="{tool_openocd}" - -############################################################################### -# Programming -############################################################################### - -if [[ $FAMILY == "ice40" ]]; then - $OCI_ENGINE $CONT_ICEPROG $TOOL_ICEPROG $PROJECT.bit -elif [[ $FAMILY == "ecp5" ]]; then - $OCI_ENGINE $CONT_OPENOCD $TOOL_OPENOCD \ - -f /usr/share/trellis/misc/openocd/ecp5-evn.cfg \ - -c "transport select jtag; init; svf $PROJECT.svf; exit" -else - echo "ERROR: unsuported tool" && exit 1 -fi +{% 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" +{% endif %} From 1d1c157276f12aba111aabc53d932113760bb28e Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Fri, 28 Jun 2024 20:33:14 -0300 Subject: [PATCH 112/254] Added get_info to obtain extra information based on the PART --- pyfpga/ise.py | 103 ++++++++++++++++++++--------------- pyfpga/libero.py | 99 ++++++++++++++++++--------------- pyfpga/openflow.py | 133 ++++++++++++++++++--------------------------- tests/test_part.py | 53 +++++++++++------- 4 files changed, 201 insertions(+), 187 deletions(-) diff --git a/pyfpga/ise.py b/pyfpga/ise.py index d5289930..22dab08f 100644 --- a/pyfpga/ise.py +++ b/pyfpga/ise.py @@ -8,7 +8,11 @@ Implements support for ISE. """ -# import re +# pylint: disable=too-many-locals +# pylint: disable=too-many-branches +# pylint: disable=duplicate-code + +import re from pyfpga.project import Project @@ -31,23 +35,6 @@ def _prog_prepare(self, bitstream, position): # _DEVTYPES = ['fpga', 'spi', 'bpi', 'detect', 'unlock'] -# def set_part(self, part): -# try: -# device, speed, package = -# re.findall(r'(\w+)-(\w+)-(\w+)', part)[0] -# if len(speed) > len(package): -# speed, package = package, speed -# part = f'{device}-{speed}-{package}' -# except IndexError: -# raise ValueError( -# 'Part must be DEVICE-SPEED-PACKAGE or DEVICE-PACKAGE-SPEED' -# ) -# self.part['name'] = part -# self.part['family'] = get_family(part) -# self.part['device'] = device -# self.part['package'] = package -# self.part['speed'] = '-' + speed - # def transfer(self, devtype, position, part, width, capture): # super().transfer(devtype, position, part, width, capture) # temp = _TEMPLATES[devtype] @@ -60,29 +47,57 @@ def _prog_prepare(self, bitstream, position): # file.write(temp) # return run(self._TRF_COMMAND, capture) -# def get_family(part): -# """Get the Family name from the specified part name.""" -# part = part.lower() -# families = { -# r'xc7a\d+l': 'artix7l', -# r'xc7a': 'artix7', -# r'xc7k\d+l': 'kintex7l', -# r'xc7k': 'kintex7', -# r'xc3sd\d+a': 'spartan3adsp', -# r'xc3s\d+a': 'spartan3a', -# r'xc3s\d+e': 'spartan3e', -# r'xc3s': 'spartan3', -# r'xc6s\d+l': 'spartan6l', -# r'xc6s': 'spartan6', -# r'xc4v': 'virtex4', -# r'xc5v': 'virtex5', -# r'xc6v\d+l': 'virtex6l', -# r'xc6v': 'virtex6', -# r'xc7v\d+l': 'virtex7l', -# r'xc7v': 'virtex7', -# r'xc7z': 'zynq' -# } -# for key, value in families.items(): -# if re.match(key, part): -# return value -# return 'UNKNOWN' + +def get_info(part): + """Get info about the FPGA part. + + :param part: the FPGA part as specified by the tool + :returns: a dictionary with the keys family, device, speed and package + """ + part = part.lower() + # Looking for the family + family = None + families = { + r'xc7a\d+l': 'artix7l', + r'xc7a': 'artix7', + r'xc7k\d+l': 'kintex7l', + r'xc7k': 'kintex7', + r'xc3sd\d+a': 'spartan3adsp', + r'xc3s\d+a': 'spartan3a', + r'xc3s\d+e': 'spartan3e', + r'xc3s': 'spartan3', + r'xc6s\d+l': 'spartan6l', + r'xc6s': 'spartan6', + r'xc4v': 'virtex4', + r'xc5v': 'virtex5', + r'xc6v\d+l': 'virtex6l', + r'xc6v': 'virtex6', + r'xc7v\d+l': 'virtex7l', + r'xc7v': 'virtex7', + r'xc7z': 'zynq' + } + for key, value in families.items(): + if re.match(key, part): + family = value + break + # Looking for the device, package and speed + device = None + speed = None + package = None + aux = part.split('-') + if len(aux) == 3: + device = aux[0] + if len(aux[1]) < len(aux[2]): + speed = aux[1] + package = aux[2] + else: + speed = aux[2] + package = aux[1] + else: + raise ValueError( + 'Part must be DEVICE-SPEED-PACKAGE or DEVICE-PACKAGE-SPEED' + ) + # Finish + return { + 'family': family, 'device': device, 'speed': speed, 'package': package + } diff --git a/pyfpga/libero.py b/pyfpga/libero.py index a717f1b7..7f807906 100644 --- a/pyfpga/libero.py +++ b/pyfpga/libero.py @@ -8,7 +8,11 @@ Implements support for Libero. """ -# import re +# pylint: disable=too-many-locals +# pylint: disable=too-many-branches +# pylint: disable=duplicate-code + +import re from pyfpga.project import Project @@ -29,47 +33,56 @@ def _prog_prepare(self, bitstream, position): self.tool['prog-app'] = '' self.tool['prog-cmd'] = '' -# def set_part(self, part): -# try: -# device, speed, package = -# re.findall(r'(\w+)-(\w+)-*(\w*)', part)[0] -# if len(speed) > len(package): -# speed, package = package, speed -# if speed == '': -# speed = 'STD' -# part = f'{device}-{speed}-{package}' -# except IndexError: -# raise ValueError( -# 'Part must be DEVICE-SPEED-PACKAGE or DEVICE-PACKAGE' -# ) -# self.part['name'] = part -# self.part['family'] = get_family(part) -# self.part['device'] = device -# self.part['package'] = package -# self.part['speed'] = 'STD' if speed == 'STD' else '-' + speed -# def transfer(self, devtype, position, part, width, capture): -# super().transfer(devtype, position, part, width, capture) -# raise NotImplementedError('transfer(libero)') +def get_info(part): + """Get info about the FPGA part. -# def get_family(part): -# """Get the Family name from the specified part name.""" -# part = part.lower() -# families = { -# r'm2s': 'SmartFusion2', -# r'm2gl': 'Igloo2', -# r'rt4g': 'RTG4', -# r'mpf': 'PolarFire', -# r'a2f': 'SmartFusion', -# r'afs': 'Fusion', -# r'aglp': 'IGLOO+', -# r'agle': 'IGLOOE', -# r'agl': 'IGLOO', -# r'a3p\d+l': 'ProAsic3L', -# r'a3pe': 'ProAsic3E', -# r'a3p': 'ProAsic3' -# } -# for key, value in families.items(): -# if re.match(key, part): -# return value -# return 'UNKNOWN' + :param part: the FPGA part as specified by the tool + :returns: a dictionary with the keys family, device, speed and package + """ + part = part.lower() + # Looking for the family + family = None + families = { + r'm2s': 'SmartFusion2', + r'm2gl': 'Igloo2', + r'rt4g': 'RTG4', + r'mpf': 'PolarFire', + r'a2f': 'SmartFusion', + r'afs': 'Fusion', + r'aglp': 'IGLOO+', + r'agle': 'IGLOOE', + r'agl': 'IGLOO', + r'a3p\d+l': 'ProAsic3L', + r'a3pe': 'ProAsic3E', + r'a3p': 'ProAsic3' + } + for key, value in families.items(): + if re.match(key, part): + family = value + break + # Looking for the device and package + device = None + speed = None + package = None + aux = part.split('-') + if len(aux) == 2: + device = aux[0] + speed = 'STD' + package = aux[1] + elif len(aux) == 3: + device = aux[0] + if len(aux[1]) < len(aux[2]): + speed = aux[1] + package = aux[2] + else: + speed = aux[2] + package = aux[1] + else: + raise ValueError( + 'Part must be DEVICE-SPEED-PACKAGE or DEVICE-PACKAGE' + ) + # Finish + return { + 'family': family, 'device': device, 'speed': speed, 'package': package + } diff --git a/pyfpga/openflow.py b/pyfpga/openflow.py index 10321b4c..8b40b259 100644 --- a/pyfpga/openflow.py +++ b/pyfpga/openflow.py @@ -20,12 +20,12 @@ class Openflow(Project): """Class to support Open Source tools.""" def _make_prepare(self, steps): + info = get_info(self.data.get('part', 'hx8k-ct256')) context = { 'PROJECT': self.name or 'openflow', - 'PART': self.data.get('part', 'hx8k-ct256'), - 'FAMILY': 'ice40', - 'DEVICE': 'hx8k', - 'PACKAGE': 'tq144:4k' + 'FAMILY': info['family'], + 'DEVICE': info['device'], + 'PACKAGE': info['package'] } for step in steps: context[step] = 1 @@ -78,29 +78,7 @@ def _prog_prepare(self, bitstream, position): self._create_file('openflow-prog', 'sh', context) return 'bash openflow-prog.sh' -# def set_part(self, part): -# self.part['name'] = part -# self.part['family'] = get_family(part) -# if self.part['family'] in ['ice40', 'ecp5']: -# aux = part.split('-') -# if len(aux) == 2: -# self.part['device'] = aux[0] -# self.part['package'] = aux[1] -# elif len(aux) == 3: -# self.part['device'] = f'{aux[0]}-{aux[1]}' -# self.part['package'] = aux[2] -# else: -# raise ValueError('Part must be DEVICE-PACKAGE') -# if self.part['device'].endswith('4k'): -# # See http://www.clifford.at/icestorm/ -# self.part['device'] = self.part['device'].replace('4', '8') -# self.part['package'] += ":4k" - # def _create_gen_script(self, tasks): -# # Verilog includes -# paths = [] -# for path in self.paths: -# paths.append(f'verilog_defaults -add -I{path}') # # Files # constraints = [] # verilogs = [] @@ -119,59 +97,56 @@ def _prog_prepare(self, bitstream, position): # constraints.append(file[0]) # if len(vhdls) > 0: # verilogs = [f'ghdl $FLAGS {self.top}'] -# # Parameters -# params = [] -# for param in self.params: -# params.append(f'chparam -set {param[0]} {param[1]} {self.top}') -# def generate(self, to_task, from_task, capture): -# if self.frontend == 'ghdl' or 'verilog' in self.backend: -# to_task = 'syn' -# from_task = 'syn' -# return super().generate(to_task, from_task, capture) -# def transfer(self, devtype, position, part, width, capture): -# super().transfer(devtype, position, part, width, capture) -# template = os.path.join(os.path.dirname(__file__), 'openprog.sh') -# with open(template, 'r', encoding='utf-8') as file: -# text = file.read() -# text = text.format( -# family=self.part['family'], -# project=self.project, -# # -# oci_engine=self.oci_engine, -# cont_iceprog=self.conts['iceprog'], -# cont_openocd=self.conts['openocd'], -# tool_iceprog=self.tools['iceprog'], -# tool_openocd=self.tools['openocd'] -# ) -# with open('openprog.sh', 'w', encoding='utf-8') as file: -# file.write(text) -# return run(self._TRF_COMMAND, capture) +def get_info(part): + """Get info about the FPGA part. -# def get_family(part): -# """Get the Family name from the specified part name.""" -# part = part.lower() -# families = [ -# # From /techlibs/xilinx/synth_xilinx.cc -# 'xcup', 'xcu', 'xc7', 'xc6s', 'xc6v', 'xc5v', 'xc4v', 'xc3sda', -# 'xc3sa', 'xc3se', 'xc3s', 'xc2vp', 'xc2v', 'xcve', 'xcv' -# ] -# for family in families: -# if part.startswith(family): -# return family -# families = [ -# # From /ice40/main.cc -# 'lp384', 'lp1k', 'lp4k', 'lp8k', 'hx1k', 'hx4k', 'hx8k', -# 'up3k', 'up5k', 'u1k', 'u2k', 'u4k' -# ] -# if part.startswith(tuple(families)): -# return 'ice40' -# families = [ -# # From /ecp5/main.cc -# '12k', '25k', '45k', '85k', 'um-25k', 'um-45k', 'um-85k', -# 'um5g-25k', 'um5g-45k', 'um5g-85k' -# ] -# if part.startswith(tuple(families)): -# return 'ecp5' -# return 'UNKNOWN' + :param part: the FPGA part as specified by the tool + :returns: a dictionary with the keys family, device and package + """ + part = part.lower() + # Looking for the family + family = None + families = [ + # From /techlibs/xilinx/synth_xilinx.cc + 'xcup', 'xcu', 'xc7', 'xc6s', 'xc6v', 'xc5v', 'xc4v', 'xc3sda', + 'xc3sa', 'xc3se', 'xc3s', 'xc2vp', 'xc2v', 'xcve', 'xcv' + ] + for item in families: + if part.startswith(item): + family = item + break + families = [ + # From /ice40/main.cc + 'lp384', 'lp1k', 'lp4k', 'lp8k', 'hx1k', 'hx4k', 'hx8k', + 'up3k', 'up5k', 'u1k', 'u2k', 'u4k' + ] + if part.startswith(tuple(families)): + family = 'ice40' + families = [ + # From /ecp5/main.cc + '12k', '25k', '45k', '85k', 'um-25k', 'um-45k', 'um-85k', + 'um5g-25k', 'um5g-45k', 'um5g-85k' + ] + if part.startswith(tuple(families)): + family = 'ecp5' + # Looking for the device and package + device = None + package = None + aux = part.split('-') + if len(aux) == 2: + device = aux[0] + package = aux[1] + elif len(aux) == 3: + device = f'{aux[0]}-{aux[1]}' + package = aux[2] + else: + raise ValueError('Part must be DEVICE-PACKAGE') + if family in ['lp4k', 'hx4k']: # See http://www.clifford.at/icestorm + device = device.replace('4', '8') + package += ":4k" + if family == 'ecp5': + package = package.upper() + # Finish + return {'family': family, 'device': device, 'package': package} diff --git a/tests/test_part.py b/tests/test_part.py index 5cee3e71..7fd24ec7 100644 --- a/tests/test_part.py +++ b/tests/test_part.py @@ -1,27 +1,38 @@ -from pyfpga.project import Project +from pyfpga.ise import get_info as get_info_ise +from pyfpga.libero import get_info as get_info_libero +from pyfpga.openflow import get_info as get_info_openflow -# def get_part(prj): -# return prj.get_configs()['part'].lower() +def test_ise(): + info = { + 'family': 'kintex7', + 'device': 'xc7k160t', + 'speed': '3', + 'package': 'fbg484' + } + assert get_info_ise('xc7k160t-3-fbg484') == info + assert get_info_ise('xc7k160t-fbg484-3') == info -# def test_ise(): -# prj = Project('ise') -# assert get_part(prj) == "xc7k160t-3-fbg484" -# prj.set_part('XC6SLX9-2-CSG324') -# assert get_part(prj) == "xc6slx9-2-csg324" -# prj.set_part('XC6SLX9-2L-CSG324') -# assert get_part(prj) == "xc6slx9-2l-csg324" -# prj.set_part('XC6SLX9-CSG324-3') -# assert get_part(prj) == "xc6slx9-3-csg324" +def test_libero(): + info = { + 'family': 'SmartFusion2', + 'device': 'm2s010', + 'speed': '1', + 'package': 'tq144' + } + assert get_info_libero('m2s010-1-tq144') == info + assert get_info_libero('m2s010-tq144-1') == info + info['speed'] = 'STD' + assert get_info_libero('m2s010-tq144') == info -# def test_libero(): -# prj = Project('libero') -# assert get_part(prj) == "mpf100t-1-fcg484" -# prj.set_part('m2s010-3-tq144') -# assert get_part(prj) == "m2s010-3-tq144" -# prj.set_part('m2s010-tq144-2') -# assert get_part(prj) == "m2s010-2-tq144" -# prj.set_part('m2s010-tq144') -# assert get_part(prj) == "m2s010-std-tq144" +def test_openflow(): + info = {'family': 'xc7', 'device': 'xc7k160t-3', 'package': 'fbg484'} + assert get_info_openflow('xc7k160t-3-fbg484') == info + info = {'family': 'ice40', 'device': 'hx1k', 'package': 'tq144'} + assert get_info_openflow('hx1k-tq144') == info + info = {'family': 'ecp5', 'device': '25k', 'package': 'CSFBGA285'} + assert get_info_openflow('25k-CSFBGA285') == info + info = {'family': 'ecp5', 'device': 'um5g-85k', 'package': 'CABGA381'} + assert get_info_openflow('um5g-85k-CABGA381') == info From 7031a189d0b0432b0002f4f6783bb97c5b97d729 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sat, 29 Jun 2024 00:54:19 -0300 Subject: [PATCH 113/254] Re-added ISE support --- examples/ise/ise.py | 46 ---------------- examples/ise/run.py | 50 +++++++++++++++++ pyfpga/ise.py | 65 ++++++++++++++++++---- pyfpga/templates/ise.jinja | 108 ++++++++++++++++--------------------- 4 files changed, 151 insertions(+), 118 deletions(-) delete mode 100644 examples/ise/ise.py create mode 100644 examples/ise/run.py diff --git a/examples/ise/ise.py b/examples/ise/ise.py deleted file mode 100644 index c4f90b41..00000000 --- a/examples/ise/ise.py +++ /dev/null @@ -1,46 +0,0 @@ -"""ISE example project.""" - -import argparse -import logging - -from fpga.project import Project - -logging.basicConfig() - -parser = argparse.ArgumentParser() -parser.add_argument( - '--action', choices=['generate', 'transfer', 'all'], default='generate', -) -parser.add_argument( - '--board', - choices=['nexys3', 's6micro'], - default='nexys3' -) -args = parser.parse_args() - -BOARDS = { - 'nexys3': ['XC6SLX16-3-CSG324', 'nexys3.ucf', 'nexys3.xcf'], - 's6micro': ['XC6SLX9-2-CSG324', 's6micro.ucf', 's6micro.xcf'] -} - -prj = Project('ise') -prj.set_part(BOARDS[args.board][0]) - -prj.set_outdir('../../build/ise-{}'.format(args.board)) - -prj.add_files('../../hdl/blinking.vhdl', library='examples') -prj.add_files('../../hdl/examples_pkg.vhdl', library='examples') -prj.add_files('../../hdl/top.vhdl') -prj.set_top('Top') - -prj.add_files(BOARDS[args.board][1]) -prj.add_files(BOARDS[args.board][2]) - -if args.action in ['generate', 'all']: - prj.generate() - -if args.action in ['transfer', 'all']: - prj.transfer('fpga') - # prj.transfer('detect') - # prj.transfer('unlock') - # prj.transfer('spi', 1, 'N25Q128', 4) diff --git a/examples/ise/run.py b/examples/ise/run.py new file mode 100644 index 00000000..93a35b13 --- /dev/null +++ b/examples/ise/run.py @@ -0,0 +1,50 @@ +"""ISE examples.""" + +import argparse + +from pyfpga.ise import Ise + +parser = argparse.ArgumentParser() +parser.add_argument( + '--board', choices=['s6micro', 'nexys3'], default='s6micro' +) +parser.add_argument( + '--source', choices=['vlog', 'vhdl'], default='vlog' +) +parser.add_argument( + '--action', choices=['make', 'prog', 'all'], default='make' +) +args = parser.parse_args() + +prj = Ise(odir='../build/ise') + +if args.board == 's6micro': + prj.set_part('xc6slx9-2-csg324') + prj.add_param('FREQ', '125000000') + prj.add_cons('../sources/s6micro/clk.xcf', 'syn') + prj.add_cons('../sources/s6micro/clk.ucf', 'par') + prj.add_cons('../sources/s6micro/led.ucf', 'par') +if args.board == 'nexys3': + prj.set_part('xc6slx16-3-csg32') + prj.add_param('FREQ', '100000000') + prj.add_cons('../sources/nexys3/clk.xcf', 'syn') + prj.add_cons('../sources/nexys3/clk.ucf', 'par') + prj.add_cons('../sources/nexys3/led.ucf', 'par') +prj.add_param('SECS', '1') + +if args.source == 'vhdl': + prj.add_vhdl('../sources/vhdl/*.vhdl', 'blink_lib') +if args.source == 'vlog': + 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') + +if args.action in ['make', 'all']: + prj.make() + +if args.action in ['prog', 'all']: + prj.prog() diff --git a/pyfpga/ise.py b/pyfpga/ise.py index 22dab08f..1777539b 100644 --- a/pyfpga/ise.py +++ b/pyfpga/ise.py @@ -20,18 +20,65 @@ class Ise(Project): """Class to support ISE projects.""" - def __init__(self, name='ise', odir='results'): - super().__init__(name=name, odir=odir) - self.set_part('xc7k160t-3-fbg484') - def _make_prepare(self, steps): - self.tool['make-app'] = 'xtclsh' - self.tool['make-cmd'] = 'xtclsh ise.tcl' + info = get_info(self.data.get('part', 'xc7k160t-3-fbg484')) + context = { + 'PROJECT': self.name or 'ise', + 'FAMILY': info['family'], + 'DEVICE': info['device'], + 'SPEED': info['speed'], + 'PACKAGE': info['package'] + } + for step in steps: + context[step] = 1 + if 'includes' in self.data: + includes = [] + for include in self.data['includes']: + includes.append(str(include)) + context['INCLUDES'] = '|'.join(includes) + files = [] + if 'files' in self.data: + for file in self.data['files']: + if 'lib' in self.data['files']: + lib = self.data['files'][file]['lib'] + files.append(f'lib_vhdl new {lib}') + files.append(f'xfile add {file} -lib_vhdl {lib}') + else: + files.append(f'xfile add {file}') + if 'constraints' in self.data: + for file in self.data['constraints']: + files.append(f'xfile add {file}') + if files: + context['FILES'] = '\n'.join(files) + if 'top' in self.data: + context['TOP'] = self.data['top'] + if 'defines' in self.data: + defines = [] + for key, value in self.data['defines'].items(): + defines.append(f'{key}={value}') + context['DEFINES'] = ' | '.join(defines) + if 'params' in self.data: + params = [] + for key, value in self.data['params'].items(): + params.append(f'{key}={value}') + context['PARAMS'] = ' '.join(params) + if 'hooks' in self.data: + for stage in self.data['hooks']: + context[stage.upper()] = '\n'.join(self.data['hooks'][stage]) + self._create_file('ise', 'tcl', context) + return 'xtclsh ise.tcl' def _prog_prepare(self, bitstream, position): - # binaries = ['bit'] - self.tool['prog-app'] = 'impact' - self.tool['prog-cmd'] = 'impact -batch impact-prog' + if not bitstream: + basename = self.name or 'ise' + bitstream = Path(self.odir).resolve() / f'{basename}.bit' + context = {'BITSTREAM': bitstream, 'POSITION': position} + self._create_file('vivado-prog', 'tcl', context) + return 'impact -batch impact-prog' + + def add_slog(self, pathname): + """Add System Verilog file/s.""" + raise NotImplementedError('ISE does not support SystemVerilog') # _DEVTYPES = ['fpga', 'spi', 'bpi', 'detect', 'unlock'] diff --git a/pyfpga/templates/ise.jinja b/pyfpga/templates/ise.jinja index c70efad6..79b3f9de 100644 --- a/pyfpga/templates/ise.jinja +++ b/pyfpga/templates/ise.jinja @@ -4,66 +4,49 @@ # SPDX-License-Identifier: GPL-3.0-or-later # -set PROJECT {{ PROJECT }} -set PART {{ PART }} -set FAMILY {{ FAMILY }} -set DEVICE {{ DEVICE }} -set PACKAGE {{ PACKAGE }} -set SPEED {{ SPEED }} -set TOP {{ TOP }} - -set PARAMS [list {{ PARAMS }}] - -proc fpga_file {FILE {LIBRARY "work"}} { - set message "adding the file '$FILE'" - if { $LIBRARY != "work" } { append message " (into the VHDL library '$LIBRARY')" } - regexp -nocase {\.(\w*)$} $FILE -> ext - if { $ext == "tcl" } { - source $FILE - return - } - if {$ext == "xcf"} { - project set "Synthesis Constraints File" $FILE -process "Synthesize - XST" - } elseif { $LIBRARY != "work" } { - lib_vhdl new $LIBRARY - xfile add $FILE -lib_vhdl $LIBRARY - } else { - xfile add $FILE - } -} - -proc fpga_include {PATH} { - lappend INCLUDED $PATH - # Verilog Included Files are NOT added - project set "Verilog Include Directories" \ - [join $INCLUDED "|"] -process "Synthesize - XST" -} - -proc fpga_params {} { - if { [llength $PARAMS] == 0 } { return } - set assigns [list] - foreach PARAM $PARAMS { lappend assigns [join $PARAM "="] } - project set "Generics, Parameters" "[join $assigns]" -process "Synthesize - XST" -} - -#--[ Project configuration ]--------------------------------------------------- +# if {$ext == "xcf"} { +# project set "Synthesis Constraints File" $FILE -process "Synthesize - XST" +# } elseif { $LIBRARY != "work" } { +# lib_vhdl new $LIBRARY +# xfile add $FILE -lib_vhdl $LIBRARY +# } else { +# xfile add $FILE +# } +#} + +#--[ Project configuration ]-------------------------------------------------- {% if CFG %} -if { [ file exists $PROJECT.xise ] } { file delete $PROJECT.xise } -project new $PROJECT.xise - -project set family $FAMILY -project set device $DEVICE -project set package $PACKAGE -project set speed $SPEED +if { [ file exists {{ PROJECT }}.xise ] } { file delete {{ PROJECT }}.xise } +project new {{ PROJECT }}.xise +project set family {{ FAMILY }} +project set device {{ DEVICE }} +project set package {{ PACKAGE }} +project set speed -{{ SPEED }} {{ PRECFG }} {{ FILES }} +{% if CONSTRAINTS %} +project set "Synthesis Constraints File" "{{ CONSTRAINTS }}" -process "Synthesize - XST" +{% endif %} -project set top $TOP +{% if TOP %} +project set top {{ TOP }} +{% endif %} -fpga_params +{% if DEFINES %} +project set "Verilog Macros" "{{ DEFINES }}" -process "Synthesize - XST" +{% endif %} + +{% if INCLUDES %} +project set "Verilog Include Directories" "{{ INCLUDES }}" -process "Synthesize - XST" +# [join $INCLUDED "|"] +{% endif %} + +{% if PARAMS %} +project set "Generics, Parameters" "{{ PARAMS }}" -process "Synthesize - XST" +{% endif %} {{ POSTCFG }} @@ -73,18 +56,16 @@ project close #--[ Design flow ]------------------------------------------------------------- {% if SYN or PAR or BIT %} -project open $PROJECT.xise +project open {{ PROJECT }}.xise {% if SYN %} {{ PRESYN }} -{% if PRESYNTH %} -project set top_level_module_type "EDIF" -{% else %} +# PRESYNTH +#project set top_level_module_type "EDIF" project clean process run "Synthesize" -if { [process get "Synthesize" status] == "errors" } { exit 2 } -{% endif %} +if { [process get "Synthesize" status] == "errors" } { exit 1 } {{ POSTSYN }} {% endif %} @@ -93,11 +74,11 @@ if { [process get "Synthesize" status] == "errors" } { exit 2 } {{ PREPAR }} process run "Translate" -if { [process get "Translate" status] == "errors" } { exit 2 } +if { [process get "Translate" status] == "errors" } { exit 1 } process run "Map" -if { [process get "Map" status] == "errors" } { exit 2 } +if { [process get "Map" status] == "errors" } { exit 1 } process run "Place & Route" -if { [process get "Place & Route" status] == "errors" } { exit 2 } +if { [process get "Place & Route" status] == "errors" } { exit 1 } {{ POSTPAR }} {% endif %} @@ -106,8 +87,9 @@ if { [process get "Place & Route" status] == "errors" } { exit 2 } {{ PREBIT }} process run "Generate Programming File" -if { [process get "Generate Programming File" status] == "errors" } { exit 2 } -catch { file rename -force $TOP.bit $PROJECT.bit } +if { [process get "Generate Programming File" status] == "errors" } { exit 1 } +# catch { file rename -force {{ TOP }}.bit {{ PROJECT }}.bit } +file rename -force {{ TOP }}.bit {{ PROJECT }}.bit {{ POSTBIT }} {% endif %} From f0044941e1c7535d9e93c7bc159e3ed8f7bcd2e4 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sat, 29 Jun 2024 14:00:55 -0300 Subject: [PATCH 114/254] Fixed to include ISE XST constraints --- pyfpga/ise.py | 5 +++++ pyfpga/templates/ise.jinja | 10 ---------- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/pyfpga/ise.py b/pyfpga/ise.py index 1777539b..b3c86739 100644 --- a/pyfpga/ise.py +++ b/pyfpga/ise.py @@ -46,8 +46,13 @@ def _make_prepare(self, steps): else: files.append(f'xfile add {file}') if 'constraints' in self.data: + constraints = [] for file in self.data['constraints']: files.append(f'xfile add {file}') + if file.suffix == '.xcf': + constraints.append(str(file)) + if constraints: + context['CONSTRAINTS'] = " ".join(constraints) if files: context['FILES'] = '\n'.join(files) if 'top' in self.data: diff --git a/pyfpga/templates/ise.jinja b/pyfpga/templates/ise.jinja index 79b3f9de..024e0edb 100644 --- a/pyfpga/templates/ise.jinja +++ b/pyfpga/templates/ise.jinja @@ -4,16 +4,6 @@ # SPDX-License-Identifier: GPL-3.0-or-later # -# if {$ext == "xcf"} { -# project set "Synthesis Constraints File" $FILE -process "Synthesize - XST" -# } elseif { $LIBRARY != "work" } { -# lib_vhdl new $LIBRARY -# xfile add $FILE -lib_vhdl $LIBRARY -# } else { -# xfile add $FILE -# } -#} - #--[ Project configuration ]-------------------------------------------------- {% if CFG %} From 349af14700764a5f3a5956b787f2c4f19c185da3 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sat, 29 Jun 2024 15:13:05 -0300 Subject: [PATCH 115/254] Modified to have different log files for make and prog --- pyfpga/project.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pyfpga/project.py b/pyfpga/project.py index faaacffe..d33dbd93 100644 --- a/pyfpga/project.py +++ b/pyfpga/project.py @@ -228,7 +228,7 @@ def make(self, first='cfg', last='bit'): message = steps[first] self.logger.info('Running %s', message) selected = [step.upper() for step in keys[index[0]:index[1]+1]] - self._run(self._make_prepare(selected)) + self._run(self._make_prepare(selected), 'make.log') def prog(self, bitstream=None, position=1): """Program the FPGA @@ -244,7 +244,7 @@ def prog(self, bitstream=None, position=1): if position not in range(1, 9): raise ValueError('Invalid position.') self.logger.info('Programming') - self._run(self._prog_prepare(bitstream, position)) + self._run(self._prog_prepare(bitstream, position), 'prog.log') def _make_prepare(self, steps): raise NotImplementedError('Tool-dependent') @@ -264,7 +264,7 @@ def _create_file(self, basename, extension, context): with open(directory / filename, 'w', encoding='utf-8') as file: file.write(content) - def _run(self, command): + def _run(self, command, logname): num = 20 error = 0 old_dir = Path.cwd() @@ -272,13 +272,13 @@ def _run(self, command): start = time() try: os.chdir(new_dir) - with open('run.log', 'w', encoding='utf-8') as file: + with open(logname, 'w', encoding='utf-8') as file: subprocess.run( command, shell=True, check=True, text=True, stdout=file, stderr=subprocess.STDOUT ) except subprocess.CalledProcessError: - with open('run.log', 'r', encoding='utf-8') as file: + with open(logname, 'r', encoding='utf-8') as file: lines = file.readlines() last_lines = lines[-num:] if len(lines) >= num else lines for line in last_lines: From 47cb94286a5f6189ddcf71d876cdeb41e9fd3371 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sat, 29 Jun 2024 15:25:34 -0300 Subject: [PATCH 116/254] Small fix on quartus-prog and prepared ise-prog --- pyfpga/templates/ise-prog.jinja | 56 +++++++++++++---------------- pyfpga/templates/quartus-prog.jinja | 2 +- 2 files changed, 25 insertions(+), 33 deletions(-) diff --git a/pyfpga/templates/ise-prog.jinja b/pyfpga/templates/ise-prog.jinja index 4c18a072..2ba0c193 100644 --- a/pyfpga/templates/ise-prog.jinja +++ b/pyfpga/templates/ise-prog.jinja @@ -4,57 +4,49 @@ # SPDX-License-Identifier: GPL-3.0-or-later # -_TEMPLATES = { - 'fpga': """setMode -bs +cleancablelock + +{% if FPGA %} +setMode -bs setCable -port auto Identify -inferir -assignFile -p #POSITION# -file #BITSTREAM# -Program -p #POSITION# +assignFile -p {{ POSITION }} -file {{ BITSTREAM }} +Program -p {{ POSITION }} +{% endif %} -quit -""", - 'spi': """setMode -pff -addConfigDevice -name #NAME# -path . +{% if SPI %} +setMode -pff +addConfigDevice -name {{ NAME }} -path . setSubmode -pffspi addDesign -version 0 -name 0 addDeviceChain -index 0 -addDevice -p 1 -file #BITSTREAM# +addDevice -p 1 -file {{ BITSTREAM }} generate -generic setMode -bs setCable -port auto Identify -attachflash -position #POSITION# -spi #NAME# -assignfiletoattachedflash -position #POSITION# -file ./#NAME#.mcs -Program -p #POSITION# -dataWidth #WIDTH# -spionly -e -v -loadfpga +attachflash -position {{ POSITION }} -spi {{ NAME }} +assignfiletoattachedflash -position {{ POSITION }} -file ./{{ NAME }}.mcs +Program -p {{ POSITION }} -dataWidth {{ WIDTH }} -spionly -e -v -loadfpga +{% endif %} -quit -""", - 'bpi': """setMode -pff -addConfigDevice -name #NAME# -path . +{% if BPI %} +setMode -pff +addConfigDevice -name {{ NAME }} -path . setSubmode -pffbpi addDesign -version 0 -name 0 addDeviceChain -index 0 -setAttribute -configdevice -attr flashDataWidth -value #WIDTH# -addDevice -p 1 -file #BITSTREAM# +setAttribute -configdevice -attr flashDataWidth -value {{ WIDTH }} +addDevice -p 1 -file {{ BITSTREAM }} generate -generic setMode -bs setCable -port auto Identify -attachflash -position #POSITION# -bpi #NAME# -assignfiletoattachedflash -position #POSITION# -file ./#NAME#.mcs -Program -p #POSITION# -dataWidth #WIDTH# \ --rs1 NONE -rs0 NONE -bpionly -e -v -loadfpga +attachflash -position {{ POSITION }} -bpi {{ NAME }} +assignfiletoattachedflash -position {{ POSITION }} -file ./{{ NAME }}.mcs +Program -p {{ POSITION }} -dataWidth {{ WIDTH }} -rs1 NONE -rs0 NONE -bpionly -e -v -loadfpga +{% endif %} quit -""", - 'detect': """setMode -bs -setCable -port auto -Identify -inferir -quit -""", - 'unlock': """cleancablelock -quit -""" -} diff --git a/pyfpga/templates/quartus-prog.jinja b/pyfpga/templates/quartus-prog.jinja index 45ee765b..d754fee5 100644 --- a/pyfpga/templates/quartus-prog.jinja +++ b/pyfpga/templates/quartus-prog.jinja @@ -4,4 +4,4 @@ # SPDX-License-Identifier: GPL-3.0-or-later # -{{ COMMAND }} -c %s --mode jtag -o "p;{{ BITSTREAM }}@{{ POSITION }}" +quartus_pgm -c {{ CABLE }} --mode jtag -o "p;{{ BITSTREAM }}@{{ POSITION }}" From 7e13dc7767a88351a5782b84e0ecd88b2155c813 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sat, 29 Jun 2024 15:27:55 -0300 Subject: [PATCH 117/254] Added missing import of Path --- pyfpga/ise.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyfpga/ise.py b/pyfpga/ise.py index b3c86739..370c80ce 100644 --- a/pyfpga/ise.py +++ b/pyfpga/ise.py @@ -14,6 +14,7 @@ import re +from pathlib import Path from pyfpga.project import Project From f2fa17f73b887af724051adb8dc9779455146a7d Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sat, 29 Jun 2024 16:11:42 -0300 Subject: [PATCH 118/254] Added to raise an exception for Libero programming --- pyfpga/libero.py | 4 +--- pyfpga/templates/libero-prog.jinja | 7 ------- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/pyfpga/libero.py b/pyfpga/libero.py index 7f807906..5cf8efe4 100644 --- a/pyfpga/libero.py +++ b/pyfpga/libero.py @@ -29,9 +29,7 @@ def _make_prepare(self, steps): self.tool['make-cmd'] = 'libero SCRIPT:libero.tcl' def _prog_prepare(self, bitstream, position): - # binaries = ['bit'] - self.tool['prog-app'] = '' - self.tool['prog-cmd'] = '' + raise NotImplementedError('Libero programming not supported') def get_info(part): diff --git a/pyfpga/templates/libero-prog.jinja b/pyfpga/templates/libero-prog.jinja index ed9effe2..95b740f0 100644 --- a/pyfpga/templates/libero-prog.jinja +++ b/pyfpga/templates/libero-prog.jinja @@ -4,13 +4,6 @@ # SPDX-License-Identifier: GPL-3.0-or-later # -_TEMPLATES = { - 'fpga': """\ -""", - 'detect': """\ -""" -} - # open_project -file {$TEMPDIR/libero.prjx} # run_tool -name {CONFIGURE_CHAIN} -script {$TEMPDIR/flashpro.tcl} # run_tool -name {PROGRAMDEVICE} From fa66cfac6355b968e72c23e9c7601835afebbd0d Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sat, 29 Jun 2024 18:17:47 -0300 Subject: [PATCH 119/254] Re-added Quartus support (WIP) --- examples/ise/run.py | 1 + examples/quartus/quartus.py | 32 -------------- examples/quartus/run.py | 50 +++++++++++++++++++++ pyfpga/quartus.py | 71 +++++++++++++++++++++++++---- pyfpga/templates/quartus.jinja | 81 +++++++++------------------------- 5 files changed, 135 insertions(+), 100 deletions(-) delete mode 100644 examples/quartus/quartus.py create mode 100644 examples/quartus/run.py diff --git a/examples/ise/run.py b/examples/ise/run.py index 93a35b13..3bd89df9 100644 --- a/examples/ise/run.py +++ b/examples/ise/run.py @@ -4,6 +4,7 @@ from pyfpga.ise import Ise + parser = argparse.ArgumentParser() parser.add_argument( '--board', choices=['s6micro', 'nexys3'], default='s6micro' diff --git a/examples/quartus/quartus.py b/examples/quartus/quartus.py deleted file mode 100644 index 6516fcd0..00000000 --- a/examples/quartus/quartus.py +++ /dev/null @@ -1,32 +0,0 @@ -"""Quartus example project.""" - -import argparse -import logging - -from fpga.project import Project - -logging.basicConfig() - -parser = argparse.ArgumentParser() -parser.add_argument( - '--action', choices=['generate', 'transfer', 'all'], default='generate', -) -args = parser.parse_args() - -prj = Project('quartus') -prj.set_part('5CSEBA6U23I7') - -prj.set_outdir('../../build/quartus') - -prj.add_files('../../hdl/blinking.vhdl', library='examples') -prj.add_files('../../hdl/examples_pkg.vhdl', library='examples') -prj.add_files('../../hdl/top.vhdl') -prj.set_top('Top') -prj.add_files('de10nano.sdc') -prj.add_files('de10nano.tcl') - -if args.action in ['generate', 'all']: - prj.generate() - -if args.action in ['transfer', 'all']: - prj.transfer('fpga', 2) diff --git a/examples/quartus/run.py b/examples/quartus/run.py new file mode 100644 index 00000000..0453ac8e --- /dev/null +++ b/examples/quartus/run.py @@ -0,0 +1,50 @@ +"""Quartus examples.""" + +import argparse + +from pyfpga.quartus import Quartus + + +parser = argparse.ArgumentParser() +parser.add_argument( + '--board', choices=['de10nano'], default='de10nano' +) +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 = Quartus(odir='../build/quartus') + +if args.board == 'de10nano': + prj.set_part('5CSEBA6U23I7') + prj.add_param('FREQ', '125000000') + prj.add_cons('../sources/de10nano/clk.sdc', 'syn') + prj.add_cons('../sources/de10nano/clk.tcl', 'par') + prj.add_cons('../sources/de10nano/led.tcl', 'par') +prj.add_param('SECS', '1') + +if args.source == 'vhdl': + prj.add_vhdl('../sources/vhdl/*.vhdl', 'blink_lib') +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_vlog('../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/pyfpga/quartus.py b/pyfpga/quartus.py index 4ca2bf78..fda50647 100644 --- a/pyfpga/quartus.py +++ b/pyfpga/quartus.py @@ -8,7 +8,9 @@ Implements support for Quartus. """ -# import re +# pylint: disable=too-many-locals +# pylint: disable=too-many-branches +# pylint: disable=duplicate-code from pyfpga.project import Project @@ -16,18 +18,69 @@ class Quartus(Project): """Class to support Quartus projects.""" - def __init__(self, name='quartus', odir='results'): - super().__init__(name=name, odir=odir) - self.set_part('10cl120zf780i8g') - def _make_prepare(self, steps): - self.tool['make-app'] = 'quartus_sh' - self.tool['make-cmd'] = 'quartus_sh --script quartus.tcl' + context = { + 'PROJECT': self.name or 'quartus', + 'PART': self.data.get('part', '10cl120zf780i8g') + } + for step in steps: + context[step] = 1 + if 'includes' in self.data: + includes = [] + for include in self.data['includes']: + includes.append(str(include)) + context['INCLUDES'] = ' '.join(includes) + files = [] + if 'files' in self.data: + types = { + 'slog': 'SYSTEMVERILOG_FILE', + 'vhdl': 'VHDL_FILE', + 'vlog': 'VERILOG_FILE' + } + for file in self.data['files']: + hdl = self.data['files'][file]['hdl'] + lib = self.data['files'][file].get('lib', None) + typ = types[hdl] + line = f'set_global_assignment -name {typ} {file}' + if lib: + line += f' -library {lib}' + files.append(line) + if 'constraints' in self.data: + for file in self.data['constraints']: + if file.suffix == '.sdc': + line = f'set_global_assignment -name SDC_FILE {file}' + else: + line = f'source {file}' + files.append(line) + if files: + context['FILES'] = '\n'.join(files) + if 'top' in self.data: + context['TOP'] = self.data['top'] + if 'defines' in self.data: + defines = [] + for key, value in self.data['defines'].items(): + defines.append(f'{key} {value}') + context['DEFINES'] = ' '.join(defines) + if 'params' in self.data: + params = [] + for key, value in self.data['params'].items(): + params.append(f'{key} {value}') + context['PARAMS'] = ' '.join(params) + if 'hooks' in self.data: + for stage in self.data['hooks']: + context[stage.upper()] = '\n'.join(self.data['hooks'][stage]) + self._create_file('quartus', 'tcl', context) + return 'quartus_sh --script quartus.tcl' def _prog_prepare(self, bitstream, position): # binaries = ['sof', 'pof'] - self.tool['prog-app'] = 'quartus_pgm' - self.tool['prog-cmd'] = 'bash quartus-prog.sh' + _ = position # Not needed for Vivado + # if not bitstream: + # basename = self.name or 'quartus' + # bitstream = Path(self.odir).resolve() / f'{basename}.bit' + context = {'BITSTREAM': bitstream} + self._create_file('quartus-prog', 'tcl', context) + return 'bash quartus-prog.sh' # def transfer(self, devtype, position, part, width, capture): # super().transfer(devtype, position, part, width, capture) diff --git a/pyfpga/templates/quartus.jinja b/pyfpga/templates/quartus.jinja index cf660a8d..4b98174a 100644 --- a/pyfpga/templates/quartus.jinja +++ b/pyfpga/templates/quartus.jinja @@ -4,72 +4,35 @@ # SPDX-License-Identifier: GPL-3.0-or-later # -set PROJECT {{ PROJECT }} -set PART {{ PART }} -set FAMILY {{ FAMILY }} -set DEVICE {{ DEVICE }} -set PACKAGE {{ PACKAGE }} -set SPEED {{ SPEED }} -set TOP {{ TOP }} - -set PARAMS [list {{ PARAMS }}] - -proc fpga_file {FILE {LIBRARY "work"}} { - set message "adding the file '$FILE'" - if { $LIBRARY != "work" } { append message " (into the VHDL library '$LIBRARY')" } - regexp -nocase {\.(\w*)$} $FILE -> ext - if { $ext == "tcl" } { - source $FILE - return - } - if {$ext == "v"} { - set TYPE VERILOG_FILE - } elseif {$ext == "sv"} { - set TYPE SYSTEMVERILOG_FILE - } elseif {$ext == "vhdl" || $ext == "vhd"} { - set TYPE VHDL_FILE - } elseif {$ext == "sdc"} { - set TYPE SDC_FILE - } else { - set TYPE SOURCE_FILE - } - if { $LIBRARY != "work" } { - set_global_assignment -name $TYPE $FILE -library $LIBRARY - } else { - set_global_assignment -name $TYPE $FILE - } -} - -proc fpga_include {PATH} { - lappend INCLUDED $PATH - # Verilog Included Files are NOT added - foreach INCLUDE $INCLUDED { - set_global_assignment -name SEARCH_PATH $INCLUDE - } -} - -proc fpga_params {} { - if { [llength $PARAMS] == 0 } { return } - foreach PARAM $PARAMS { - eval "set_parameter -name $PARAM" - } -} - #--[ Project configuration ]--------------------------------------------------- {% if CFG %} package require ::quartus::project -project_new $PROJECT -overwrite - -set_global_assignment -name DEVICE $PART +project_new {{ PROJECT }} -overwrite +set_global_assignment -name DEVICE {{ PART }} {{ PRECFG }} -fpga_files +{{ FILES }} + +{% if TOP %} +set_global_assignment -name TOP_LEVEL_ENTITY {{ TOP }} +{% endif %} + +{% if DEFINES %} +set defines [dict create {{ DEFINES }}] +foreach {key value} $defines {set_global_assignment -name VERILOG_MACRO "$key=$value"} +{% endif %} -set_global_assignment -name TOP_LEVEL_ENTITY $TOP +{% if INCLUDES %} +set includes { {{ INCLUDES}} } +foreach value $includes {set_global_assignment -name SEARCH_PATH $value} +{% endif %} -fpga_params +{% if PARAMS %} +set params [dict create {{ PARAMS }}] +foreach {key value} $params {set_parameter -name $key $value} +{% endif %} {{ POSTCFG }} @@ -80,8 +43,8 @@ project_close {% if SYN or PAR or BIT %} package require ::quartus::flow -project_open -force $PROJECT.qpf -set_global_assignment -name NUM_PARALLEL_PROCESSORS ALL +project_open -force {{ PROJECT }}.qpf +# set_global_assignment -name NUM_PARALLEL_PROCESSORS ALL {% if SYN %} {{ PRESYN }} From 2b34189694b5a5107e3379b8d9198542008337c5 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sat, 29 Jun 2024 20:54:48 -0300 Subject: [PATCH 120/254] Fix duplicate logging issue when creating new instances --- pyfpga/project.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/pyfpga/project.py b/pyfpga/project.py index d33dbd93..b9192c03 100644 --- a/pyfpga/project.py +++ b/pyfpga/project.py @@ -36,14 +36,15 @@ def __init__(self, name=None, odir='results'): self.odir = odir # logging config self.logger = logging.getLogger(self.__class__.__name__) - self.logger.setLevel(logging.INFO) - handler = logging.StreamHandler() - formatter = logging.Formatter( - '%(asctime)s - %(levelname)s - %(message)s', - datefmt='%Y-%m-%d %H:%M:%S' - ) - handler.setFormatter(formatter) - self.logger.addHandler(handler) + if not self.logger.handlers: + self.logger.setLevel(logging.INFO) + handler = logging.StreamHandler() + formatter = logging.Formatter( + '%(asctime)s - %(levelname)s - %(message)s', + datefmt='%Y-%m-%d %H:%M:%S' + ) + handler.setFormatter(formatter) + self.logger.addHandler(handler) def set_part(self, name): """Set the FPGA part name. From c8841d5ddd933e15f98d9363806b7ad0c3893cbf Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sat, 29 Jun 2024 20:58:53 -0300 Subject: [PATCH 121/254] Add a test to verify support of the specified tool --- tests/projects/support.py | 126 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100644 tests/projects/support.py diff --git a/tests/projects/support.py b/tests/projects/support.py new file mode 100644 index 00000000..3b529f14 --- /dev/null +++ b/tests/projects/support.py @@ -0,0 +1,126 @@ +"""Vivado examples.""" + +import sys + +from pyfpga.ise import Ise +from pyfpga.libero import Libero +from pyfpga.openflow import Openflow +from pyfpga.quartus import Quartus +from pyfpga.vivado import Vivado + + +classes = { + 'ise': Ise, + 'libero': Libero, + 'openflow': Openflow, + 'quartus': Quartus, + 'vivado': Vivado +} + +tool = sys.argv[1] if len(sys.argv) > 1 else 'openflow' + +Class = classes.get(tool) + +if Class is None: + sys.exit('Unsupported tool') + +print(f'* Class Under Test: {Class.__name__}') + +try: + print('* Verilog Includes') + prj = Class() + prj.add_vlog('../../examples/sources/vlog/*.v') + prj.set_top('Top') + prj.add_define('DEFINE1', '1') + prj.add_define('DEFINE2', '1') + prj.add_param('FREQ', '1') + prj.add_param('SECS', '1') + prj.make() + sys.exit('* FAIL') +except SystemExit: + raise +except: + print('* PASS') + +try: + print('* Verilog Defines') + prj = Class() + prj.add_vlog('../../examples/sources/vlog/*.v') + prj.set_top('Top') + prj.add_include('../../examples/sources/vlog/include1') + prj.add_include('../../examples/sources/vlog/include2') + prj.add_param('FREQ', '1') + prj.add_param('SECS', '1') + prj.make() + sys.exit('* FAIL') +except SystemExit: + raise +except: + print('* PASS') + +try: + print('* Verilog Parameters') + prj = Class() + prj.add_vlog('../../examples/sources/vlog/*.v') + prj.set_top('Top') + prj.add_include('../../examples/sources/vlog/include1') + prj.add_include('../../examples/sources/vlog/include2') + prj.add_define('DEFINE1', '1') + prj.add_define('DEFINE2', '1') + prj.make() + sys.exit('* FAIL') +except SystemExit: + raise +except: + print('* PASS') + +print('* Verilog Support') +prj = Class() +prj.add_vlog('../../examples/sources/vlog/*.v') +prj.set_top('Top') +prj.add_include('../../examples/sources/vlog/include1') +prj.add_include('../../examples/sources/vlog/include2') +prj.add_define('DEFINE1', '1') +prj.add_define('DEFINE2', '1') +prj.add_param('FREQ', '1') +prj.add_param('SECS', '1') +prj.make() +print('* PASS') + +if tool not in ['ise', 'openflow']: + print('* System Verilog Support') + prj = Class() + prj.add_vlog('../../examples/sources/slog/*.sv') + prj.set_top('Top') + prj.add_include('../../examples/sources/slog/include1') + prj.add_include('../../examples/sources/slog/include2') + prj.add_define('DEFINE1', '1') + prj.add_define('DEFINE2', '1') + prj.add_param('FREQ', '1') + prj.add_param('SECS', '1') + prj.make() + print('* PASS') + +if tool not in ['openflow']: + try: + print('* VHDL Generics') + prj = Class() + prj.add_vhdl('../../examples/sources/vhdl/*.vhdl', 'blink_lib') + prj.set_top('Top') + prj.make() + sys.exit('* FAIL') + except SystemExit: + raise + except: + print('* PASS') + + print('* VHDL Support') + prj = Class() + prj.add_vhdl('../../examples/sources/vhdl/*.vhdl', 'blink_lib') + prj.set_top('Top') + prj.add_param('FREQ', '1') + prj.add_param('SECS', '1') + prj.make() + print('* PASS') + +print(f'* Class Under Test works as expected') From d8eebcfd2d8b2d64956ed64a86d1dcc4a54b4bde Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sat, 29 Jun 2024 22:33:41 -0300 Subject: [PATCH 122/254] Improve/simplify the generation of an intentional error --- examples/sources/slog/top.sv | 55 +++++++++++----------------------- examples/sources/vhdl/top.vhdl | 8 +++-- examples/sources/vlog/top.v | 55 +++++++++++----------------------- 3 files changed, 41 insertions(+), 77 deletions(-) diff --git a/examples/sources/slog/top.sv b/examples/sources/slog/top.sv index 4665011a..cfc700e3 100644 --- a/examples/sources/slog/top.sv +++ b/examples/sources/slog/top.sv @@ -9,47 +9,28 @@ module Top #( output led_o ); - initial begin - if (!FREQ) begin - $stop("FREQ not set"); - $error("FREQ not set"); - $fatal("FREQ not set"); - end - if (!SECS) begin - $stop("SECS not set"); - $error("SECS not set"); - $fatal("SECS not set"); - end - `ifndef INCLUDE1 - $stop("INCLUDE1 not defined"); - $error("INCLUDE1 not defined"); - $fatal("INCLUDE1 not defined"); - `endif - `ifndef INCLUDE2 - $stop("INCLUDE2 not defined"); - $error("INCLUDE2 not defined"); - $fatal("INCLUDE2 not defined"); - `endif - `ifndef DEFINE1 - $stop("DEFINE1 not defined"); - $error("DEFINE1 not defined"); - $fatal("DEFINE1 not defined"); - `endif - `ifndef DEFINE2 - $stop("DEFINE2 not defined"); - $error("DEFINE2 not defined"); - $fatal("DEFINE2 not defined"); - `endif - end - -`ifdef INCLUDE1 -`ifdef INCLUDE2 -`ifdef DEFINE1 -`ifdef DEFINE2 Blink #(.FREQ (FREQ), .SECS (SECS)) dut (.clk_i (clk_i), .led_o (led_o)); + +`ifndef INCLUDE1 + Top Top (.clk_i (clk_i), .led_o (led_o)); `endif + +`ifndef INCLUDE2 + Top Top (.clk_i (clk_i), .led_o (led_o)); `endif + +`ifndef DEFINE1 + Top Top (.clk_i (clk_i), .led_o (led_o)); `endif + +`ifndef DEFINE2 + Top Top (.clk_i (clk_i), .led_o (led_o)); `endif + generate + if (!FREQ || !SECS) begin: gen_error + Top Top (.clk_i (clk_i), .led_o (led_o)); + end + endgenerate + endmodule diff --git a/examples/sources/vhdl/top.vhdl b/examples/sources/vhdl/top.vhdl index 24977d6c..c6537185 100644 --- a/examples/sources/vhdl/top.vhdl +++ b/examples/sources/vhdl/top.vhdl @@ -17,11 +17,13 @@ end entity Top; architecture ARCH of Top is begin - assert FREQ > 0 report "FREQ not set" severity failure; - assert SECS > 0 report "SECS not set" severity failure; - blink_i: Blink generic map (FREQ => FREQ, SECS => SECS) port map (clk_i => clk_i, led_o => led_o); + gen_error : if FREQ=0 or SECS=0 generate + top_i: entity work.Top + port map (clk_i => clk_i, led_o => led_o); + end generate gen_error; + end architecture ARCH; diff --git a/examples/sources/vlog/top.v b/examples/sources/vlog/top.v index fb6dff0b..f47433ba 100644 --- a/examples/sources/vlog/top.v +++ b/examples/sources/vlog/top.v @@ -9,47 +9,28 @@ module Top #( output led_o ); - initial begin - if (!FREQ) begin - $stop("FREQ not set"); - $error("FREQ not set"); - $fatal("FREQ not set"); - end - if (!SECS) begin - $stop("SECS not set"); - $error("SECS not set"); - $fatal("SECS not set"); - end - `ifndef INCLUDE1 - $stop("INCLUDE1 not defined"); - $error("INCLUDE1 not defined"); - $fatal("INCLUDE1 not defined"); - `endif - `ifndef INCLUDE2 - $stop("INCLUDE2 not defined"); - $error("INCLUDE2 not defined"); - $fatal("INCLUDE2 not defined"); - `endif - `ifndef DEFINE1 - $stop("DEFINE1 not defined"); - $error("DEFINE1 not defined"); - $fatal("DEFINE1 not defined"); - `endif - `ifndef DEFINE2 - $stop("DEFINE2 not defined"); - $error("DEFINE2 not defined"); - $fatal("DEFINE2 not defined"); - `endif - end - -`ifdef INCLUDE1 -`ifdef INCLUDE2 -`ifdef DEFINE1 -`ifdef DEFINE2 Blink #(.FREQ (FREQ), .SECS (SECS)) dut (.clk_i (clk_i), .led_o (led_o)); + +`ifndef INCLUDE1 + Top Top (.clk_i (clk_i), .led_o (led_o)); `endif + +`ifndef INCLUDE2 + Top Top (.clk_i (clk_i), .led_o (led_o)); `endif + +`ifndef DEFINE1 + Top Top (.clk_i (clk_i), .led_o (led_o)); `endif + +`ifndef DEFINE2 + Top Top (.clk_i (clk_i), .led_o (led_o)); `endif + generate + if (!FREQ || !SECS) begin: gen_error + Top Top (.clk_i (clk_i), .led_o (led_o)); + end + endgenerate + endmodule From 032a8e171a8d7cbf816601230b4d652e3ad86163 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sat, 29 Jun 2024 22:34:34 -0300 Subject: [PATCH 123/254] Fix VHDL libraries support for ISE --- pyfpga/ise.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyfpga/ise.py b/pyfpga/ise.py index 370c80ce..6109742d 100644 --- a/pyfpga/ise.py +++ b/pyfpga/ise.py @@ -40,7 +40,7 @@ def _make_prepare(self, steps): files = [] if 'files' in self.data: for file in self.data['files']: - if 'lib' in self.data['files']: + if 'lib' in self.data['files'][file]: lib = self.data['files'][file]['lib'] files.append(f'lib_vhdl new {lib}') files.append(f'xfile add {file} -lib_vhdl {lib}') From c072eafad1caa60039a86d897888cc5b8664111e Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sat, 29 Jun 2024 22:36:35 -0300 Subject: [PATCH 124/254] Improve/simplify the test to verify support --- tests/projects/support.py | 49 ++++++++++++++++++--------------------- 1 file changed, 23 insertions(+), 26 deletions(-) diff --git a/tests/projects/support.py b/tests/projects/support.py index 3b529f14..99ec3cb7 100644 --- a/tests/projects/support.py +++ b/tests/projects/support.py @@ -24,10 +24,10 @@ if Class is None: sys.exit('Unsupported tool') -print(f'* Class Under Test: {Class.__name__}') +print(f'INFO: the Class Under Test is {Class.__name__}') try: - print('* Verilog Includes') + print('INFO: checking Verilog Includes') prj = Class() prj.add_vlog('../../examples/sources/vlog/*.v') prj.set_top('Top') @@ -35,15 +35,15 @@ prj.add_define('DEFINE2', '1') prj.add_param('FREQ', '1') prj.add_param('SECS', '1') - prj.make() - sys.exit('* FAIL') + prj.make(last='syn') + sys.exit('ERROR: something does not work as expected') except SystemExit: raise except: - print('* PASS') + pass try: - print('* Verilog Defines') + print('INFO: checking Verilog Defines') prj = Class() prj.add_vlog('../../examples/sources/vlog/*.v') prj.set_top('Top') @@ -51,15 +51,15 @@ prj.add_include('../../examples/sources/vlog/include2') prj.add_param('FREQ', '1') prj.add_param('SECS', '1') - prj.make() - sys.exit('* FAIL') + prj.make(last='syn') + sys.exit('ERROR: something does not work as expected') except SystemExit: raise except: - print('* PASS') + pass try: - print('* Verilog Parameters') + print('INFO: checking Verilog Parameters') prj = Class() prj.add_vlog('../../examples/sources/vlog/*.v') prj.set_top('Top') @@ -67,14 +67,14 @@ prj.add_include('../../examples/sources/vlog/include2') prj.add_define('DEFINE1', '1') prj.add_define('DEFINE2', '1') - prj.make() - sys.exit('* FAIL') + prj.make(last='syn') + sys.exit('ERROR: something does not work as expected') except SystemExit: raise except: - print('* PASS') + pass -print('* Verilog Support') +print('INFO: checking Verilog Support') prj = Class() prj.add_vlog('../../examples/sources/vlog/*.v') prj.set_top('Top') @@ -84,11 +84,10 @@ prj.add_define('DEFINE2', '1') prj.add_param('FREQ', '1') prj.add_param('SECS', '1') -prj.make() -print('* PASS') +prj.make(last='syn') if tool not in ['ise', 'openflow']: - print('* System Verilog Support') + print('INFO: checking System Verilog Support') prj = Class() prj.add_vlog('../../examples/sources/slog/*.sv') prj.set_top('Top') @@ -98,21 +97,20 @@ prj.add_define('DEFINE2', '1') prj.add_param('FREQ', '1') prj.add_param('SECS', '1') - prj.make() - print('* PASS') + prj.make(last='syn') if tool not in ['openflow']: try: - print('* VHDL Generics') + print('INFO: checking VHDL Generics') prj = Class() prj.add_vhdl('../../examples/sources/vhdl/*.vhdl', 'blink_lib') prj.set_top('Top') - prj.make() - sys.exit('* FAIL') + prj.make(last='syn') + sys.exit('ERROR: something does not work as expected') except SystemExit: raise except: - print('* PASS') + pass print('* VHDL Support') prj = Class() @@ -120,7 +118,6 @@ prj.set_top('Top') prj.add_param('FREQ', '1') prj.add_param('SECS', '1') - prj.make() - print('* PASS') + prj.make(last='syn') -print(f'* Class Under Test works as expected') +print(f'INFO: Class Under Test works as expected') From f403d88826cf25fd131dfe997d1857e8e34af638 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sat, 29 Jun 2024 22:37:11 -0300 Subject: [PATCH 125/254] Add a script to run the ISE examples --- examples/ise/run.sh | 13 +++++++++++++ examples/openflow/run.sh | 7 ++----- examples/vivado/run.sh | 7 ++----- 3 files changed, 17 insertions(+), 10 deletions(-) create mode 100644 examples/ise/run.sh diff --git a/examples/ise/run.sh b/examples/ise/run.sh new file mode 100644 index 00000000..c13ee1c0 --- /dev/null +++ b/examples/ise/run.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +set -e + +BOARDS=("s6micro" "nexys3") +SOURCES=("vlog" "vhdl") + +for BOARD in "${BOARDS[@]}"; do + for SOURCE in "${SOURCES[@]}"; do + echo "> $BOARD - $SOURCE" + python3 run.py --board $BOARD --source $SOURCE + done +done diff --git a/examples/openflow/run.sh b/examples/openflow/run.sh index 4e213f40..986538d5 100644 --- a/examples/openflow/run.sh +++ b/examples/openflow/run.sh @@ -4,13 +4,10 @@ set -e BOARDS=("icestick" "edu-ciaa" "orangecrab" "ecp5evn") SOURCES=("vlog" "vhdl" "slog") -ACTIONS=("make" "prog" "all") for BOARD in "${BOARDS[@]}"; do for SOURCE in "${SOURCES[@]}"; do - for ACTION in "${ACTIONS[@]}"; do - echo "> $BOARD - $SOURCE - $ACTION" - python3 run.py --board $BOARD --source $SOURCE --action $ACTION - done + echo "> $BOARD - $SOURCE" + python3 run.py --board $BOARD --source $SOURCE done done diff --git a/examples/vivado/run.sh b/examples/vivado/run.sh index bd894b91..3b6a065a 100644 --- a/examples/vivado/run.sh +++ b/examples/vivado/run.sh @@ -4,13 +4,10 @@ set -e BOARDS=("zybo" "arty") SOURCES=("vlog" "vhdl" "slog") -ACTIONS=("make" "prog" "all") for BOARD in "${BOARDS[@]}"; do for SOURCE in "${SOURCES[@]}"; do - for ACTION in "${ACTIONS[@]}"; do - echo "> $BOARD - $SOURCE - $ACTION" - python3 run.py --board $BOARD --source $SOURCE --action $ACTION - done + echo "> $BOARD - $SOURCE" + python3 run.py --board $BOARD --source $SOURCE done done From f027f228968188289861fce8945ba2b2685efb2d Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sat, 29 Jun 2024 22:39:53 -0300 Subject: [PATCH 126/254] Fix pylint complaint --- tests/projects/support.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/projects/support.py b/tests/projects/support.py index 99ec3cb7..358cb8ae 100644 --- a/tests/projects/support.py +++ b/tests/projects/support.py @@ -39,7 +39,7 @@ sys.exit('ERROR: something does not work as expected') except SystemExit: raise -except: +except Exception: pass try: @@ -55,7 +55,7 @@ sys.exit('ERROR: something does not work as expected') except SystemExit: raise -except: +except Exception: pass try: @@ -71,7 +71,7 @@ sys.exit('ERROR: something does not work as expected') except SystemExit: raise -except: +except Exception: pass print('INFO: checking Verilog Support') @@ -109,7 +109,7 @@ sys.exit('ERROR: something does not work as expected') except SystemExit: raise - except: + except Exception: pass print('* VHDL Support') From daf7f7ca82ee48d01179dd44f136db7a3beea21a Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sun, 30 Jun 2024 14:38:48 -0300 Subject: [PATCH 127/254] Add checking of success to force an error if not --- pyfpga/templates/vivado.jinja | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pyfpga/templates/vivado.jinja b/pyfpga/templates/vivado.jinja index 2c593f69..8f0cbb8e 100644 --- a/pyfpga/templates/vivado.jinja +++ b/pyfpga/templates/vivado.jinja @@ -50,6 +50,8 @@ open_project {{ PROJECT }} reset_run synth_1 launch_runs synth_1 wait_on_run synth_1 +#report_property [get_runs synth_1] +if { [get_property STATUS [get_runs synth_1]] ne "synth_design Complete!" } { exit 1 } {{ POSTSYN }} {% endif %} @@ -60,6 +62,8 @@ wait_on_run synth_1 reset_run impl_1 launch_runs impl_1 wait_on_run impl_1 +#report_property [get_runs impl_1] +if { [get_property STATUS [get_runs impl_1]] ne "route_design Complete!" } { exit 1 } {{ POSTPAR }} {% endif %} From 23a89d720f6b0b91cb42a9de60635493f8262b76 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sun, 30 Jun 2024 15:10:05 -0300 Subject: [PATCH 128/254] Modified default part --- pyfpga/quartus.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyfpga/quartus.py b/pyfpga/quartus.py index fda50647..fec1e25b 100644 --- a/pyfpga/quartus.py +++ b/pyfpga/quartus.py @@ -21,7 +21,7 @@ class Quartus(Project): def _make_prepare(self, steps): context = { 'PROJECT': self.name or 'quartus', - 'PART': self.data.get('part', '10cl120zf780i8g') + 'PART': self.data.get('part', '10M50SCE144I7G') } for step in steps: context[step] = 1 From b26ac58a3155c39888c360a16451ff4b83df6e47 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sun, 30 Jun 2024 15:10:22 -0300 Subject: [PATCH 129/254] Fix SystemVerilog test --- tests/projects/support.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/projects/support.py b/tests/projects/support.py index 358cb8ae..4ce15596 100644 --- a/tests/projects/support.py +++ b/tests/projects/support.py @@ -89,7 +89,7 @@ if tool not in ['ise', 'openflow']: print('INFO: checking System Verilog Support') prj = Class() - prj.add_vlog('../../examples/sources/slog/*.sv') + prj.add_slog('../../examples/sources/slog/*.sv') prj.set_top('Top') prj.add_include('../../examples/sources/slog/include1') prj.add_include('../../examples/sources/slog/include2') From 063c5bb9e07dfc3c69f33897ab31fd2658c9e8d2 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sun, 30 Jun 2024 15:25:39 -0300 Subject: [PATCH 130/254] Fix Vivado mock-up after last changes on the Vivado template --- tests/mocks/vivado | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/mocks/vivado b/tests/mocks/vivado index f6a47eff..fb59d819 100755 --- a/tests/mocks/vivado +++ b/tests/mocks/vivado @@ -42,6 +42,21 @@ tool = parser.prog tcl = f''' proc unknown args {{ }} +proc get_runs {{run_name}} {{ + return $run_name +}} + +proc get_property {{property run}} {{ + if {{ $property eq "STATUS" }} {{ + if {{ $run eq "synth_1" }} {{ + return "synth_design Complete!" + }} elseif {{ $run eq "impl_1" }} {{ + return "route_design Complete!" + }} + }} + return "" +}} + source {args.source} ''' From 73417f832a950ff1bac77409af97099e8a524dd5 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sun, 30 Jun 2024 20:03:36 -0300 Subject: [PATCH 131/254] Clean-up --- pyfpga/ise.py | 17 +-------- pyfpga/libero.py | 57 ++++++++++++++++++++++++++--- pyfpga/openflow.py | 43 +++++++--------------- pyfpga/project.py | 2 - pyfpga/quartus.py | 27 +++----------- pyfpga/templates/quartus-prog.jinja | 8 +++- pyfpga/vivado.py | 5 +-- 7 files changed, 81 insertions(+), 78 deletions(-) diff --git a/pyfpga/ise.py b/pyfpga/ise.py index 6109742d..a964af7c 100644 --- a/pyfpga/ise.py +++ b/pyfpga/ise.py @@ -14,7 +14,6 @@ import re -from pathlib import Path from pyfpga.project import Project @@ -77,7 +76,7 @@ def _make_prepare(self, steps): def _prog_prepare(self, bitstream, position): if not bitstream: basename = self.name or 'ise' - bitstream = Path(self.odir).resolve() / f'{basename}.bit' + bitstream = f'{basename}.bit' context = {'BITSTREAM': bitstream, 'POSITION': position} self._create_file('vivado-prog', 'tcl', context) return 'impact -batch impact-prog' @@ -86,20 +85,6 @@ def add_slog(self, pathname): """Add System Verilog file/s.""" raise NotImplementedError('ISE does not support SystemVerilog') -# _DEVTYPES = ['fpga', 'spi', 'bpi', 'detect', 'unlock'] - -# def transfer(self, devtype, position, part, width, capture): -# super().transfer(devtype, position, part, width, capture) -# temp = _TEMPLATES[devtype] -# if devtype not in ['detect', 'unlock']: -# temp = temp.replace('#BITSTREAM#', self.bitstream) -# temp = temp.replace('#POSITION#', str(position)) -# temp = temp.replace('#NAME#', part) -# temp = temp.replace('#WIDTH#', str(width)) -# with open('ise-prog.impact', 'w', encoding='utf-8') as file: -# file.write(temp) -# return run(self._TRF_COMMAND, capture) - def get_info(part): """Get info about the FPGA part. diff --git a/pyfpga/libero.py b/pyfpga/libero.py index 5cf8efe4..1d0dc983 100644 --- a/pyfpga/libero.py +++ b/pyfpga/libero.py @@ -20,13 +20,58 @@ class Libero(Project): """Class to support Libero.""" - def __init__(self, name='libero', odir='results'): - super().__init__(name=name, odir=odir) - self.set_part('mpf100t-1-fcg484') - def _make_prepare(self, steps): - self.tool['make-app'] = 'libero' - self.tool['make-cmd'] = 'libero SCRIPT:libero.tcl' + info = get_info(self.data.get('part', 'mpf100t-1-fcg484')) + context = { + 'PROJECT': self.name or 'ise', + 'FAMILY': info['family'], + 'DEVICE': info['device'], + 'SPEED': info['speed'], + 'PACKAGE': info['package'] + } + for step in steps: + context[step] = 1 + if 'includes' in self.data: + includes = [] + for include in self.data['includes']: + includes.append(str(include)) + context['INCLUDES'] = '|'.join(includes) + files = [] + if 'files' in self.data: + for file in self.data['files']: + if 'lib' in self.data['files'][file]: + lib = self.data['files'][file]['lib'] + files.append(f'lib_vhdl new {lib}') + files.append(f'xfile add {file} -lib_vhdl {lib}') + else: + files.append(f'xfile add {file}') + if 'constraints' in self.data: + constraints = [] + for file in self.data['constraints']: + files.append(f'xfile add {file}') + if file.suffix == '.xcf': + constraints.append(str(file)) + if constraints: + context['CONSTRAINTS'] = " ".join(constraints) + if files: + context['FILES'] = '\n'.join(files) + if 'top' in self.data: + context['TOP'] = self.data['top'] + if 'defines' in self.data: + defines = [] + for key, value in self.data['defines'].items(): + defines.append(f'{key}={value}') + context['DEFINES'] = ' | '.join(defines) + if 'params' in self.data: + params = [] + for key, value in self.data['params'].items(): + params.append(f'{key}={value}') + context['PARAMS'] = ' '.join(params) + if 'hooks' in self.data: + for stage in self.data['hooks']: + context[stage.upper()] = '\n'.join(self.data['hooks'][stage]) + self._create_file('ise', 'tcl', context) + return 'libero SCRIPT:libero.tcl' def _prog_prepare(self, bitstream, position): raise NotImplementedError('Libero programming not supported') diff --git a/pyfpga/openflow.py b/pyfpga/openflow.py index 8b40b259..51dcd84a 100644 --- a/pyfpga/openflow.py +++ b/pyfpga/openflow.py @@ -12,7 +12,6 @@ # pylint: disable=too-many-branches # pylint: disable=duplicate-code -from pathlib import Path from pyfpga.project import Project @@ -40,12 +39,18 @@ def _make_prepare(self, steps): files.append(f'read_verilog -defer {file}') if files: context['VLOGS'] = '\n'.join(files) -# for file in self.data['files']: -# if 'lib' in self.data['files'][file]: -# lib = self.data['files'][file]['lib'] -# files.append( -# f'set_property library {lib} [get_files {file}]' -# ) +# for file in self.files['vhdl']: +# lib = '' +# if file[1] is not None: +# lib = f'--work={file[1]}' +# vhdls.append(f'{self.tools["ghdl"]} -a $FLAGS {lib} {file[0]}') +# for file in self.files['verilog']: +# if file[0].endswith('.sv'): +# verilogs.append(f'read_verilog -sv -defer {file[0]}') +# else: +# verilogs.append(f'read_verilog -defer {file[0]}') +# if len(vhdls) > 0: +# verilogs = [f'ghdl $FLAGS {self.top}'] if 'constraints' in self.data: constraints = [] for constraint in self.data['constraints']: @@ -70,34 +75,14 @@ def _make_prepare(self, steps): return 'bash openflow.sh' def _prog_prepare(self, bitstream, position): - _ = position + _ = position # Not needed if not bitstream: basename = self.name or 'openflow' - bitstream = Path(self.odir).resolve() / f'{basename}.bit' + bitstream = f'{basename}.bit' context = {'BITSTREAM': bitstream} self._create_file('openflow-prog', 'sh', context) return 'bash openflow-prog.sh' -# def _create_gen_script(self, tasks): -# # Files -# constraints = [] -# verilogs = [] -# vhdls = [] -# for file in self.files['vhdl']: -# lib = '' -# if file[1] is not None: -# lib = f'--work={file[1]}' -# vhdls.append(f'{self.tools["ghdl"]} -a $FLAGS {lib} {file[0]}') -# for file in self.files['verilog']: -# if file[0].endswith('.sv'): -# verilogs.append(f'read_verilog -sv -defer {file[0]}') -# else: -# verilogs.append(f'read_verilog -defer {file[0]}') -# for file in self.files['constraint']: -# constraints.append(file[0]) -# if len(vhdls) > 0: -# verilogs = [f'ghdl $FLAGS {self.top}'] - def get_info(part): """Get info about the FPGA part. diff --git a/pyfpga/project.py b/pyfpga/project.py index b9192c03..b77b8159 100644 --- a/pyfpga/project.py +++ b/pyfpga/project.py @@ -27,8 +27,6 @@ class Project: :type odir: str, optional """ - tool = {} - def __init__(self, name=None, odir='results'): """Class constructor.""" self.data = {} diff --git a/pyfpga/quartus.py b/pyfpga/quartus.py index fec1e25b..417a497a 100644 --- a/pyfpga/quartus.py +++ b/pyfpga/quartus.py @@ -73,26 +73,11 @@ def _make_prepare(self, steps): return 'quartus_sh --script quartus.tcl' def _prog_prepare(self, bitstream, position): - # binaries = ['sof', 'pof'] - _ = position # Not needed for Vivado - # if not bitstream: - # basename = self.name or 'quartus' - # bitstream = Path(self.odir).resolve() / f'{basename}.bit' - context = {'BITSTREAM': bitstream} + # sof: SRAM Object File + # pof: Programming Object File + if not bitstream: + basename = self.name or 'quartus' + bitstream = f'{basename}.sof' + context = {'BITSTREAM': bitstream, 'POSITION': position} self._create_file('quartus-prog', 'tcl', context) return 'bash quartus-prog.sh' - -# def transfer(self, devtype, position, part, width, capture): -# super().transfer(devtype, position, part, width, capture) -# result = subprocess.run( -# 'jtagconfig', shell=True, check=True, -# stdout=subprocess.PIPE, universal_newlines=True -# ) -# result = result.stdout -# if devtype == 'detect': -# print(result) -# else: -# cable = re.match(r"1\) (.*) \[", result).groups()[0] -# cmd = self._TRF_COMMAND % (cable, self.bitstream, position) -# result = run(cmd, capture) -# return result diff --git a/pyfpga/templates/quartus-prog.jinja b/pyfpga/templates/quartus-prog.jinja index d754fee5..62839a6b 100644 --- a/pyfpga/templates/quartus-prog.jinja +++ b/pyfpga/templates/quartus-prog.jinja @@ -4,4 +4,10 @@ # SPDX-License-Identifier: GPL-3.0-or-later # -quartus_pgm -c {{ CABLE }} --mode jtag -o "p;{{ BITSTREAM }}@{{ POSITION }}" +RESULT=$(jtagconfig) +echo "$RESULT" + +# cable = re.match(r"1\) (.*) \[", result).groups()[0] +CABLE=$(echo "$RESULT" | awk -F '1\\) | \\[' '/1\)/ {print $2}') + +quartus_pgm -c $CABLE --mode jtag -o "p;{{ BITSTREAM }}@{{ POSITION }}" diff --git a/pyfpga/vivado.py b/pyfpga/vivado.py index c0a625e6..1858ed82 100644 --- a/pyfpga/vivado.py +++ b/pyfpga/vivado.py @@ -11,7 +11,6 @@ # pylint: disable=too-many-locals # pylint: disable=too-many-branches -from pathlib import Path from pyfpga.project import Project @@ -74,10 +73,10 @@ def _make_prepare(self, steps): return 'vivado -mode batch -notrace -quiet -source vivado.tcl' def _prog_prepare(self, bitstream, position): - _ = position # Not needed for Vivado + _ = position # Not needed if not bitstream: basename = self.name or 'vivado' - bitstream = Path(self.odir).resolve() / f'{basename}.bit' + bitstream = f'{basename}.bit' context = {'BITSTREAM': bitstream} self._create_file('vivado-prog', 'tcl', context) return 'vivado -mode batch -notrace -quiet -source vivado-prog.tcl' From 030b335ddb24e759b420b67ce1a00884a36bd825 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sun, 30 Jun 2024 23:14:40 -0300 Subject: [PATCH 132/254] Re-added support for Libero (WIP) --- examples/libero/libero.py | 32 -------- examples/libero/run.py | 45 +++++++++++ pyfpga/libero.py | 22 ++--- pyfpga/templates/libero.jinja | 148 ++++++++++++++-------------------- 4 files changed, 116 insertions(+), 131 deletions(-) delete mode 100644 examples/libero/libero.py create mode 100644 examples/libero/run.py diff --git a/examples/libero/libero.py b/examples/libero/libero.py deleted file mode 100644 index 32ef38a5..00000000 --- a/examples/libero/libero.py +++ /dev/null @@ -1,32 +0,0 @@ -"""Libero example project.""" - -import argparse -import logging - -from fpga.project import Project - -logging.basicConfig() - -parser = argparse.ArgumentParser() -parser.add_argument( - '--action', choices=['generate', 'transfer', 'all'], default='generate', -) -args = parser.parse_args() - -prj = Project('libero') -prj.set_part('m2s010-1-tq144') - -prj.set_outdir('../../build/libero') - -prj.add_files('../../hdl/blinking.vhdl', library='examples') -prj.add_files('../../hdl/examples_pkg.vhdl', library='examples') -prj.add_files('../../hdl/top.vhdl') -prj.set_top('Top') -prj.add_files('mkr.pdc') -prj.add_files('mkr.sdc') - -if args.action in ['generate', 'all']: - prj.generate() - -if args.action in ['transfer', 'all']: - print('ERROR:transfer:Not yet implemented') diff --git a/examples/libero/run.py b/examples/libero/run.py new file mode 100644 index 00000000..87e9dc06 --- /dev/null +++ b/examples/libero/run.py @@ -0,0 +1,45 @@ +"""Libero examples.""" + +import argparse + +from pyfpga.libero import Libero + +parser = argparse.ArgumentParser() +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 = Libero(odir='../build/libero') + +prj.set_part('m2s010-1-tq144') +prj.add_param('FREQ', '125000000') +prj.add_cons('../sources/maker-board/clk.sdc', 'syn') +prj.add_cons('../sources/maker-board/clk.pdc', 'par') +prj.add_cons('../sources/maker-board/led.pdc', 'par') +prj.add_param('SECS', '1') + +if args.source == 'vhdl': + prj.add_vhdl('../sources/vhdl/*.vhdl', 'blink_lib') +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_vlog('../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/pyfpga/libero.py b/pyfpga/libero.py index 1d0dc983..29fc471a 100644 --- a/pyfpga/libero.py +++ b/pyfpga/libero.py @@ -23,7 +23,7 @@ class Libero(Project): def _make_prepare(self, steps): info = get_info(self.data.get('part', 'mpf100t-1-fcg484')) context = { - 'PROJECT': self.name or 'ise', + 'PROJECT': self.name or 'libero', 'FAMILY': info['family'], 'DEVICE': info['device'], 'SPEED': info['speed'], @@ -35,22 +35,24 @@ def _make_prepare(self, steps): includes = [] for include in self.data['includes']: includes.append(str(include)) - context['INCLUDES'] = '|'.join(includes) + context['INCLUDES'] = ';'.join(includes) files = [] if 'files' in self.data: for file in self.data['files']: if 'lib' in self.data['files'][file]: lib = self.data['files'][file]['lib'] - files.append(f'lib_vhdl new {lib}') - files.append(f'xfile add {file} -lib_vhdl {lib}') + files.append( + f'create_links -library {lib} -hdl_source {file}' + ) else: - files.append(f'xfile add {file}') + files.append(f'create_links -hdl_source {file}') if 'constraints' in self.data: constraints = [] for file in self.data['constraints']: - files.append(f'xfile add {file}') - if file.suffix == '.xcf': - constraints.append(str(file)) + if file.suffix == '.sdc': + constraints.append(f'create_links -sdc {file}') + else: + constraints.append(f'create_links -io_pdc {file}') if constraints: context['CONSTRAINTS'] = " ".join(constraints) if files: @@ -61,7 +63,7 @@ def _make_prepare(self, steps): defines = [] for key, value in self.data['defines'].items(): defines.append(f'{key}={value}') - context['DEFINES'] = ' | '.join(defines) + context['DEFINES'] = ' '.join(defines) if 'params' in self.data: params = [] for key, value in self.data['params'].items(): @@ -70,7 +72,7 @@ def _make_prepare(self, steps): if 'hooks' in self.data: for stage in self.data['hooks']: context[stage.upper()] = '\n'.join(self.data['hooks'][stage]) - self._create_file('ise', 'tcl', context) + self._create_file('libero', 'tcl', context) return 'libero SCRIPT:libero.tcl' def _prog_prepare(self, bitstream, position): diff --git a/pyfpga/templates/libero.jinja b/pyfpga/templates/libero.jinja index 1b230c2c..cc8af1e9 100644 --- a/pyfpga/templates/libero.jinja +++ b/pyfpga/templates/libero.jinja @@ -4,106 +4,76 @@ # SPDX-License-Identifier: GPL-3.0-or-later # -set PROJECT {{ PROJECT }} -set PART {{ PART }} -set FAMILY {{ FAMILY }} -set DEVICE {{ DEVICE }} -set PACKAGE {{ PACKAGE }} -set SPEED {{ SPEED }} -set TOP {{ TOP }} - -set PARAMS [list {{ PARAMS }}] - -proc fpga_file {FILE {LIBRARY "work"}} { - set message "adding the file '$FILE'" - if { $LIBRARY != "work" } { append message " (into the VHDL library '$LIBRARY')" } - regexp -nocase {\.(\w*)$} $FILE -> ext - if { $ext == "tcl" } { - source $FILE - return - } - global LIBERO_PLACE_CONSTRAINTS - global LIBERO_OTHER_CONSTRAINTS - if {$ext == "pdc"} { - create_links -io_pdc $FILE - append LIBERO_PLACE_CONSTRAINTS "-file $FILE " - } elseif {$ext == "sdc"} { - create_links -sdc $FILE - append LIBERO_PLACE_CONSTRAINTS "-file $FILE " - append LIBERO_OTHER_CONSTRAINTS "-file $FILE " - } else { - create_links -library $LIBRARY -hdl_source $FILE - build_design_hierarchy - } -} - -proc fpga_include {PATH} { - lappend INCLUDED $PATH - # Verilog Included Files are ALSO added - # They must be specified after set_root (see fpga_top) - foreach FILE [glob -nocomplain $PATH/*.vh] { - create_links -hdl_source $FILE - } - build_design_hierarchy -} - -proc fpga_params {} { - if { [llength $PARAMS] == 0 } { return } - # They must be specified after set_root (see fpga_top) -} +#proc fpga_include {PATH} { +# lappend INCLUDED $PATH +# # Verilog Included Files are ALSO added +# # They must be specified after set_root (see fpga_top) +# foreach FILE [glob -nocomplain $PATH/*.vh] { +# create_links -hdl_source $FILE +# } +# build_design_hierarchy +#} #--[ Project configuration ]--------------------------------------------------- {% if CFG %} -if { [ file exists $PROJECT ] } { file delete -force -- $PROJECT } -new_project -name $PROJECT -location $PROJECT -hdl {VHDL} -family {SmartFusion2} +if { [ file exists {{ PROJECT }} ] } { file delete -force -- {{ PROJECT }} } +new_project -name {{ PROJECT }} -location . -hdl {VHDL} -family {SmartFusion2} -set_device -family $FAMILY -die $DEVICE -package $PACKAGE -speed $SPEED +set_device -family {{ FAMILY }} -die {{ DEVICE }} -package {{ PACKAGE }} -speed {{ SPEED }} {{ PRECFG }} -fpga_files - -set_root $TOP -# Verilog Included files -set cmd "configure_tool -name {SYNTHESIZE} -params {SYNPLIFY_OPTIONS:" -if { [info exists INCLUDED] && [llength $INCLUDED] > 0 } { - # See /poc/include/libero.tcl for details - set PATHS "../../" - append PATHS [join $INCLUDED ";../../"] - append cmd "set_option -include_path \"$PATHS\"" - append cmd "\n" -} -foreach PARAM $PARAMS { - set assign [join $PARAM] - append cmd "set_option -hdl_param -set \"$assign\"" - append cmd "\n" +{{ FILES }} +build_design_hierarchy + +{% if TOP %} +set_root {{ TOP }} +{% endif %} + +{% if INCLUDES or DEFINES or PARAMS %} +configure_tool -name {SYNTHESIZE} -params {SYNPLIFY_OPTIONS: +{% if INCLUDES %} set_option -include_path "{{ INCLUDES }}"{% endif %} +{% if DEFINES %} set_option -hdl_define "{{ DEFINES }}"{% endif %} +{% if PARAMS %} set_option -hdl_param "{{ PARAMS }}"{% endif %} } -append cmd "}" -eval $cmd +{% endif %} + # Constraints # PDC is only used for PLACEROUTE. # SDC is used by ALL (SYNTHESIZE, PLACEROUTE and VERIFYTIMING). -global LIBERO_PLACE_CONSTRAINTS -global LIBERO_OTHER_CONSTRAINTS -if { [info exists LIBERO_OTHER_CONSTRAINTS] } { - set cmd "organize_tool_files -tool {SYNTHESIZE} " - append cmd $LIBERO_OTHER_CONSTRAINTS - append cmd "-module $TOP -input_type {constraint}" - eval $cmd - set cmd "organize_tool_files -tool {VERIFYTIMING} " - append cmd $LIBERO_OTHER_CONSTRAINTS - append cmd "-module $TOP -input_type {constraint}" - eval $cmd -} -if { [info exists LIBERO_PLACE_CONSTRAINTS] } { - set cmd "organize_tool_files -tool {PLACEROUTE} " - append cmd $LIBERO_PLACE_CONSTRAINTS - append cmd "-module $TOP -input_type {constraint}" - eval $cmd -} - -fpga_params +#global LIBERO_PLACE_CONSTRAINTS +#global LIBERO_OTHER_CONSTRAINTS +#if { [info exists LIBERO_OTHER_CONSTRAINTS] } { +# set cmd "organize_tool_files -tool {SYNTHESIZE} " +# append cmd $LIBERO_OTHER_CONSTRAINTS +# append cmd "-module {{ TOP }} -input_type {constraint}" +# eval $cmd +# set cmd "organize_tool_files -tool {VERIFYTIMING} " +# append cmd $LIBERO_OTHER_CONSTRAINTS +# append cmd "-module {{ TOP }} -input_type {constraint}" +# eval $cmd +#} +#if { [info exists LIBERO_PLACE_CONSTRAINTS] } { +# set cmd "organize_tool_files -tool {PLACEROUTE} " +# append cmd $LIBERO_PLACE_CONSTRAINTS +# append cmd "-module {{ TOP }} -input_type {constraint}" +# eval $cmd +#} + +#organize_tool_files -tool {SYNTHESIZE} \ +# -file ../resources/constraints/maker-board/clk.sdc \ +# -module Blink -input_type {constraint} + +#organize_tool_files -tool {PLACEROUTE} \ +# -file ../resources/constraints/maker-board/clk.sdc \ +# -file ../resources/constraints/maker-board/clk.pdc \ +# -file ../resources/constraints/maker-board/led.pdc \ +# -module Blink -input_type {constraint} + +#organize_tool_files -tool {VERIFYTIMING} \ +# -file ../resources/constraints/maker-board/clk.sdc \ +# -module Blink -input_type {constraint} {{ POSTCFG }} @@ -113,7 +83,7 @@ close_project #--[ Design flow ]------------------------------------------------------------- {% if SYN or PAR or BIT %} -open_project $PROJECT/$PROJECT.prjx +open_project {{ PROJECT }}/{{ PROJECT }}.prjx {% if SYN %} {{ PRESYN }} From 6ae7ce5f603e9a3f712d866e0b38d54da2a59e1a Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Mon, 1 Jul 2024 22:07:18 -0300 Subject: [PATCH 133/254] Fix quartus_sh mock-up --- tests/mocks/quartus_sh | 24 ++++++------------------ 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/tests/mocks/quartus_sh b/tests/mocks/quartus_sh index 64a8cf65..472f639f 100755 --- a/tests/mocks/quartus_sh +++ b/tests/mocks/quartus_sh @@ -20,30 +20,18 @@ args = parser.parse_args() tool = parser.prog tcl = f''' -lappend auto_path pkg - proc unknown args {{ }} -source {args.script} -''' +package provide ::quartus::project 1.0 +namespace eval ::quartus::project {{ }} -with open(f'{tool}-mock.tcl', 'w', encoding='utf-8') as file: - file.write(tcl) +package provide ::quartus::flow 1.0 +namespace eval ::quartus::flow {{ }} -tcl = f''' -namespace eval ::quartus {{ - namespace export project -}} +source {args.script} ''' -if not os.path.exists('pkg'): - os.makedirs('pkg') - -package ifneeded ::quartus 1.0 [list source quartus-pkg.tcl] - -pkgIndex.tcl - -with open(f'pkg/quartus.tcl', 'w', encoding='utf-8') as file: +with open(f'{tool}-mock.tcl', 'w', encoding='utf-8') as file: file.write(tcl) subprocess.run( From 4b8fe9eade9434042aaa5295b1061762e27fa835 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Mon, 1 Jul 2024 22:08:41 -0300 Subject: [PATCH 134/254] Fix ecp5 flow --- pyfpga/templates/openflow.jinja | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyfpga/templates/openflow.jinja b/pyfpga/templates/openflow.jinja index 971149be..f9b3ba05 100644 --- a/pyfpga/templates/openflow.jinja +++ b/pyfpga/templates/openflow.jinja @@ -98,7 +98,7 @@ icepack {{ PROJECT }}.asc {{ PROJECT }}.bit {% endif %} {% if FAMILY == 'ecp5' %} -$DOCKER hdlc/icestorm /bin/bash -c " +$DOCKER hdlc/prjtrellis /bin/bash -c " {{ PREBIT }} ecppack --svf {{ PROJECT }}.svf {{ PROJECT }}.config {{ PROJECT }}.bit {{ POSTBIT }} From 4769fe29191af81156f57ee2548175d24df78e65 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Mon, 1 Jul 2024 22:10:25 -0300 Subject: [PATCH 135/254] Fix to abort if the bit file doesn't exists --- pyfpga/templates/ise.jinja | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pyfpga/templates/ise.jinja b/pyfpga/templates/ise.jinja index 024e0edb..701a028d 100644 --- a/pyfpga/templates/ise.jinja +++ b/pyfpga/templates/ise.jinja @@ -78,8 +78,7 @@ if { [process get "Place & Route" status] == "errors" } { exit 1 } process run "Generate Programming File" if { [process get "Generate Programming File" status] == "errors" } { exit 1 } -# catch { file rename -force {{ TOP }}.bit {{ PROJECT }}.bit } -file rename -force {{ TOP }}.bit {{ PROJECT }}.bit +catch { file rename -force {{ TOP }}.bit {{ PROJECT }}.bit } {{ POSTBIT }} {% endif %} From 17af7b689a0e80773e6f268691f29065a87f6033 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Mon, 1 Jul 2024 22:11:39 -0300 Subject: [PATCH 136/254] Merged several examples under the project directory --- examples/ise/run.sh | 13 --------- examples/openflow/run.sh | 13 --------- examples/{ise/run.py => projects/ise.py} | 0 .../{libero/run.py => projects/libero.py} | 14 +++++---- .../{openflow/run.py => projects/openflow.py} | 0 .../{quartus/run.py => projects/quartus.py} | 0 examples/projects/regress.sh | 29 +++++++++++++++++++ .../{vivado/run.py => projects/vivado.py} | 0 examples/vivado/run.sh | 13 --------- 9 files changed, 38 insertions(+), 44 deletions(-) delete mode 100644 examples/ise/run.sh delete mode 100644 examples/openflow/run.sh rename examples/{ise/run.py => projects/ise.py} (100%) rename examples/{libero/run.py => projects/libero.py} (73%) rename examples/{openflow/run.py => projects/openflow.py} (100%) rename examples/{quartus/run.py => projects/quartus.py} (100%) create mode 100644 examples/projects/regress.sh rename examples/{vivado/run.py => projects/vivado.py} (100%) delete mode 100644 examples/vivado/run.sh diff --git a/examples/ise/run.sh b/examples/ise/run.sh deleted file mode 100644 index c13ee1c0..00000000 --- a/examples/ise/run.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash - -set -e - -BOARDS=("s6micro" "nexys3") -SOURCES=("vlog" "vhdl") - -for BOARD in "${BOARDS[@]}"; do - for SOURCE in "${SOURCES[@]}"; do - echo "> $BOARD - $SOURCE" - python3 run.py --board $BOARD --source $SOURCE - done -done diff --git a/examples/openflow/run.sh b/examples/openflow/run.sh deleted file mode 100644 index 986538d5..00000000 --- a/examples/openflow/run.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash - -set -e - -BOARDS=("icestick" "edu-ciaa" "orangecrab" "ecp5evn") -SOURCES=("vlog" "vhdl" "slog") - -for BOARD in "${BOARDS[@]}"; do - for SOURCE in "${SOURCES[@]}"; do - echo "> $BOARD - $SOURCE" - python3 run.py --board $BOARD --source $SOURCE - done -done diff --git a/examples/ise/run.py b/examples/projects/ise.py similarity index 100% rename from examples/ise/run.py rename to examples/projects/ise.py diff --git a/examples/libero/run.py b/examples/projects/libero.py similarity index 73% rename from examples/libero/run.py rename to examples/projects/libero.py index 87e9dc06..9d2fc6da 100644 --- a/examples/libero/run.py +++ b/examples/projects/libero.py @@ -5,6 +5,9 @@ from pyfpga.libero import Libero parser = argparse.ArgumentParser() +parser.add_argument( + '--board', choices=['maker-board'], default='maker-board' +) parser.add_argument( '--source', choices=['vlog', 'vhdl', 'slog'], default='vlog' ) @@ -15,11 +18,12 @@ prj = Libero(odir='../build/libero') -prj.set_part('m2s010-1-tq144') -prj.add_param('FREQ', '125000000') -prj.add_cons('../sources/maker-board/clk.sdc', 'syn') -prj.add_cons('../sources/maker-board/clk.pdc', 'par') -prj.add_cons('../sources/maker-board/led.pdc', 'par') +if args.board == 'maker-board': + prj.set_part('m2s010-1-tq144') + prj.add_param('FREQ', '125000000') + prj.add_cons('../sources/maker-board/clk.sdc', 'syn') + prj.add_cons('../sources/maker-board/clk.pdc', 'par') + prj.add_cons('../sources/maker-board/led.pdc', 'par') prj.add_param('SECS', '1') if args.source == 'vhdl': diff --git a/examples/openflow/run.py b/examples/projects/openflow.py similarity index 100% rename from examples/openflow/run.py rename to examples/projects/openflow.py diff --git a/examples/quartus/run.py b/examples/projects/quartus.py similarity index 100% rename from examples/quartus/run.py rename to examples/projects/quartus.py diff --git a/examples/projects/regress.sh b/examples/projects/regress.sh new file mode 100644 index 00000000..f5513b3e --- /dev/null +++ b/examples/projects/regress.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +set -e + +declare -A TOOLS + +TOOLS["ise"]="s6micro nexys3" +TOOLS["libero"]="maker-board" +TOOLS["openflow"]="icestick edu-ciaa orangecrab ecp5evn" +TOOLS["quartus"]="de10nano" +TOOLS["vivado"]="zybo arty" + +SOURCES=("vlog" "vhdl" "slog") + +for TOOL in "${!TOOLS[@]}"; do + BOARDS=${TOOLS[$TOOL]} + for BOARD in $BOARDS; do + for SOURCE in "${SOURCES[@]}"; do + if [[ "$TOOL" == "ise" && "$SOURCE" == "slog" ]]; then + continue + fi + if [[ "$TOOL" == "openflow" && "$SOURCE" != "vlog" ]]; then + continue + fi + echo "> $TOOL - $BOARD - $SOURCE" + python3 $TOOL.py --board $BOARD --source $SOURCE + done + done +done diff --git a/examples/vivado/run.py b/examples/projects/vivado.py similarity index 100% rename from examples/vivado/run.py rename to examples/projects/vivado.py diff --git a/examples/vivado/run.sh b/examples/vivado/run.sh deleted file mode 100644 index 3b6a065a..00000000 --- a/examples/vivado/run.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash - -set -e - -BOARDS=("zybo" "arty") -SOURCES=("vlog" "vhdl" "slog") - -for BOARD in "${BOARDS[@]}"; do - for SOURCE in "${SOURCES[@]}"; do - echo "> $BOARD - $SOURCE" - python3 run.py --board $BOARD --source $SOURCE - done -done From bce2b2f23107a01285c367194121790851f17bd8 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Mon, 1 Jul 2024 22:17:50 -0300 Subject: [PATCH 137/254] Removed deprecated files/examples --- examples/Makefile | 11 ------- examples/configs.yml | 26 ---------------- examples/multi/memory.py | 24 --------------- examples/multi/parameters.py | 44 -------------------------- examples/multi/projects.py | 60 ------------------------------------ examples/multi/verilog.py | 24 --------------- examples/multi/vhdl.py | 21 ------------- 7 files changed, 210 deletions(-) delete mode 100644 examples/Makefile delete mode 100644 examples/configs.yml delete mode 100644 examples/multi/memory.py delete mode 100644 examples/multi/parameters.py delete mode 100644 examples/multi/projects.py delete mode 100644 examples/multi/verilog.py delete mode 100644 examples/multi/vhdl.py diff --git a/examples/Makefile b/examples/Makefile deleted file mode 100644 index 7bfe3fc4..00000000 --- a/examples/Makefile +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/make - -ifdef MOCKS -export PATH := $(PATH):$(PWD)/../test/mocks -$(info INFO: using MOCKS for the vendor EDA tools) -endif - -DIRS=$(wildcard */) - -all: - @$(foreach DIR, $(DIRS), make -C $(DIR) || exit;) diff --git a/examples/configs.yml b/examples/configs.yml deleted file mode 100644 index 9feaf6b7..00000000 --- a/examples/configs.yml +++ /dev/null @@ -1,26 +0,0 @@ -openflow: - oci: - engine: - command: docker - volumes: ["$HOME:$HOME"] - work: $PWD - containers: - ghdl: "ghdl/synth:beta" - yosys: "ghdl/synth:beta" - nextpnr-ice40: "ghdl/synth:nextpnr-ice40" - icetime: "ghdl/synth:icestorm" - icepack: "ghdl/synth:icestorm" - iceprog: "--device /dev/bus/usb ghdl/synth:prog" - nextpnr-ecp5: "ghdl/synth:nextpnr-ecp5" - ecppack: "ghdl/synth:trellis" - openocd: "--device /dev/bus/usb ghdl/synth:prog" - tools: - ghdl: ghdl - yosys: yosys - nextpnr-ice40: nextpnr-ice40 - icetime: icetime - icepack: icepack - iceprog: iceprog - nextpnr-ecp5: nextpnr-ecp5 - ecppack: ecppack - openocd: openocd diff --git a/examples/multi/memory.py b/examples/multi/memory.py deleted file mode 100644 index bbec7300..00000000 --- a/examples/multi/memory.py +++ /dev/null @@ -1,24 +0,0 @@ -"""PyFPGA example about Memory Content Files inclusion. - -This example is mainly used as a test of this feature through the different -tools. -""" - -import logging - -from fpga.project import Project, TOOLS - -logging.basicConfig() - -for hdl in ['vhdl', 'verilog']: - for tool in TOOLS: - if tool == 'ghdl' and hdl == 'verilog': - continue - PRJ = Project(tool) - PRJ.set_outdir('../../build/multi/memory/%s/%s' % (tool, hdl)) - if hdl == 'vhdl': - PRJ.add_files('../../hdl/ram.vhdl') - else: - PRJ.add_files('../../hdl/ram.v') - PRJ.set_top('ram') - PRJ.generate(to_task='syn') diff --git a/examples/multi/parameters.py b/examples/multi/parameters.py deleted file mode 100644 index f1912c85..00000000 --- a/examples/multi/parameters.py +++ /dev/null @@ -1,44 +0,0 @@ -"""PyFPGA Multi Vendor example where parameters are changed. - -The main idea of a multi-vendor project is to implements the same HDL code -with different tools, to make comparisons. The project name is not important -and the default devices are used. In this example, VHDL and Verilog -files are synthesized changing the value of its generics/parameters. -""" - -import logging - -from fpga.project import Project, TOOLS - -logging.basicConfig() - -for hdl in ['vhdl', 'verilog']: - for tool in TOOLS: - if tool == 'ghdl': - continue - if hdl == 'vhdl': - if tool in ['openflow', 'yosys', 'yosys-ise', 'yosys-vivado']: - continue - PRJ = Project(tool) - PRJ.add_param('FREQ', '50000000') - PRJ.add_param('SECS', '2') - PRJ.set_outdir('../../build/multi/params/%s/%s' % (tool, hdl)) - if hdl == 'vhdl': - PRJ.add_files('../../hdl/blinking.vhdl') - else: - PRJ.add_vlog_include('../../hdl/headers1') - PRJ.add_vlog_include('../../hdl/headers2') - PRJ.add_files('../../hdl/blinking.v') - PRJ.set_top('Blinking') - # PRJ.add_param('INT', '15') - # PRJ.add_param('REA', '1.5') - # PRJ.add_param('LOG', "'1'") - # PRJ.add_param('VEC', '"10101010"') - # PRJ.add_param('STR', '"WXYZ"') - # PRJ.set_outdir('../../build/multi/params/%s/%s' % (tool, hdl)) - # if hdl == 'vhdl': - # PRJ.add_files('../../hdl/fakes/generics.vhdl') - # else: - # PRJ.add_files('../../hdl/fakes/parameters.v') - # PRJ.set_top('Params') - PRJ.generate(to_task='syn') diff --git a/examples/multi/projects.py b/examples/multi/projects.py deleted file mode 100644 index 3d267a93..00000000 --- a/examples/multi/projects.py +++ /dev/null @@ -1,60 +0,0 @@ -"""PyFPGA Multi Project example. - -The main idea of a multi-project is to manage more than one project with the -same script. -""" - -import logging - -from fpga.project import Project - -logging.basicConfig() - -PROJECTS = { - 'prj1': Project( - 'vivado', - 'vivado-prj', - meta={ - 'outdir': '../../build/multi/projects/vivado', - 'part': 'xc7k70t-3-fbg484', - 'vhdl': [ - ['../../hdl/blinking.vhdl', 'examples'], - ['../../hdl/examples_pkg.vhdl', 'examples'], - '../../hdl/top.vhdl' - ], - 'top': 'Top' - } - ), - 'prj2': Project( - 'ise', - 'ise-prj', - meta={ - 'outdir': '../../build/multi/projects/ise', - 'part': 'xc6slx9-2-csg324', - 'vhdl': [ - '../../hdl/blinking.vhdl' - ], - 'top': 'Blinking' - } - ), - 'prj3': Project( - 'quartus', - 'qurtus-prj', - meta={ - 'outdir': '../../build/multi/projects/quartus', - 'part': '5CEBA2F17A7', - 'paths': [ - '../../hdl/headers1', - '../../hdl/headers2' - ], - 'verilog': [ - '../../hdl/blinking.v', - '../../hdl/top.v' - ], - 'top': 'Top' - } - ) -} - -for prj in PROJECTS: - PROJECTS[prj].generate('syn') diff --git a/examples/multi/verilog.py b/examples/multi/verilog.py deleted file mode 100644 index b16be138..00000000 --- a/examples/multi/verilog.py +++ /dev/null @@ -1,24 +0,0 @@ -"""PyFPGA Multi Vendor Verilog example. - -The main idea of a multi-vendor project is to implements the same HDL code -with different tools, to make comparisons. The project name is not important -and the default devices could be used. -""" - -import logging - -from fpga.project import Project, TOOLS - -logging.basicConfig() - -for tool in TOOLS: - if tool == 'ghdl': - continue - PRJ = Project(tool) - PRJ.set_outdir('../../build/multi/verilog/%s' % tool) - PRJ.add_vlog_include('../../hdl/headers1') - PRJ.add_vlog_include('../../hdl/headers2') - PRJ.add_files('../../hdl/blinking.v') - PRJ.add_files('../../hdl/top.v') - PRJ.set_top('Top') - PRJ.generate(to_task='syn') diff --git a/examples/multi/vhdl.py b/examples/multi/vhdl.py deleted file mode 100644 index 7c6ee7d8..00000000 --- a/examples/multi/vhdl.py +++ /dev/null @@ -1,21 +0,0 @@ -"""PyFPGA Multi Vendor VHDL example. - -The main idea of a multi-vendor project is to implements the same HDL code -with different tools, to make comparisons. The project name is not important -and the default devices are used. -""" - -import logging - -from fpga.project import Project, TOOLS - -logging.basicConfig() - -for tool in TOOLS: - PRJ = Project(tool) - PRJ.set_outdir('../../build/multi/vhdl/%s' % tool) - PRJ.add_files('../../hdl/blinking.vhdl', library='examples') - PRJ.add_files('../../hdl/examples_pkg.vhdl', library='examples') - PRJ.add_files('../../hdl/top.vhdl') - PRJ.set_top('Top') - PRJ.generate(to_task='syn') From db4a4eae532775492a91c6e51fc269fb14fbc7b8 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Mon, 1 Jul 2024 22:36:48 -0300 Subject: [PATCH 138/254] Moved constraint files under the cons directory --- examples/projects/ise.py | 12 ++++++------ examples/projects/libero.py | 8 ++++---- examples/projects/openflow.py | 16 ++++++++-------- examples/projects/quartus.py | 6 +++--- examples/projects/regress.sh | 2 +- examples/projects/vivado.py | 12 ++++++------ examples/sources/{zybo => cons}/ZYBO/clk.xdc | 0 examples/sources/{zybo => cons}/ZYBO/led.xdc | 0 examples/sources/{zybo => cons}/ZYBO/timing.xdc | 0 .../{arty/a7-35t => cons/arty_a7_35t}/clk.xdc | 0 .../{arty/a7-35t => cons/arty_a7_35t}/led.xdc | 0 .../{arty/a7-35t => cons/arty_a7_35t}/timing.xdc | 0 examples/sources/{ => cons}/de10nano/clk.sdc | 0 examples/sources/{ => cons}/de10nano/clk.tcl | 0 examples/sources/{ => cons}/de10nano/led.tcl | 0 examples/sources/{ => cons}/ecp5evn/clk.lpf | 0 examples/sources/{ => cons}/ecp5evn/led.lpf | 0 examples/sources/{ => cons}/edu-ciaa/clk.pcf | 0 examples/sources/{ => cons}/edu-ciaa/led.pcf | 0 examples/sources/{ => cons}/icestick/clk.pcf | 0 examples/sources/{ => cons}/icestick/led.pcf | 0 .../sources/{maker-board => cons/maker}/clk.pdc | 0 .../sources/{maker-board => cons/maker}/clk.sdc | 0 .../sources/{maker-board => cons/maker}/led.pdc | 0 examples/sources/{ => cons}/nexys3/clk.ucf | 0 examples/sources/{ => cons}/nexys3/clk.xcf | 0 examples/sources/{ => cons}/nexys3/led.ucf | 0 examples/sources/{ => cons}/orangecrab/clk.lpf | 0 examples/sources/{ => cons}/orangecrab/led.lpf | 0 examples/sources/{ => cons}/s6micro/clk.ucf | 0 examples/sources/{ => cons}/s6micro/clk.xcf | 0 examples/sources/{ => cons}/s6micro/led.ucf | 0 32 files changed, 28 insertions(+), 28 deletions(-) rename examples/sources/{zybo => cons}/ZYBO/clk.xdc (100%) rename examples/sources/{zybo => cons}/ZYBO/led.xdc (100%) rename examples/sources/{zybo => cons}/ZYBO/timing.xdc (100%) rename examples/sources/{arty/a7-35t => cons/arty_a7_35t}/clk.xdc (100%) rename examples/sources/{arty/a7-35t => cons/arty_a7_35t}/led.xdc (100%) rename examples/sources/{arty/a7-35t => cons/arty_a7_35t}/timing.xdc (100%) rename examples/sources/{ => cons}/de10nano/clk.sdc (100%) rename examples/sources/{ => cons}/de10nano/clk.tcl (100%) rename examples/sources/{ => cons}/de10nano/led.tcl (100%) rename examples/sources/{ => cons}/ecp5evn/clk.lpf (100%) rename examples/sources/{ => cons}/ecp5evn/led.lpf (100%) rename examples/sources/{ => cons}/edu-ciaa/clk.pcf (100%) rename examples/sources/{ => cons}/edu-ciaa/led.pcf (100%) rename examples/sources/{ => cons}/icestick/clk.pcf (100%) rename examples/sources/{ => cons}/icestick/led.pcf (100%) rename examples/sources/{maker-board => cons/maker}/clk.pdc (100%) rename examples/sources/{maker-board => cons/maker}/clk.sdc (100%) rename examples/sources/{maker-board => cons/maker}/led.pdc (100%) rename examples/sources/{ => cons}/nexys3/clk.ucf (100%) rename examples/sources/{ => cons}/nexys3/clk.xcf (100%) rename examples/sources/{ => cons}/nexys3/led.ucf (100%) rename examples/sources/{ => cons}/orangecrab/clk.lpf (100%) rename examples/sources/{ => cons}/orangecrab/led.lpf (100%) rename examples/sources/{ => cons}/s6micro/clk.ucf (100%) rename examples/sources/{ => cons}/s6micro/clk.xcf (100%) rename examples/sources/{ => cons}/s6micro/led.ucf (100%) diff --git a/examples/projects/ise.py b/examples/projects/ise.py index 3bd89df9..a1b9dba0 100644 --- a/examples/projects/ise.py +++ b/examples/projects/ise.py @@ -22,15 +22,15 @@ if args.board == 's6micro': prj.set_part('xc6slx9-2-csg324') prj.add_param('FREQ', '125000000') - prj.add_cons('../sources/s6micro/clk.xcf', 'syn') - prj.add_cons('../sources/s6micro/clk.ucf', 'par') - prj.add_cons('../sources/s6micro/led.ucf', 'par') + prj.add_cons('../sources/cons/s6micro/clk.xcf', 'syn') + prj.add_cons('../sources/cons/s6micro/clk.ucf', 'par') + prj.add_cons('../sources/cons/s6micro/led.ucf', 'par') if args.board == 'nexys3': prj.set_part('xc6slx16-3-csg32') prj.add_param('FREQ', '100000000') - prj.add_cons('../sources/nexys3/clk.xcf', 'syn') - prj.add_cons('../sources/nexys3/clk.ucf', 'par') - prj.add_cons('../sources/nexys3/led.ucf', 'par') + prj.add_cons('../sources/cons/nexys3/clk.xcf', 'syn') + prj.add_cons('../sources/cons/nexys3/clk.ucf', 'par') + prj.add_cons('../sources/cons/nexys3/led.ucf', 'par') prj.add_param('SECS', '1') if args.source == 'vhdl': diff --git a/examples/projects/libero.py b/examples/projects/libero.py index 9d2fc6da..9c570a0e 100644 --- a/examples/projects/libero.py +++ b/examples/projects/libero.py @@ -6,7 +6,7 @@ parser = argparse.ArgumentParser() parser.add_argument( - '--board', choices=['maker-board'], default='maker-board' + '--board', choices=['maker'], default='maker' ) parser.add_argument( '--source', choices=['vlog', 'vhdl', 'slog'], default='vlog' @@ -21,9 +21,9 @@ if args.board == 'maker-board': prj.set_part('m2s010-1-tq144') prj.add_param('FREQ', '125000000') - prj.add_cons('../sources/maker-board/clk.sdc', 'syn') - prj.add_cons('../sources/maker-board/clk.pdc', 'par') - prj.add_cons('../sources/maker-board/led.pdc', 'par') + prj.add_cons('../sources/cons/maker/clk.sdc', 'syn') + prj.add_cons('../sources/cons/maker/clk.pdc', 'par') + prj.add_cons('../sources/cons/maker/led.pdc', 'par') prj.add_param('SECS', '1') if args.source == 'vhdl': diff --git a/examples/projects/openflow.py b/examples/projects/openflow.py index b01153c3..43c20f0b 100644 --- a/examples/projects/openflow.py +++ b/examples/projects/openflow.py @@ -23,23 +23,23 @@ if args.board == 'icestick': prj.set_part('hx1k-tq144') prj.add_param('FREQ', '100000000') - prj.add_cons('../sources/icestick/clk.pcf', 'par') - prj.add_cons('../sources/icestick/led.pcf', 'par') + prj.add_cons('../sources/cons/icestick/clk.pcf', 'par') + prj.add_cons('../sources/cons/icestick/led.pcf', 'par') if args.board == 'edu-ciaa': prj.set_part('hx1k-tq144') prj.add_param('FREQ', '100000000') - prj.add_cons('../sources/edu-ciaa/clk.pcf', 'par') - prj.add_cons('../sources/edu-ciaa/led.pcf', 'par') + prj.add_cons('../sources/cons/edu-ciaa/clk.pcf', 'par') + prj.add_cons('../sources/cons/edu-ciaa/led.pcf', 'par') if args.board == 'orangecrab': prj.set_part('25k-CSFBGA285') prj.add_param('FREQ', '100000000') - prj.add_cons('../sources/orangecrab/clk.lpf', 'par') - prj.add_cons('../sources/orangecrab/led.lpf', 'par') + prj.add_cons('../sources/cons/orangecrab/clk.lpf', 'par') + prj.add_cons('../sources/cons/orangecrab/led.lpf', 'par') if args.board == 'ecp5evn': prj.set_part('um5g-85k-CABGA381') prj.add_param('FREQ', '100000000') - prj.add_cons('../sources/ecp5evn/clk.lpf', 'par') - prj.add_cons('../sources/ecp5evn/led.lpf', 'par') + prj.add_cons('../sources/cons/ecp5evn/clk.lpf', 'par') + prj.add_cons('../sources/cons/ecp5evn/led.lpf', 'par') prj.add_param('FREQ', '100000000') prj.add_param('SECS', '1') diff --git a/examples/projects/quartus.py b/examples/projects/quartus.py index 0453ac8e..ed5e4270 100644 --- a/examples/projects/quartus.py +++ b/examples/projects/quartus.py @@ -22,9 +22,9 @@ if args.board == 'de10nano': prj.set_part('5CSEBA6U23I7') prj.add_param('FREQ', '125000000') - prj.add_cons('../sources/de10nano/clk.sdc', 'syn') - prj.add_cons('../sources/de10nano/clk.tcl', 'par') - prj.add_cons('../sources/de10nano/led.tcl', 'par') + prj.add_cons('../sources/cons/de10nano/clk.sdc', 'syn') + prj.add_cons('../sources/cons/de10nano/clk.tcl', 'par') + prj.add_cons('../sources/cons/de10nano/led.tcl', 'par') prj.add_param('SECS', '1') if args.source == 'vhdl': diff --git a/examples/projects/regress.sh b/examples/projects/regress.sh index f5513b3e..6af2f12b 100644 --- a/examples/projects/regress.sh +++ b/examples/projects/regress.sh @@ -5,7 +5,7 @@ set -e declare -A TOOLS TOOLS["ise"]="s6micro nexys3" -TOOLS["libero"]="maker-board" +TOOLS["libero"]="maker" TOOLS["openflow"]="icestick edu-ciaa orangecrab ecp5evn" TOOLS["quartus"]="de10nano" TOOLS["vivado"]="zybo arty" diff --git a/examples/projects/vivado.py b/examples/projects/vivado.py index 8ae97d6f..1ef6ace7 100644 --- a/examples/projects/vivado.py +++ b/examples/projects/vivado.py @@ -22,15 +22,15 @@ if args.board == 'zybo': prj.set_part('xc7z010-1-clg400') prj.add_param('FREQ', '125000000') - prj.add_cons('../sources/zybo/ZYBO/timing.xdc', 'syn') - prj.add_cons('../sources/zybo/ZYBO/clk.xdc', 'par') - prj.add_cons('../sources/zybo/ZYBO/led.xdc', 'par') + prj.add_cons('../sources/cons/ZYBO/timing.xdc', 'syn') + prj.add_cons('../sources/cons/ZYBO/clk.xdc', 'par') + prj.add_cons('../sources/cons/ZYBO/led.xdc', 'par') if args.board == 'arty': prj.set_part('xc7a35ticsg324-1L') prj.add_param('FREQ', '100000000') - prj.add_cons('../sources/arty/a7-35t/timing.xdc', 'syn') - prj.add_cons('../sources/arty/a7-35t/clk.xdc', 'par') - prj.add_cons('../sources/arty/a7-35t/led.xdc', 'par') + prj.add_cons('../sources/cons/arty_a7_35t/timing.xdc', 'syn') + prj.add_cons('../sources/cons/arty_a7_35t/clk.xdc', 'par') + prj.add_cons('../sources/cons/arty_a7_35t/led.xdc', 'par') prj.add_param('SECS', '1') if args.source == 'vhdl': diff --git a/examples/sources/zybo/ZYBO/clk.xdc b/examples/sources/cons/ZYBO/clk.xdc similarity index 100% rename from examples/sources/zybo/ZYBO/clk.xdc rename to examples/sources/cons/ZYBO/clk.xdc diff --git a/examples/sources/zybo/ZYBO/led.xdc b/examples/sources/cons/ZYBO/led.xdc similarity index 100% rename from examples/sources/zybo/ZYBO/led.xdc rename to examples/sources/cons/ZYBO/led.xdc diff --git a/examples/sources/zybo/ZYBO/timing.xdc b/examples/sources/cons/ZYBO/timing.xdc similarity index 100% rename from examples/sources/zybo/ZYBO/timing.xdc rename to examples/sources/cons/ZYBO/timing.xdc diff --git a/examples/sources/arty/a7-35t/clk.xdc b/examples/sources/cons/arty_a7_35t/clk.xdc similarity index 100% rename from examples/sources/arty/a7-35t/clk.xdc rename to examples/sources/cons/arty_a7_35t/clk.xdc diff --git a/examples/sources/arty/a7-35t/led.xdc b/examples/sources/cons/arty_a7_35t/led.xdc similarity index 100% rename from examples/sources/arty/a7-35t/led.xdc rename to examples/sources/cons/arty_a7_35t/led.xdc diff --git a/examples/sources/arty/a7-35t/timing.xdc b/examples/sources/cons/arty_a7_35t/timing.xdc similarity index 100% rename from examples/sources/arty/a7-35t/timing.xdc rename to examples/sources/cons/arty_a7_35t/timing.xdc diff --git a/examples/sources/de10nano/clk.sdc b/examples/sources/cons/de10nano/clk.sdc similarity index 100% rename from examples/sources/de10nano/clk.sdc rename to examples/sources/cons/de10nano/clk.sdc diff --git a/examples/sources/de10nano/clk.tcl b/examples/sources/cons/de10nano/clk.tcl similarity index 100% rename from examples/sources/de10nano/clk.tcl rename to examples/sources/cons/de10nano/clk.tcl diff --git a/examples/sources/de10nano/led.tcl b/examples/sources/cons/de10nano/led.tcl similarity index 100% rename from examples/sources/de10nano/led.tcl rename to examples/sources/cons/de10nano/led.tcl diff --git a/examples/sources/ecp5evn/clk.lpf b/examples/sources/cons/ecp5evn/clk.lpf similarity index 100% rename from examples/sources/ecp5evn/clk.lpf rename to examples/sources/cons/ecp5evn/clk.lpf diff --git a/examples/sources/ecp5evn/led.lpf b/examples/sources/cons/ecp5evn/led.lpf similarity index 100% rename from examples/sources/ecp5evn/led.lpf rename to examples/sources/cons/ecp5evn/led.lpf diff --git a/examples/sources/edu-ciaa/clk.pcf b/examples/sources/cons/edu-ciaa/clk.pcf similarity index 100% rename from examples/sources/edu-ciaa/clk.pcf rename to examples/sources/cons/edu-ciaa/clk.pcf diff --git a/examples/sources/edu-ciaa/led.pcf b/examples/sources/cons/edu-ciaa/led.pcf similarity index 100% rename from examples/sources/edu-ciaa/led.pcf rename to examples/sources/cons/edu-ciaa/led.pcf diff --git a/examples/sources/icestick/clk.pcf b/examples/sources/cons/icestick/clk.pcf similarity index 100% rename from examples/sources/icestick/clk.pcf rename to examples/sources/cons/icestick/clk.pcf diff --git a/examples/sources/icestick/led.pcf b/examples/sources/cons/icestick/led.pcf similarity index 100% rename from examples/sources/icestick/led.pcf rename to examples/sources/cons/icestick/led.pcf diff --git a/examples/sources/maker-board/clk.pdc b/examples/sources/cons/maker/clk.pdc similarity index 100% rename from examples/sources/maker-board/clk.pdc rename to examples/sources/cons/maker/clk.pdc diff --git a/examples/sources/maker-board/clk.sdc b/examples/sources/cons/maker/clk.sdc similarity index 100% rename from examples/sources/maker-board/clk.sdc rename to examples/sources/cons/maker/clk.sdc diff --git a/examples/sources/maker-board/led.pdc b/examples/sources/cons/maker/led.pdc similarity index 100% rename from examples/sources/maker-board/led.pdc rename to examples/sources/cons/maker/led.pdc diff --git a/examples/sources/nexys3/clk.ucf b/examples/sources/cons/nexys3/clk.ucf similarity index 100% rename from examples/sources/nexys3/clk.ucf rename to examples/sources/cons/nexys3/clk.ucf diff --git a/examples/sources/nexys3/clk.xcf b/examples/sources/cons/nexys3/clk.xcf similarity index 100% rename from examples/sources/nexys3/clk.xcf rename to examples/sources/cons/nexys3/clk.xcf diff --git a/examples/sources/nexys3/led.ucf b/examples/sources/cons/nexys3/led.ucf similarity index 100% rename from examples/sources/nexys3/led.ucf rename to examples/sources/cons/nexys3/led.ucf diff --git a/examples/sources/orangecrab/clk.lpf b/examples/sources/cons/orangecrab/clk.lpf similarity index 100% rename from examples/sources/orangecrab/clk.lpf rename to examples/sources/cons/orangecrab/clk.lpf diff --git a/examples/sources/orangecrab/led.lpf b/examples/sources/cons/orangecrab/led.lpf similarity index 100% rename from examples/sources/orangecrab/led.lpf rename to examples/sources/cons/orangecrab/led.lpf diff --git a/examples/sources/s6micro/clk.ucf b/examples/sources/cons/s6micro/clk.ucf similarity index 100% rename from examples/sources/s6micro/clk.ucf rename to examples/sources/cons/s6micro/clk.ucf diff --git a/examples/sources/s6micro/clk.xcf b/examples/sources/cons/s6micro/clk.xcf similarity index 100% rename from examples/sources/s6micro/clk.xcf rename to examples/sources/cons/s6micro/clk.xcf diff --git a/examples/sources/s6micro/led.ucf b/examples/sources/cons/s6micro/led.ucf similarity index 100% rename from examples/sources/s6micro/led.ucf rename to examples/sources/cons/s6micro/led.ucf From ba268eeab18d12eb8e71532c51a444fda60fd6c9 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Wed, 3 Jul 2024 21:19:34 -0300 Subject: [PATCH 139/254] Added a list of similar projects --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index aa17c849..9630e0ce 100644 --- a/README.md +++ b/README.md @@ -65,3 +65,11 @@ pip install -e . > With `-e` (`--editable`) your application is installed into site-packages via a kind of symlink. > That allows pulling changes through git or changing the branch, avoiding the need to reinstall the package. + +## Similar projects + +* [edalize](https://github.com/olofk/edalize): an abstraction library for interfacing EDA tools. +* [Hdlmake](https://ohwr.org/project/hdl-make): tool for generating multi-purpose makefiles for FPGA projects. +* HDL On Git ([Hog](https://gitlab.com/hog-cern/Hog)): a set of Tcl/Shell scripts plus a suitable methodology to handle HDL designs in a GitLab repository. +* IPbus Builder ([IPBB](https://github.com/ipbus/ipbb)): a tool for streamlining the synthesis, implementation and simulation of modular firmware projects over multiple platforms. +* [tsfpa](https://github.com/tsfpga/tsfpga): a flexible and scalable development platform for modern FPGA projects. From b13cc74292a19e5b4f0b8b174fd0406e148f9995 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Wed, 3 Jul 2024 21:20:03 -0300 Subject: [PATCH 140/254] Removed an extra add_param --- examples/projects/openflow.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/examples/projects/openflow.py b/examples/projects/openflow.py index 43c20f0b..8eec90e4 100644 --- a/examples/projects/openflow.py +++ b/examples/projects/openflow.py @@ -40,8 +40,6 @@ prj.add_param('FREQ', '100000000') prj.add_cons('../sources/cons/ecp5evn/clk.lpf', 'par') prj.add_cons('../sources/cons/ecp5evn/led.lpf', 'par') - -prj.add_param('FREQ', '100000000') prj.add_param('SECS', '1') if args.source == 'vhdl': From e153c19e29c1495fb5fdbf106098366ecfa28484 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Wed, 3 Jul 2024 21:21:30 -0300 Subject: [PATCH 141/254] Small improvements --- tests/projects/support.py | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/tests/projects/support.py b/tests/projects/support.py index 4ce15596..12cb13c9 100644 --- a/tests/projects/support.py +++ b/tests/projects/support.py @@ -1,5 +1,6 @@ -"""Vivado examples.""" +"""Support examples.""" +import argparse import sys from pyfpga.ise import Ise @@ -9,7 +10,7 @@ from pyfpga.vivado import Vivado -classes = { +tools = { 'ise': Ise, 'libero': Libero, 'openflow': Openflow, @@ -17,18 +18,18 @@ 'vivado': Vivado } -tool = sys.argv[1] if len(sys.argv) > 1 else 'openflow' +parser = argparse.ArgumentParser() +parser.add_argument( + '--tool', default='openflow', + choices=['ise', 'libero', 'quartus', 'openflow', 'vivado'] +) +args = parser.parse_args() -Class = classes.get(tool) - -if Class is None: - sys.exit('Unsupported tool') - -print(f'INFO: the Class Under Test is {Class.__name__}') +print(f'INFO: the Tool Under Test is {args.tool}') try: print('INFO: checking Verilog Includes') - prj = Class() + prj = tools[args.tool]() prj.add_vlog('../../examples/sources/vlog/*.v') prj.set_top('Top') prj.add_define('DEFINE1', '1') @@ -44,7 +45,7 @@ try: print('INFO: checking Verilog Defines') - prj = Class() + prj = tools[args.tool]() prj.add_vlog('../../examples/sources/vlog/*.v') prj.set_top('Top') prj.add_include('../../examples/sources/vlog/include1') @@ -60,7 +61,7 @@ try: print('INFO: checking Verilog Parameters') - prj = Class() + prj = tools[args.tool]() prj.add_vlog('../../examples/sources/vlog/*.v') prj.set_top('Top') prj.add_include('../../examples/sources/vlog/include1') @@ -75,7 +76,7 @@ pass print('INFO: checking Verilog Support') -prj = Class() +prj = tools[args.tool]() prj.add_vlog('../../examples/sources/vlog/*.v') prj.set_top('Top') prj.add_include('../../examples/sources/vlog/include1') @@ -86,9 +87,9 @@ prj.add_param('SECS', '1') prj.make(last='syn') -if tool not in ['ise', 'openflow']: +if args.tool not in ['ise', 'openflow']: print('INFO: checking System Verilog Support') - prj = Class() + prj = tools[args.tool]() prj.add_slog('../../examples/sources/slog/*.sv') prj.set_top('Top') prj.add_include('../../examples/sources/slog/include1') @@ -99,10 +100,10 @@ prj.add_param('SECS', '1') prj.make(last='syn') -if tool not in ['openflow']: +if args.tool not in ['openflow']: try: print('INFO: checking VHDL Generics') - prj = Class() + prj = tools[args.tool]() prj.add_vhdl('../../examples/sources/vhdl/*.vhdl', 'blink_lib') prj.set_top('Top') prj.make(last='syn') @@ -113,7 +114,7 @@ pass print('* VHDL Support') - prj = Class() + prj = tools[args.tool]() prj.add_vhdl('../../examples/sources/vhdl/*.vhdl', 'blink_lib') prj.set_top('Top') prj.add_param('FREQ', '1') From 508f58a5ae6b29ab4855b8fc1210206c4390051d Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Wed, 3 Jul 2024 21:22:13 -0300 Subject: [PATCH 142/254] Fix board name --- examples/projects/libero.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/projects/libero.py b/examples/projects/libero.py index 9c570a0e..10d6b36d 100644 --- a/examples/projects/libero.py +++ b/examples/projects/libero.py @@ -18,7 +18,7 @@ prj = Libero(odir='../build/libero') -if args.board == 'maker-board': +if args.board == 'maker': prj.set_part('m2s010-1-tq144') prj.add_param('FREQ', '125000000') prj.add_cons('../sources/cons/maker/clk.sdc', 'syn') From aa342893ea291931f46601bf37d672ab6540c779 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Wed, 3 Jul 2024 21:23:06 -0300 Subject: [PATCH 143/254] Improved support for Libero (still WIP) --- pyfpga/libero.py | 35 +++++++++++++-------------------- pyfpga/templates/libero.jinja | 37 +++++++++++++++++++---------------- 2 files changed, 34 insertions(+), 38 deletions(-) diff --git a/pyfpga/libero.py b/pyfpga/libero.py index 29fc471a..a6aa6adf 100644 --- a/pyfpga/libero.py +++ b/pyfpga/libero.py @@ -31,11 +31,6 @@ def _make_prepare(self, steps): } for step in steps: context[step] = 1 - if 'includes' in self.data: - includes = [] - for include in self.data['includes']: - includes.append(str(include)) - context['INCLUDES'] = ';'.join(includes) files = [] if 'files' in self.data: for file in self.data['files']: @@ -57,21 +52,19 @@ def _make_prepare(self, steps): context['CONSTRAINTS'] = " ".join(constraints) if files: context['FILES'] = '\n'.join(files) - if 'top' in self.data: - context['TOP'] = self.data['top'] - if 'defines' in self.data: - defines = [] - for key, value in self.data['defines'].items(): - defines.append(f'{key}={value}') - context['DEFINES'] = ' '.join(defines) - if 'params' in self.data: - params = [] - for key, value in self.data['params'].items(): - params.append(f'{key}={value}') - context['PARAMS'] = ' '.join(params) + context['INCLUDES'] = self.data.get('includes', None) # ';'.join(includes) + context['TOP'] = self.data.get('top', None) + context['DEFINES'] = self.data.get('defines', None) + context['PARAMS'] = self.data.get('params', None) if 'hooks' in self.data: - for stage in self.data['hooks']: - context[stage.upper()] = '\n'.join(self.data['hooks'][stage]) + context['PRECFG'] = self.data['hooks'].get('precfg', None) + context['POSTCFG'] = self.data['hooks'].get('postcfg', None) + context['PRESYN'] = self.data['hooks'].get('presyn', None) + context['POSTSYN'] = self.data['hooks'].get('postsyn', None) + context['PREPAR'] = self.data['hooks'].get('prepar', None) + context['POSTPAR'] = self.data['hooks'].get('postpar', None) + context['PRESBIT'] = self.data['hooks'].get('prebit', None) + context['POSTBIT'] = self.data['hooks'].get('postbit', None) self._create_file('libero', 'tcl', context) return 'libero SCRIPT:libero.tcl' @@ -118,10 +111,10 @@ def get_info(part): elif len(aux) == 3: device = aux[0] if len(aux[1]) < len(aux[2]): - speed = aux[1] + speed = f'-{aux[1]}' package = aux[2] else: - speed = aux[2] + speed = f'-{aux[2]}' package = aux[1] else: raise ValueError( diff --git a/pyfpga/templates/libero.jinja b/pyfpga/templates/libero.jinja index cc8af1e9..6eb0e090 100644 --- a/pyfpga/templates/libero.jinja +++ b/pyfpga/templates/libero.jinja @@ -4,41 +4,44 @@ # SPDX-License-Identifier: GPL-3.0-or-later # -#proc fpga_include {PATH} { -# lappend INCLUDED $PATH -# # Verilog Included Files are ALSO added -# # They must be specified after set_root (see fpga_top) -# foreach FILE [glob -nocomplain $PATH/*.vh] { -# create_links -hdl_source $FILE -# } -# build_design_hierarchy -#} - #--[ Project configuration ]--------------------------------------------------- {% if CFG %} if { [ file exists {{ PROJECT }} ] } { file delete -force -- {{ PROJECT }} } -new_project -name {{ PROJECT }} -location . -hdl {VHDL} -family {SmartFusion2} +new_project -name {{ PROJECT }} -location {libero} -hdl {VERILOG} -family {SmartFusion2} set_device -family {{ FAMILY }} -die {{ DEVICE }} -package {{ PACKAGE }} -speed {{ SPEED }} {{ PRECFG }} +{% if INCLUDES %}set_global_include_path_order -paths "{{ INCLUDES | join(' ') }}"{% endif %} + {{ FILES }} build_design_hierarchy -{% if TOP %} -set_root {{ TOP }} -{% endif %} +{% if TOP %}set_root {{ TOP }}{% endif %} {% if INCLUDES or DEFINES or PARAMS %} configure_tool -name {SYNTHESIZE} -params {SYNPLIFY_OPTIONS: -{% if INCLUDES %} set_option -include_path "{{ INCLUDES }}"{% endif %} -{% if DEFINES %} set_option -hdl_define "{{ DEFINES }}"{% endif %} -{% if PARAMS %} set_option -hdl_param "{{ PARAMS }}"{% endif %} +{% if INCLUDES %} +{% for INCLUDE in INCLUDES %} + set_option -include_path "{{ INCLUDE }}" +{% endfor %} +{% endif %} +{% if DEFINES %} +{% for KEY, VALUE in DEFINES.items() %} + set_option -hdl_define -set {{ KEY }}={{ VALUE }} +{% endfor %} +{% endif %} +{% if PARAMS %} +{% for KEY, VALUE in PARAMS.items() %} + set_option -hdl_param -set {{ KEY }}={{ VALUE }} +{% endfor %} +{% endif %} } {% endif %} + # Constraints # PDC is only used for PLACEROUTE. # SDC is used by ALL (SYNTHESIZE, PLACEROUTE and VERIFYTIMING). From 0c2ecb35fa33eae06155bc345b57bc9f84257789 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Thu, 4 Jul 2024 18:25:51 -0300 Subject: [PATCH 144/254] Modify how to generate an intentional error --- examples/sources/slog/top.sv | 32 ++++++++++++++++++++++++++------ examples/sources/vhdl/top.vhdl | 19 ++++++++++++++++--- examples/sources/vlog/top.v | 32 ++++++++++++++++++++++++++------ 3 files changed, 68 insertions(+), 15 deletions(-) diff --git a/examples/sources/slog/top.sv b/examples/sources/slog/top.sv index cfc700e3..f36d5ee0 100644 --- a/examples/sources/slog/top.sv +++ b/examples/sources/slog/top.sv @@ -12,24 +12,44 @@ module Top #( Blink #(.FREQ (FREQ), .SECS (SECS)) dut (.clk_i (clk_i), .led_o (led_o)); `ifndef INCLUDE1 - Top Top (.clk_i (clk_i), .led_o (led_o)); + reg led; + always @(posedge clk_i) led <= 1'b0; + always @(posedge clk_i) led <= 1'b1; + assign led_o = led; + initial begin $stop; end `endif `ifndef INCLUDE2 - Top Top (.clk_i (clk_i), .led_o (led_o)); + reg led; + always @(posedge clk_i) led <= 1'b0; + always @(posedge clk_i) led <= 1'b1; + assign led_o = led; + initial begin $stop; end `endif `ifndef DEFINE1 - Top Top (.clk_i (clk_i), .led_o (led_o)); + reg led; + always @(posedge clk_i) led <= 1'b0; + always @(posedge clk_i) led <= 1'b1; + assign led_o = led; + initial begin $stop; end `endif `ifndef DEFINE2 - Top Top (.clk_i (clk_i), .led_o (led_o)); + reg led; + always @(posedge clk_i) led <= 1'b0; + always @(posedge clk_i) led <= 1'b1; + assign led_o = led; + initial begin $stop; end `endif generate - if (!FREQ || !SECS) begin: gen_error - Top Top (.clk_i (clk_i), .led_o (led_o)); + if (!FREQ || !SECS) begin + reg led; + always @(posedge clk_i) led <= 1'b0; + always @(posedge clk_i) led <= 1'b1; + assign led_o = led; + initial begin $stop; end end endgenerate diff --git a/examples/sources/vhdl/top.vhdl b/examples/sources/vhdl/top.vhdl index c6537185..c89ad50e 100644 --- a/examples/sources/vhdl/top.vhdl +++ b/examples/sources/vhdl/top.vhdl @@ -15,15 +15,28 @@ entity Top is end entity Top; architecture ARCH of Top is + signal led : std_logic; begin blink_i: Blink generic map (FREQ => FREQ, SECS => SECS) port map (clk_i => clk_i, led_o => led_o); - gen_error : if FREQ=0 or SECS=0 generate - top_i: entity work.Top - port map (clk_i => clk_i, led_o => led_o); + gen_error: if (FREQ=0 or SECS=0) generate + begin + process(clk_i) + begin + if rising_edge(clk_i) then + led <= '0'; + end if; + end process; + process(clk_i) + begin + if rising_edge(clk_i) then + led <= '1'; + end if; + end process; + led_o <= led; end generate gen_error; end architecture ARCH; diff --git a/examples/sources/vlog/top.v b/examples/sources/vlog/top.v index f47433ba..5d3b9562 100644 --- a/examples/sources/vlog/top.v +++ b/examples/sources/vlog/top.v @@ -12,24 +12,44 @@ module Top #( Blink #(.FREQ (FREQ), .SECS (SECS)) dut (.clk_i (clk_i), .led_o (led_o)); `ifndef INCLUDE1 - Top Top (.clk_i (clk_i), .led_o (led_o)); + reg led; + always @(posedge clk_i) led <= 1'b0; + always @(posedge clk_i) led <= 1'b1; + assign led_o = led; + initial begin $stop; end `endif `ifndef INCLUDE2 - Top Top (.clk_i (clk_i), .led_o (led_o)); + reg led; + always @(posedge clk_i) led <= 1'b0; + always @(posedge clk_i) led <= 1'b1; + assign led_o = led; + initial begin $stop; end `endif `ifndef DEFINE1 - Top Top (.clk_i (clk_i), .led_o (led_o)); + reg led; + always @(posedge clk_i) led <= 1'b0; + always @(posedge clk_i) led <= 1'b1; + assign led_o = led; + initial begin $stop; end `endif `ifndef DEFINE2 - Top Top (.clk_i (clk_i), .led_o (led_o)); + reg led; + always @(posedge clk_i) led <= 1'b0; + always @(posedge clk_i) led <= 1'b1; + assign led_o = led; + initial begin $stop; end `endif generate - if (!FREQ || !SECS) begin: gen_error - Top Top (.clk_i (clk_i), .led_o (led_o)); + if (!FREQ || !SECS) begin + reg led; + always @(posedge clk_i) led <= 1'b0; + always @(posedge clk_i) led <= 1'b1; + assign led_o = led; + initial begin $stop; end end endgenerate From 8b93f66fea27ed7156f49a277a8ef4910ffcf84e Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Thu, 4 Jul 2024 18:28:20 -0300 Subject: [PATCH 145/254] Modified to be Libero compatible --- examples/projects/libero.py | 1 + tests/projects/support.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/examples/projects/libero.py b/examples/projects/libero.py index 10d6b36d..4f4360d7 100644 --- a/examples/projects/libero.py +++ b/examples/projects/libero.py @@ -28,6 +28,7 @@ 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') diff --git a/tests/projects/support.py b/tests/projects/support.py index 12cb13c9..d0f4827e 100644 --- a/tests/projects/support.py +++ b/tests/projects/support.py @@ -105,6 +105,7 @@ print('INFO: checking VHDL Generics') prj = tools[args.tool]() prj.add_vhdl('../../examples/sources/vhdl/*.vhdl', 'blink_lib') + prj.add_vhdl('../../examples/sources/vhdl/top.vhdl') prj.set_top('Top') prj.make(last='syn') sys.exit('ERROR: something does not work as expected') @@ -116,6 +117,7 @@ print('* VHDL Support') prj = tools[args.tool]() prj.add_vhdl('../../examples/sources/vhdl/*.vhdl', 'blink_lib') + prj.add_vhdl('../../examples/sources/vhdl/top.vhdl') prj.set_top('Top') prj.add_param('FREQ', '1') prj.add_param('SECS', '1') From 211bbb7495fa392716a4308344c84e8e0c92533d Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Fri, 5 Jul 2024 00:01:53 -0300 Subject: [PATCH 146/254] Finished support for Libero --- pyfpga/libero.py | 25 ++-------------- pyfpga/templates/libero.jinja | 55 ++++++++++------------------------- 2 files changed, 19 insertions(+), 61 deletions(-) diff --git a/pyfpga/libero.py b/pyfpga/libero.py index a6aa6adf..b9a76d60 100644 --- a/pyfpga/libero.py +++ b/pyfpga/libero.py @@ -31,28 +31,9 @@ def _make_prepare(self, steps): } for step in steps: context[step] = 1 - files = [] - if 'files' in self.data: - for file in self.data['files']: - if 'lib' in self.data['files'][file]: - lib = self.data['files'][file]['lib'] - files.append( - f'create_links -library {lib} -hdl_source {file}' - ) - else: - files.append(f'create_links -hdl_source {file}') - if 'constraints' in self.data: - constraints = [] - for file in self.data['constraints']: - if file.suffix == '.sdc': - constraints.append(f'create_links -sdc {file}') - else: - constraints.append(f'create_links -io_pdc {file}') - if constraints: - context['CONSTRAINTS'] = " ".join(constraints) - if files: - context['FILES'] = '\n'.join(files) - context['INCLUDES'] = self.data.get('includes', None) # ';'.join(includes) + context['FILES'] = self.data.get('files', None) + context['CONSTRAINTS'] = self.data.get('constraints', None) + context['INCLUDES'] = self.data.get('includes', None) context['TOP'] = self.data.get('top', None) context['DEFINES'] = self.data.get('defines', None) context['PARAMS'] = self.data.get('params', None) diff --git a/pyfpga/templates/libero.jinja b/pyfpga/templates/libero.jinja index 6eb0e090..36db875a 100644 --- a/pyfpga/templates/libero.jinja +++ b/pyfpga/templates/libero.jinja @@ -9,18 +9,32 @@ {% if CFG %} if { [ file exists {{ PROJECT }} ] } { file delete -force -- {{ PROJECT }} } new_project -name {{ PROJECT }} -location {libero} -hdl {VERILOG} -family {SmartFusion2} - set_device -family {{ FAMILY }} -die {{ DEVICE }} -package {{ PACKAGE }} -speed {{ SPEED }} {{ PRECFG }} {% if INCLUDES %}set_global_include_path_order -paths "{{ INCLUDES | join(' ') }}"{% endif %} -{{ FILES }} +{% for name, attr in FILES.items() %} +create_links -hdl_source {{ name }}{% if 'lib' in attr %} -library {{ attr.lib }}{% endif %} +{% endfor %} +{% for name, attr in CONSTRAINTS.items() %}{% set name_str = name|string %} +create_links {% if name_str.endswith('.sdc') %}-sdc{% else %}-io_pdc{% endif %} {{ name_str }} +{% endfor %} + build_design_hierarchy {% if TOP %}set_root {{ TOP }}{% endif %} +{% set sdc_files = [] %}{% set pdc_files = [] %} +{% for name, attr in CONSTRAINTS.items() %}{% set name_str = name|string %} +{% if name_str.endswith('.sdc') %}{% set _ = sdc_files.append(name_str) %}{% endif %}{% set _ = pdc_files.append(name_str) %} +{% endfor %} + +{% if sdc_files %}organize_tool_files -tool {SYNTHESIZE} -file {{ sdc_files | join(' -file ') }} -module {{ TOP }} -input_type {constraint}{% endif %} +{% if pdc_files %}organize_tool_files -tool {PLACEROUTE} -file {{ pdc_files | join(' -file ') }} -module {{ TOP }} -input_type {constraint}{% endif %} +{% if sdc_files %}organize_tool_files -tool {VERIFYTIMING} -file {{ sdc_files | join(' -file ') }} -module {{ TOP }} -input_type {constraint}{% endif %} + {% if INCLUDES or DEFINES or PARAMS %} configure_tool -name {SYNTHESIZE} -params {SYNPLIFY_OPTIONS: {% if INCLUDES %} @@ -41,43 +55,6 @@ configure_tool -name {SYNTHESIZE} -params {SYNPLIFY_OPTIONS: } {% endif %} - -# Constraints -# PDC is only used for PLACEROUTE. -# SDC is used by ALL (SYNTHESIZE, PLACEROUTE and VERIFYTIMING). -#global LIBERO_PLACE_CONSTRAINTS -#global LIBERO_OTHER_CONSTRAINTS -#if { [info exists LIBERO_OTHER_CONSTRAINTS] } { -# set cmd "organize_tool_files -tool {SYNTHESIZE} " -# append cmd $LIBERO_OTHER_CONSTRAINTS -# append cmd "-module {{ TOP }} -input_type {constraint}" -# eval $cmd -# set cmd "organize_tool_files -tool {VERIFYTIMING} " -# append cmd $LIBERO_OTHER_CONSTRAINTS -# append cmd "-module {{ TOP }} -input_type {constraint}" -# eval $cmd -#} -#if { [info exists LIBERO_PLACE_CONSTRAINTS] } { -# set cmd "organize_tool_files -tool {PLACEROUTE} " -# append cmd $LIBERO_PLACE_CONSTRAINTS -# append cmd "-module {{ TOP }} -input_type {constraint}" -# eval $cmd -#} - -#organize_tool_files -tool {SYNTHESIZE} \ -# -file ../resources/constraints/maker-board/clk.sdc \ -# -module Blink -input_type {constraint} - -#organize_tool_files -tool {PLACEROUTE} \ -# -file ../resources/constraints/maker-board/clk.sdc \ -# -file ../resources/constraints/maker-board/clk.pdc \ -# -file ../resources/constraints/maker-board/led.pdc \ -# -module Blink -input_type {constraint} - -#organize_tool_files -tool {VERIFYTIMING} \ -# -file ../resources/constraints/maker-board/clk.sdc \ -# -module Blink -input_type {constraint} - {{ POSTCFG }} close_project From c64e9f648295ebc79386853c3d0a633e324a00ac Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Fri, 5 Jul 2024 00:07:53 -0300 Subject: [PATCH 147/254] Fix value of speed in a Libero test case --- tests/test_part.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_part.py b/tests/test_part.py index 7fd24ec7..86087946 100644 --- a/tests/test_part.py +++ b/tests/test_part.py @@ -18,7 +18,7 @@ def test_libero(): info = { 'family': 'SmartFusion2', 'device': 'm2s010', - 'speed': '1', + 'speed': '-1', 'package': 'tq144' } assert get_info_libero('m2s010-1-tq144') == info From 7ddd0e50ca019b25de1145b15b02972f63a9e5a5 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Fri, 5 Jul 2024 18:27:00 -0300 Subject: [PATCH 148/254] Updated tools versions --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 9630e0ce..cfc2e872 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # PyFPGA [![License](https://img.shields.io/badge/License-GPL--3.0-darkgreen?style=flat-square)](LICENSE) -![Vivado](https://img.shields.io/badge/Vivado-2019.2-blue.svg?style=flat-square) -![Quartus](https://img.shields.io/badge/Quartus--Prime-19.1-blue.svg?style=flat-square) -![Libero](https://img.shields.io/badge/Libero--Soc-12.2-blue.svg?style=flat-square) +![Vivado](https://img.shields.io/badge/Vivado-2022.1-blue.svg?style=flat-square) +![Quartus](https://img.shields.io/badge/Quartus--Prime-23.1-blue.svg?style=flat-square) +![Libero](https://img.shields.io/badge/Libero--Soc-2024.1-blue.svg?style=flat-square) ![ISE](https://img.shields.io/badge/ISE-14.7-blue.svg?style=flat-square) ![Openflow](https://img.shields.io/badge/Openflow-GHDL%20%7C%20Yosys%20%7C%20nextpnr%20%7C%20icestorm%20%7C%20prjtrellis-darkgreen.svg?style=flat-square) From 154da913850b538f8118e59754458683cfbadec4 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Fri, 5 Jul 2024 20:30:42 -0300 Subject: [PATCH 149/254] Add to check basic support based on the Blink module --- tests/projects/support.py | 70 ++++++++++++++++++++++++--------------- 1 file changed, 44 insertions(+), 26 deletions(-) diff --git a/tests/projects/support.py b/tests/projects/support.py index d0f4827e..13c9b868 100644 --- a/tests/projects/support.py +++ b/tests/projects/support.py @@ -27,8 +27,26 @@ print(f'INFO: the Tool Under Test is {args.tool}') +print('INFO: checking basic Verilog Support') +prj = tools[args.tool]() +prj.add_vlog('../../examples/sources/vlog/blink.v') +prj.set_top('Blink') +prj.make(last='syn') + +print('INFO: checking advanced Verilog Support') +prj = tools[args.tool]() +prj.add_vlog('../../examples/sources/vlog/*.v') +prj.set_top('Top') +prj.add_include('../../examples/sources/vlog/include1') +prj.add_include('../../examples/sources/vlog/include2') +prj.add_define('DEFINE1', '1') +prj.add_define('DEFINE2', '1') +prj.add_param('FREQ', '1') +prj.add_param('SECS', '1') +prj.make(last='syn') + try: - print('INFO: checking Verilog Includes') + print('INFO: checking Verilog Includes Support') prj = tools[args.tool]() prj.add_vlog('../../examples/sources/vlog/*.v') prj.set_top('Top') @@ -44,7 +62,7 @@ pass try: - print('INFO: checking Verilog Defines') + print('INFO: checking Verilog Defines Support') prj = tools[args.tool]() prj.add_vlog('../../examples/sources/vlog/*.v') prj.set_top('Top') @@ -60,7 +78,7 @@ pass try: - print('INFO: checking Verilog Parameters') + print('INFO: checking Verilog Parameters Support') prj = tools[args.tool]() prj.add_vlog('../../examples/sources/vlog/*.v') prj.set_top('Top') @@ -75,20 +93,14 @@ except Exception: pass -print('INFO: checking Verilog Support') -prj = tools[args.tool]() -prj.add_vlog('../../examples/sources/vlog/*.v') -prj.set_top('Top') -prj.add_include('../../examples/sources/vlog/include1') -prj.add_include('../../examples/sources/vlog/include2') -prj.add_define('DEFINE1', '1') -prj.add_define('DEFINE2', '1') -prj.add_param('FREQ', '1') -prj.add_param('SECS', '1') -prj.make(last='syn') - if args.tool not in ['ise', 'openflow']: - print('INFO: checking System Verilog Support') + print('INFO: checking basic System Verilog Support') + prj = tools[args.tool]() + prj.add_slog('../../examples/sources/slog/blink.sv') + prj.set_top('Blink') + prj.make(last='syn') + + print('INFO: checking advanced System Verilog Support') prj = tools[args.tool]() prj.add_slog('../../examples/sources/slog/*.sv') prj.set_top('Top') @@ -101,6 +113,21 @@ prj.make(last='syn') if args.tool not in ['openflow']: + print('* INFO: checking basic VHDL Support') + prj = tools[args.tool]() + prj.add_vhdl('../../examples/sources/vhdl/blink.vhdl') + prj.set_top('Blink') + prj.make(last='syn') + + print('* INFO: checking advanced VHDL Support') + prj = tools[args.tool]() + prj.add_vhdl('../../examples/sources/vhdl/*.vhdl', 'blink_lib') + prj.add_vhdl('../../examples/sources/vhdl/top.vhdl') + prj.set_top('Top') + prj.add_param('FREQ', '1') + prj.add_param('SECS', '1') + prj.make(last='syn') + try: print('INFO: checking VHDL Generics') prj = tools[args.tool]() @@ -114,13 +141,4 @@ except Exception: pass - print('* VHDL Support') - prj = tools[args.tool]() - prj.add_vhdl('../../examples/sources/vhdl/*.vhdl', 'blink_lib') - prj.add_vhdl('../../examples/sources/vhdl/top.vhdl') - prj.set_top('Top') - prj.add_param('FREQ', '1') - prj.add_param('SECS', '1') - prj.make(last='syn') - -print(f'INFO: Class Under Test works as expected') +print(f'INFO: Tool Under Test works as expected') From c00c278073cbac9cefc07da2622b1911a9552f7d Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Fri, 5 Jul 2024 20:33:30 -0300 Subject: [PATCH 150/254] Libero support slightly improved --- pyfpga/libero.py | 2 +- pyfpga/templates/libero.jinja | 29 +++++++++++++---------------- 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/pyfpga/libero.py b/pyfpga/libero.py index b9a76d60..813eb3da 100644 --- a/pyfpga/libero.py +++ b/pyfpga/libero.py @@ -31,9 +31,9 @@ def _make_prepare(self, steps): } for step in steps: context[step] = 1 + context['INCLUDES'] = self.data.get('includes', None) context['FILES'] = self.data.get('files', None) context['CONSTRAINTS'] = self.data.get('constraints', None) - context['INCLUDES'] = self.data.get('includes', None) context['TOP'] = self.data.get('top', None) context['DEFINES'] = self.data.get('defines', None) context['PARAMS'] = self.data.get('params', None) diff --git a/pyfpga/templates/libero.jinja b/pyfpga/templates/libero.jinja index 36db875a..a0f6259d 100644 --- a/pyfpga/templates/libero.jinja +++ b/pyfpga/templates/libero.jinja @@ -15,21 +15,24 @@ set_device -family {{ FAMILY }} -die {{ DEVICE }} -package {{ PACKAGE }} -speed {% if INCLUDES %}set_global_include_path_order -paths "{{ INCLUDES | join(' ') }}"{% endif %} -{% for name, attr in FILES.items() %} +{% if FILES %}{% for name, attr in FILES.items() %} create_links -hdl_source {{ name }}{% if 'lib' in attr %} -library {{ attr.lib }}{% endif %} -{% endfor %} -{% for name, attr in CONSTRAINTS.items() %}{% set name_str = name|string %} +{% endfor %}{% endif %} + +{% if CONSTRAINTS %}{% for name, attr in CONSTRAINTS.items() %}{% set name_str = name|string %} create_links {% if name_str.endswith('.sdc') %}-sdc{% else %}-io_pdc{% endif %} {{ name_str }} -{% endfor %} +{% endfor %}{% endif %} build_design_hierarchy {% if TOP %}set_root {{ TOP }}{% endif %} +{% if CONSTRAINTS %} {% set sdc_files = [] %}{% set pdc_files = [] %} {% for name, attr in CONSTRAINTS.items() %}{% set name_str = name|string %} {% if name_str.endswith('.sdc') %}{% set _ = sdc_files.append(name_str) %}{% endif %}{% set _ = pdc_files.append(name_str) %} {% endfor %} +{% endif %} {% if sdc_files %}organize_tool_files -tool {SYNTHESIZE} -file {{ sdc_files | join(' -file ') }} -module {{ TOP }} -input_type {constraint}{% endif %} {% if pdc_files %}organize_tool_files -tool {PLACEROUTE} -file {{ pdc_files | join(' -file ') }} -module {{ TOP }} -input_type {constraint}{% endif %} @@ -37,21 +40,15 @@ build_design_hierarchy {% if INCLUDES or DEFINES or PARAMS %} configure_tool -name {SYNTHESIZE} -params {SYNPLIFY_OPTIONS: -{% if INCLUDES %} -{% for INCLUDE in INCLUDES %} +{% if INCLUDES %}{% for INCLUDE in INCLUDES %} set_option -include_path "{{ INCLUDE }}" -{% endfor %} -{% endif %} -{% if DEFINES %} -{% for KEY, VALUE in DEFINES.items() %} +{% endfor %}{% endif %} +{% if DEFINES %}{% for KEY, VALUE in DEFINES.items() %} set_option -hdl_define -set {{ KEY }}={{ VALUE }} -{% endfor %} -{% endif %} -{% if PARAMS %} -{% for KEY, VALUE in PARAMS.items() %} +{% endfor %}{% endif %} +{% if PARAMS %}{% for KEY, VALUE in PARAMS.items() %} set_option -hdl_param -set {{ KEY }}={{ VALUE }} -{% endfor %} -{% endif %} +{% endfor %}{% endif %} } {% endif %} From 7963eb1c36bd518b50ec9b175b5e63d114246bff Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Fri, 5 Jul 2024 20:34:09 -0300 Subject: [PATCH 151/254] Quartus support improved/simplified --- pyfpga/quartus.py | 57 +++++++++------------------------- pyfpga/templates/quartus.jinja | 41 +++++++++++++++--------- 2 files changed, 40 insertions(+), 58 deletions(-) diff --git a/pyfpga/quartus.py b/pyfpga/quartus.py index 417a497a..10a5927e 100644 --- a/pyfpga/quartus.py +++ b/pyfpga/quartus.py @@ -25,50 +25,21 @@ def _make_prepare(self, steps): } for step in steps: context[step] = 1 - if 'includes' in self.data: - includes = [] - for include in self.data['includes']: - includes.append(str(include)) - context['INCLUDES'] = ' '.join(includes) - files = [] - if 'files' in self.data: - types = { - 'slog': 'SYSTEMVERILOG_FILE', - 'vhdl': 'VHDL_FILE', - 'vlog': 'VERILOG_FILE' - } - for file in self.data['files']: - hdl = self.data['files'][file]['hdl'] - lib = self.data['files'][file].get('lib', None) - typ = types[hdl] - line = f'set_global_assignment -name {typ} {file}' - if lib: - line += f' -library {lib}' - files.append(line) - if 'constraints' in self.data: - for file in self.data['constraints']: - if file.suffix == '.sdc': - line = f'set_global_assignment -name SDC_FILE {file}' - else: - line = f'source {file}' - files.append(line) - if files: - context['FILES'] = '\n'.join(files) - if 'top' in self.data: - context['TOP'] = self.data['top'] - if 'defines' in self.data: - defines = [] - for key, value in self.data['defines'].items(): - defines.append(f'{key} {value}') - context['DEFINES'] = ' '.join(defines) - if 'params' in self.data: - params = [] - for key, value in self.data['params'].items(): - params.append(f'{key} {value}') - context['PARAMS'] = ' '.join(params) + context['INCLUDES'] = self.data.get('includes', None) + context['FILES'] = self.data.get('files', None) + context['CONSTRAINTS'] = self.data.get('constraints', None) + context['TOP'] = self.data.get('top', None) + context['DEFINES'] = self.data.get('defines', None) + context['PARAMS'] = self.data.get('params', None) if 'hooks' in self.data: - for stage in self.data['hooks']: - context[stage.upper()] = '\n'.join(self.data['hooks'][stage]) + context['PRECFG'] = self.data['hooks'].get('precfg', None) + context['POSTCFG'] = self.data['hooks'].get('postcfg', None) + context['PRESYN'] = self.data['hooks'].get('presyn', None) + context['POSTSYN'] = self.data['hooks'].get('postsyn', None) + context['PREPAR'] = self.data['hooks'].get('prepar', None) + context['POSTPAR'] = self.data['hooks'].get('postpar', None) + context['PRESBIT'] = self.data['hooks'].get('prebit', None) + context['POSTBIT'] = self.data['hooks'].get('postbit', None) self._create_file('quartus', 'tcl', context) return 'quartus_sh --script quartus.tcl' diff --git a/pyfpga/templates/quartus.jinja b/pyfpga/templates/quartus.jinja index 4b98174a..3f97e363 100644 --- a/pyfpga/templates/quartus.jinja +++ b/pyfpga/templates/quartus.jinja @@ -13,26 +13,37 @@ set_global_assignment -name DEVICE {{ PART }} {{ PRECFG }} -{{ FILES }} - -{% if TOP %} -set_global_assignment -name TOP_LEVEL_ENTITY {{ TOP }} +{% if FILES %}{% for NAME, ATTR in FILES.items() %} +{% if ATTR.hdl == "vhdl" %} +set_global_assignment -name VHDL_FILE {{ NAME }} {% if 'lib' in ATTR %} -library {{ ATTR.lib }}{% endif %} +{% elif ATTR.hdl == "vlog" %} +set_global_assignment -name VERILOG_FILE {{ NAME }} +{% elif ATTR.hdl == "slog" %} +set_global_assignment -name SYSTEMVERILOG_FILE {{ NAME }} {% endif %} +{% endfor %}{% endif %} -{% if DEFINES %} -set defines [dict create {{ DEFINES }}] -foreach {key value} $defines {set_global_assignment -name VERILOG_MACRO "$key=$value"} +{% if CONSTRAINTS %}{% for name, attr in CONSTRAINTS.items() %}{% set name_str = name|string %} +{% if name_str.endswith('.sdc') %} +set_global_assignment -name SDC_FILE {{ name_str }} +{% else %} +source {{ name_str }} {% endif %} +{% endfor %}{% endif %} -{% if INCLUDES %} -set includes { {{ INCLUDES}} } -foreach value $includes {set_global_assignment -name SEARCH_PATH $value} -{% endif %} +{% if TOP %}set_global_assignment -name TOP_LEVEL_ENTITY {{ TOP }}{% endif %} -{% if PARAMS %} -set params [dict create {{ PARAMS }}] -foreach {key value} $params {set_parameter -name $key $value} -{% endif %} +{% if INCLUDES %}{% for INCLUDE in INCLUDES %} +set_global_assignment -name SEARCH_PATH {{ INCLUDE }} +{% endfor %}{% endif %} + +{% if DEFINES %}{% for KEY, VALUE in DEFINES.items() %} +set_global_assignment -name VERILOG_MACRO {{ KEY }}={{ VALUE }} +{% endfor %}{% endif %} + +{% if PARAMS %}{% for KEY, VALUE in PARAMS.items() %} +set_parameter -name {{ KEY }} {{ VALUE }} +{% endfor %}{% endif %} {{ POSTCFG }} From 250409906a0c8f3466097e64b1f55aff57097812 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Fri, 5 Jul 2024 22:00:58 -0300 Subject: [PATCH 152/254] Applied a few format fixes --- pyfpga/templates/libero.jinja | 8 ++++---- pyfpga/templates/quartus.jinja | 22 +++++++++++----------- pyfpga/templates/vivado.jinja | 2 +- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/pyfpga/templates/libero.jinja b/pyfpga/templates/libero.jinja index a0f6259d..3d961e51 100644 --- a/pyfpga/templates/libero.jinja +++ b/pyfpga/templates/libero.jinja @@ -43,11 +43,11 @@ configure_tool -name {SYNTHESIZE} -params {SYNPLIFY_OPTIONS: {% if INCLUDES %}{% for INCLUDE in INCLUDES %} set_option -include_path "{{ INCLUDE }}" {% endfor %}{% endif %} -{% if DEFINES %}{% for KEY, VALUE in DEFINES.items() %} - set_option -hdl_define -set {{ KEY }}={{ VALUE }} +{% if DEFINES %}{% for key, value in DEFINES.items() %} + set_option -hdl_define -set {{ key }}={{ value }} {% endfor %}{% endif %} -{% if PARAMS %}{% for KEY, VALUE in PARAMS.items() %} - set_option -hdl_param -set {{ KEY }}={{ VALUE }} +{% if PARAMS %}{% for key, value in PARAMS.items() %} + set_option -hdl_param -set {{ key }}={{ value }} {% endfor %}{% endif %} } {% endif %} diff --git a/pyfpga/templates/quartus.jinja b/pyfpga/templates/quartus.jinja index 3f97e363..1caa2108 100644 --- a/pyfpga/templates/quartus.jinja +++ b/pyfpga/templates/quartus.jinja @@ -13,13 +13,13 @@ set_global_assignment -name DEVICE {{ PART }} {{ PRECFG }} -{% if FILES %}{% for NAME, ATTR in FILES.items() %} -{% if ATTR.hdl == "vhdl" %} -set_global_assignment -name VHDL_FILE {{ NAME }} {% if 'lib' in ATTR %} -library {{ ATTR.lib }}{% endif %} -{% elif ATTR.hdl == "vlog" %} -set_global_assignment -name VERILOG_FILE {{ NAME }} -{% elif ATTR.hdl == "slog" %} -set_global_assignment -name SYSTEMVERILOG_FILE {{ NAME }} +{% if FILES %}{% for name, attr in FILES.items() %} +{% if attr.hdl == "vhdl" %} +set_global_assignment -name VHDL_FILE {{ name }} {% if 'lib' in attr %} -library {{ attr.lib }}{% endif %} +{% elif attr.hdl == "vlog" %} +set_global_assignment -name VERILOG_FILE {{ name }} +{% elif attr.hdl == "slog" %} +set_global_assignment -name SYSTEMVERILOG_FILE {{ name }} {% endif %} {% endfor %}{% endif %} @@ -37,12 +37,12 @@ source {{ name_str }} set_global_assignment -name SEARCH_PATH {{ INCLUDE }} {% endfor %}{% endif %} -{% if DEFINES %}{% for KEY, VALUE in DEFINES.items() %} -set_global_assignment -name VERILOG_MACRO {{ KEY }}={{ VALUE }} +{% if DEFINES %}{% for key, value in DEFINES.items() %} +set_global_assignment -name VERILOG_MACRO {{ key }}={{ value }} {% endfor %}{% endif %} -{% if PARAMS %}{% for KEY, VALUE in PARAMS.items() %} -set_parameter -name {{ KEY }} {{ VALUE }} +{% if PARAMS %}{% for key, value in PARAMS.items() %} +set_parameter -name {{ key }} {{ value }} {% endfor %}{% endif %} {{ POSTCFG }} diff --git a/pyfpga/templates/vivado.jinja b/pyfpga/templates/vivado.jinja index 8f0cbb8e..ffef4aa8 100644 --- a/pyfpga/templates/vivado.jinja +++ b/pyfpga/templates/vivado.jinja @@ -4,7 +4,7 @@ # SPDX-License-Identifier: GPL-3.0-or-later # -#--[ Project configuration ]-------------------------------------------------- +#--[ Project configuration ]--------------------------------------------------- {% if CFG %} create_project -force {{ PROJECT }} From cae0d326fe1c903787767879970704aca96fd8eb Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Fri, 5 Jul 2024 22:01:48 -0300 Subject: [PATCH 153/254] ISE support improved/simplified --- pyfpga/ise.py | 52 ++++++++++---------------------------- pyfpga/templates/ise.jinja | 30 ++++++++++++---------- 2 files changed, 31 insertions(+), 51 deletions(-) diff --git a/pyfpga/ise.py b/pyfpga/ise.py index a964af7c..9e173f2f 100644 --- a/pyfpga/ise.py +++ b/pyfpga/ise.py @@ -31,45 +31,21 @@ def _make_prepare(self, steps): } for step in steps: context[step] = 1 - if 'includes' in self.data: - includes = [] - for include in self.data['includes']: - includes.append(str(include)) - context['INCLUDES'] = '|'.join(includes) - files = [] - if 'files' in self.data: - for file in self.data['files']: - if 'lib' in self.data['files'][file]: - lib = self.data['files'][file]['lib'] - files.append(f'lib_vhdl new {lib}') - files.append(f'xfile add {file} -lib_vhdl {lib}') - else: - files.append(f'xfile add {file}') - if 'constraints' in self.data: - constraints = [] - for file in self.data['constraints']: - files.append(f'xfile add {file}') - if file.suffix == '.xcf': - constraints.append(str(file)) - if constraints: - context['CONSTRAINTS'] = " ".join(constraints) - if files: - context['FILES'] = '\n'.join(files) - if 'top' in self.data: - context['TOP'] = self.data['top'] - if 'defines' in self.data: - defines = [] - for key, value in self.data['defines'].items(): - defines.append(f'{key}={value}') - context['DEFINES'] = ' | '.join(defines) - if 'params' in self.data: - params = [] - for key, value in self.data['params'].items(): - params.append(f'{key}={value}') - context['PARAMS'] = ' '.join(params) + context['INCLUDES'] = self.data.get('includes', None) + context['FILES'] = self.data.get('files', None) + context['CONSTRAINTS'] = self.data.get('constraints', None) + context['TOP'] = self.data.get('top', None) + context['DEFINES'] = self.data.get('defines', None) + context['PARAMS'] = self.data.get('params', None) if 'hooks' in self.data: - for stage in self.data['hooks']: - context[stage.upper()] = '\n'.join(self.data['hooks'][stage]) + context['PRECFG'] = self.data['hooks'].get('precfg', None) + context['POSTCFG'] = self.data['hooks'].get('postcfg', None) + context['PRESYN'] = self.data['hooks'].get('presyn', None) + context['POSTSYN'] = self.data['hooks'].get('postsyn', None) + context['PREPAR'] = self.data['hooks'].get('prepar', None) + context['POSTPAR'] = self.data['hooks'].get('postpar', None) + context['PRESBIT'] = self.data['hooks'].get('prebit', None) + context['POSTBIT'] = self.data['hooks'].get('postbit', None) self._create_file('ise', 'tcl', context) return 'xtclsh ise.tcl' diff --git a/pyfpga/templates/ise.jinja b/pyfpga/templates/ise.jinja index 701a028d..d72a26ae 100644 --- a/pyfpga/templates/ise.jinja +++ b/pyfpga/templates/ise.jinja @@ -4,7 +4,7 @@ # SPDX-License-Identifier: GPL-3.0-or-later # -#--[ Project configuration ]-------------------------------------------------- +#--[ Project configuration ]--------------------------------------------------- {% if CFG %} if { [ file exists {{ PROJECT }}.xise ] } { file delete {{ PROJECT }}.xise } @@ -16,26 +16,30 @@ project set speed -{{ SPEED }} {{ PRECFG }} -{{ FILES }} -{% if CONSTRAINTS %} -project set "Synthesis Constraints File" "{{ CONSTRAINTS }}" -process "Synthesize - XST" -{% endif %} +{% if FILES %}{% for name, attr in FILES.items() %} +{% if 'lib' in attr %}lib_vhdl new {{ attr.lib }}{% endif %} +xfile add {{ name }}{% if 'lib' in attr %} -lib_vhdl {{ attr.lib }}{% endif %} +{% endfor %}{% endif %} -{% if TOP %} -project set top {{ TOP }} +{% if CONSTRAINTS %}{% for name, attr in CONSTRAINTS.items() %}{% set name_str = name|string %} +xfile add {{ name_str }} +{% if name_str.endswith('.xcf') %} +project set "Synthesis Constraints File" "{{ name_str }}" -process "Synthesize - XST" {% endif %} +{% endfor %}{% endif %} -{% if DEFINES %} -project set "Verilog Macros" "{{ DEFINES }}" -process "Synthesize - XST" -{% endif %} +{% if TOP %}project set top {{ TOP }}{% endif %} {% if INCLUDES %} -project set "Verilog Include Directories" "{{ INCLUDES }}" -process "Synthesize - XST" -# [join $INCLUDED "|"] +project set "Verilog Include Directories" "{{ INCLUDES | join('|') }}" -process "Synthesize - XST" +{% endif %} + +{% if DEFINES %} +project set "Verilog Macros" "{{ DEFINES.items() | map('join', '=') | join(' | ') }}" -process "Synthesize - XST" {% endif %} {% if PARAMS %} -project set "Generics, Parameters" "{{ PARAMS }}" -process "Synthesize - XST" +project set "Generics, Parameters" "{{ PARAMS.items() | map('join', '=') | join(' ') }}" -process "Synthesize - XST" {% endif %} {{ POSTCFG }} From d0226be81eaaaa949da142d7ff0f28a1351fd3ac Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sat, 6 Jul 2024 00:09:38 -0300 Subject: [PATCH 154/254] Fix intentional error generation for Vivado --- examples/sources/slog/top.sv | 10 +++++----- examples/sources/vlog/top.v | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/examples/sources/slog/top.sv b/examples/sources/slog/top.sv index f36d5ee0..732ae368 100644 --- a/examples/sources/slog/top.sv +++ b/examples/sources/slog/top.sv @@ -16,7 +16,7 @@ module Top #( always @(posedge clk_i) led <= 1'b0; always @(posedge clk_i) led <= 1'b1; assign led_o = led; - initial begin $stop; end + initial begin $stop; $error("intentional"); end `endif `ifndef INCLUDE2 @@ -24,7 +24,7 @@ module Top #( always @(posedge clk_i) led <= 1'b0; always @(posedge clk_i) led <= 1'b1; assign led_o = led; - initial begin $stop; end + initial begin $stop; $error("intentional"); end `endif `ifndef DEFINE1 @@ -32,7 +32,7 @@ module Top #( always @(posedge clk_i) led <= 1'b0; always @(posedge clk_i) led <= 1'b1; assign led_o = led; - initial begin $stop; end + initial begin $stop; $error("intentional"); end `endif `ifndef DEFINE2 @@ -40,7 +40,7 @@ module Top #( always @(posedge clk_i) led <= 1'b0; always @(posedge clk_i) led <= 1'b1; assign led_o = led; - initial begin $stop; end + initial begin $stop; $error("intentional"); end `endif generate @@ -49,7 +49,7 @@ module Top #( always @(posedge clk_i) led <= 1'b0; always @(posedge clk_i) led <= 1'b1; assign led_o = led; - initial begin $stop; end + initial begin $stop; $error("intentional"); end end endgenerate diff --git a/examples/sources/vlog/top.v b/examples/sources/vlog/top.v index 5d3b9562..9a6d452b 100644 --- a/examples/sources/vlog/top.v +++ b/examples/sources/vlog/top.v @@ -16,7 +16,7 @@ module Top #( always @(posedge clk_i) led <= 1'b0; always @(posedge clk_i) led <= 1'b1; assign led_o = led; - initial begin $stop; end + initial begin $stop; $error("intentional"); end `endif `ifndef INCLUDE2 @@ -24,7 +24,7 @@ module Top #( always @(posedge clk_i) led <= 1'b0; always @(posedge clk_i) led <= 1'b1; assign led_o = led; - initial begin $stop; end + initial begin $stop; $error("intentional"); end `endif `ifndef DEFINE1 @@ -32,7 +32,7 @@ module Top #( always @(posedge clk_i) led <= 1'b0; always @(posedge clk_i) led <= 1'b1; assign led_o = led; - initial begin $stop; end + initial begin $stop; $error("intentional"); end `endif `ifndef DEFINE2 @@ -40,7 +40,7 @@ module Top #( always @(posedge clk_i) led <= 1'b0; always @(posedge clk_i) led <= 1'b1; assign led_o = led; - initial begin $stop; end + initial begin $stop; $error("intentional"); end `endif generate @@ -49,7 +49,7 @@ module Top #( always @(posedge clk_i) led <= 1'b0; always @(posedge clk_i) led <= 1'b1; assign led_o = led; - initial begin $stop; end + initial begin $stop; $error("intentional"); end end endgenerate From fbd52b16cf3050d6038a31e14cb89ed50058796e Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sat, 6 Jul 2024 00:10:05 -0300 Subject: [PATCH 155/254] Vivado support improved/simplified --- pyfpga/templates/vivado.jinja | 29 ++++++++++++------ pyfpga/vivado.py | 58 +++++++++-------------------------- 2 files changed, 34 insertions(+), 53 deletions(-) diff --git a/pyfpga/templates/vivado.jinja b/pyfpga/templates/vivado.jinja index ffef4aa8..6a40c459 100644 --- a/pyfpga/templates/vivado.jinja +++ b/pyfpga/templates/vivado.jinja @@ -14,22 +14,33 @@ set_property PART {{ PART }} [current_project] {{ PRECFG }} -{{ FILES }} - -{% if TOP %} -set_property TOP {{ TOP }} [current_fileset] +{% if FILES %}{% for name, attr in FILES.items() %} +add_file {{ name }} +{% if 'lib' in attr %}set_property library {{ attr.lib }} [get_files {{ name }}]{% endif %} +{% endfor %}{% endif %} + +{% if CONSTRAINTS %}{% for name, attr in CONSTRAINTS.items() %} +add_file -fileset constrs_1 {{ name }} +{% if attr == "syn" %} +set_property USED_IN_IMPLEMENTATION FALSE [get_files {{ name }}] +{% elif attr == "par" %} +set_property USED_IN_SYNTHESIS FALSE [get_files {{ name }}] {% endif %} +{% if loop.first %}set_property TARGET_CONSTRS_FILE {{ name }} [current_fileset -constrset]{% endif %} +{% endfor %}{% endif %} -{% if DEFINES %} -set_property VERILOG_DEFINE { {{ DEFINES}} } [current_fileset] -{% endif %} +{% if TOP %}set_property TOP {{ TOP }} [current_fileset]{% endif %} {% if INCLUDES %} -set_property INCLUDE_DIRS { {{ INCLUDES}} } [current_fileset] +set_property INCLUDE_DIRS { {{ INCLUDES | join(' ') }} } [current_fileset] +{% endif %} + +{% if DEFINES %} +set_property VERILOG_DEFINE { {{ DEFINES.items() | map('join', '=') | join(' ') }} } [current_fileset] {% endif %} {% if PARAMS %} -set_property GENERIC { {{ PARAMS }} } -objects [get_filesets sources_1] +set_property GENERIC { {{ PARAMS.items() | map('join', '=') | join(' ') }} } -objects [get_filesets sources_1] {% endif %} {{ POSTCFG }} diff --git a/pyfpga/vivado.py b/pyfpga/vivado.py index 1858ed82..c0a48864 100644 --- a/pyfpga/vivado.py +++ b/pyfpga/vivado.py @@ -24,51 +24,21 @@ def _make_prepare(self, steps): } for step in steps: context[step] = 1 - if 'includes' in self.data: - includes = [] - for include in self.data['includes']: - includes.append(str(include)) - context['INCLUDES'] = ' '.join(includes) - files = [] - if 'files' in self.data: - for file in self.data['files']: - files.append(f'add_file {file}') - for file in self.data['files']: - if 'lib' in self.data['files'][file]: - lib = self.data['files'][file]['lib'] - files.append( - f'set_property library {lib} [get_files {file}]' - ) - if 'constraints' in self.data: - for file in self.data['constraints']: - files.append(f'add_file -fileset constrs_1 {file}') - for file in self.data['constraints']: - if self.data['constraints'][file] == 'syn': - prop = 'USED_IN_IMPLEMENTATION FALSE' - if self.data['constraints'][file] == 'syn': - prop = 'USED_IN_SYNTHESIS FALSE' - if self.data['constraints'][file] != 'all': - files.append(f'set_property {prop} [get_files {file}]') - first = next(iter(self.data['constraints'])) - prop = f'TARGET_CONSTRS_FILE {first}' - files.append(f'set_property {prop} [current_fileset -constrset]') - if files: - context['FILES'] = '\n'.join(files) - if 'top' in self.data: - context['TOP'] = self.data['top'] - if 'defines' in self.data: - defines = [] - for key, value in self.data['defines'].items(): - defines.append(f'{key}={value}') - context['DEFINES'] = ' '.join(defines) - if 'params' in self.data: - params = [] - for key, value in self.data['params'].items(): - params.append(f'{key}={value}') - context['PARAMS'] = ' '.join(params) + context['INCLUDES'] = self.data.get('includes', None) + context['FILES'] = self.data.get('files', None) + context['CONSTRAINTS'] = self.data.get('constraints', None) + context['TOP'] = self.data.get('top', None) + context['DEFINES'] = self.data.get('defines', None) + context['PARAMS'] = self.data.get('params', None) if 'hooks' in self.data: - for stage in self.data['hooks']: - context[stage.upper()] = '\n'.join(self.data['hooks'][stage]) + context['PRECFG'] = self.data['hooks'].get('precfg', None) + context['POSTCFG'] = self.data['hooks'].get('postcfg', None) + context['PRESYN'] = self.data['hooks'].get('presyn', None) + context['POSTSYN'] = self.data['hooks'].get('postsyn', None) + context['PREPAR'] = self.data['hooks'].get('prepar', None) + context['POSTPAR'] = self.data['hooks'].get('postpar', None) + context['PRESBIT'] = self.data['hooks'].get('prebit', None) + context['POSTBIT'] = self.data['hooks'].get('postbit', None) self._create_file('vivado', 'tcl', context) return 'vivado -mode batch -notrace -quiet -source vivado.tcl' From 8d2a067f4bd8374b6e7e7fda54dac8cd866eb0d8 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sat, 6 Jul 2024 13:42:33 -0300 Subject: [PATCH 156/254] Openflow support improved/simplified --- pyfpga/openflow.py | 56 +++++++++------------------------ pyfpga/templates/openflow.jinja | 18 ++++++----- 2 files changed, 25 insertions(+), 49 deletions(-) diff --git a/pyfpga/openflow.py b/pyfpga/openflow.py index 51dcd84a..a2e4092d 100644 --- a/pyfpga/openflow.py +++ b/pyfpga/openflow.py @@ -28,49 +28,21 @@ def _make_prepare(self, steps): } for step in steps: context[step] = 1 - if 'includes' in self.data: - includes = [] - for include in self.data['includes']: - includes.append(f'-I{str(include)}') - context['INCLUDES'] = ' '.join(includes) - files = [] - if 'files' in self.data: - for file in self.data['files']: - files.append(f'read_verilog -defer {file}') - if files: - context['VLOGS'] = '\n'.join(files) -# for file in self.files['vhdl']: -# lib = '' -# if file[1] is not None: -# lib = f'--work={file[1]}' -# vhdls.append(f'{self.tools["ghdl"]} -a $FLAGS {lib} {file[0]}') -# for file in self.files['verilog']: -# if file[0].endswith('.sv'): -# verilogs.append(f'read_verilog -sv -defer {file[0]}') -# else: -# verilogs.append(f'read_verilog -defer {file[0]}') -# if len(vhdls) > 0: -# verilogs = [f'ghdl $FLAGS {self.top}'] - if 'constraints' in self.data: - constraints = [] - for constraint in self.data['constraints']: - constraints.append(str(constraint)) - context['CONSTRAINTS'] = ' '.join(constraints) - if 'top' in self.data: - context['TOP'] = self.data['top'] - if 'defines' in self.data: - defines = [] - for key, value in self.data['defines'].items(): - defines.append(f'-D{key}={value}') - context['DEFINES'] = ' '.join(defines) - if 'params' in self.data: - params = [] - for key, value in self.data['params'].items(): - params.append(f'-set {key} {value}') - context['PARAMS'] = ' '.join(params) + context['INCLUDES'] = self.data.get('includes', None) + context['FILES'] = self.data.get('files', None) + context['CONSTRAINTS'] = self.data.get('constraints', None) + context['TOP'] = self.data.get('top', None) + context['DEFINES'] = self.data.get('defines', None) + context['PARAMS'] = self.data.get('params', None) if 'hooks' in self.data: - for stage in self.data['hooks']: - context[stage.upper()] = '\n'.join(self.data['hooks'][stage]) + context['PRECFG'] = self.data['hooks'].get('precfg', None) + context['POSTCFG'] = self.data['hooks'].get('postcfg', None) + context['PRESYN'] = self.data['hooks'].get('presyn', None) + context['POSTSYN'] = self.data['hooks'].get('postsyn', None) + context['PREPAR'] = self.data['hooks'].get('prepar', None) + context['POSTPAR'] = self.data['hooks'].get('postpar', None) + context['PRESBIT'] = self.data['hooks'].get('prebit', None) + context['POSTBIT'] = self.data['hooks'].get('postbit', None) self._create_file('openflow', 'sh', context) return 'bash openflow.sh' diff --git a/pyfpga/templates/openflow.jinja b/pyfpga/templates/openflow.jinja index f9b3ba05..d29cbbc5 100644 --- a/pyfpga/templates/openflow.jinja +++ b/pyfpga/templates/openflow.jinja @@ -13,18 +13,22 @@ DOCKER="docker run --user $(id -u):$(id -g) --rm -v $HOME:$HOME -w $PWD" {% if SYN %} $DOCKER hdlc/ghdl:yosys /bin/bash -c " {{ PRESYN }} -{{ VHDLS }} yosys -Q -m ghdl -p ' {% if INCLUDES %} -verilog_defaults -add {{ INCLUDES }} +verilog_defaults -add{% for path in INCLUDES %} -I{{ path }}{% endfor %} {% endif %} {% if DEFINES %} -verilog_defines {{ DEFINES }} +verilog_defines{% for key, value in DEFINES.items() %} -D{{ key }}={{ value }}{% endfor %} {% endif %} -{{ VLOGS }} -{{ SLOGS }} +{% if FILES %}{% for name, attr in FILES.items() %} +{% if attr.hdl == "vlog" %} +read_verilog -defer {{ name }} +{% elif attr.hdl == "slog" %} +read_verilog -defer -sv {{ name }} +{% endif %} +{% endfor %}{% endif %} {% if PARAMS %} -chparam {{ PARAMS }} {{ TOP }} +chparam{% for key, value in PARAMS.items() %} -set {{ key }} {{ value }}{% endfor %} {% endif %} synth -top {{ TOP }} synth_{{ FAMILY }} -top {{ TOP }} -json {{ PROJECT }}.json @@ -54,7 +58,7 @@ synth_{{ FAMILY }} -top {{ TOP }} -json {{ PROJECT }}.json {% if PAR %} -CONSTRAINTS="{{ CONSTRAINTS }}" +CONSTRAINTS="{{ CONSTRAINTS | join(' ') }}" {% if FAMILY == 'ice40' %} if [ -n "$CONSTRAINTS" ]; then From 6936d14cef5bc2e56ca4374b107b4f9ac3999782 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sat, 6 Jul 2024 14:09:16 -0300 Subject: [PATCH 157/254] Fix SV files inclusion --- examples/projects/openflow.py | 2 +- examples/projects/quartus.py | 2 +- examples/projects/vivado.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/projects/openflow.py b/examples/projects/openflow.py index 8eec90e4..b40f6c96 100644 --- a/examples/projects/openflow.py +++ b/examples/projects/openflow.py @@ -51,7 +51,7 @@ if args.source == 'slog': prj.add_include('../sources/slog/include1') prj.add_include('../sources/slog/include2') - prj.add_vlog('../sources/slog/*.sv') + prj.add_slog('../sources/slog/*.sv') if args.source in ['vlog', 'slog']: prj.add_define('DEFINE1', '1') prj.add_define('DEFINE2', '1') diff --git a/examples/projects/quartus.py b/examples/projects/quartus.py index ed5e4270..07cfedc4 100644 --- a/examples/projects/quartus.py +++ b/examples/projects/quartus.py @@ -36,7 +36,7 @@ if args.source == 'slog': prj.add_include('../sources/slog/include1') prj.add_include('../sources/slog/include2') - prj.add_vlog('../sources/slog/*.sv') + prj.add_slog('../sources/slog/*.sv') if args.source in ['vlog', 'slog']: prj.add_define('DEFINE1', '1') prj.add_define('DEFINE2', '1') diff --git a/examples/projects/vivado.py b/examples/projects/vivado.py index 1ef6ace7..367f2861 100644 --- a/examples/projects/vivado.py +++ b/examples/projects/vivado.py @@ -42,7 +42,7 @@ if args.source == 'slog': prj.add_include('../sources/slog/include1') prj.add_include('../sources/slog/include2') - prj.add_vlog('../sources/slog/*.sv') + prj.add_slog('../sources/slog/*.sv') if args.source in ['vlog', 'slog']: prj.add_define('DEFINE1', '1') prj.add_define('DEFINE2', '1') From ed6257b0bb4c4f8cdce9bfe5b966a6eafdd143f1 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sun, 7 Jul 2024 21:49:29 -0300 Subject: [PATCH 158/254] Modified to try SV for Openflow --- tests/projects/support.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/projects/support.py b/tests/projects/support.py index 13c9b868..4aa0d009 100644 --- a/tests/projects/support.py +++ b/tests/projects/support.py @@ -93,7 +93,7 @@ except Exception: pass -if args.tool not in ['ise', 'openflow']: +if args.tool not in ['ise']: print('INFO: checking basic System Verilog Support') prj = tools[args.tool]() prj.add_slog('../../examples/sources/slog/blink.sv') From f249f1431c25e1a9147a0aa6513efa4300c4a6ed Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sun, 7 Jul 2024 22:02:31 -0300 Subject: [PATCH 159/254] Cosmetic changes to Jinja templates --- pyfpga/templates/ise-prog.jinja | 10 ++++ pyfpga/templates/ise.jinja | 45 ++++++++++++--- pyfpga/templates/libero-prog.jinja | 1 + pyfpga/templates/libero.jinja | 82 ++++++++++++++++++++++------ pyfpga/templates/openflow-prog.jinja | 1 + pyfpga/templates/openflow.jinja | 25 +++++++-- pyfpga/templates/quartus-prog.jinja | 2 + pyfpga/templates/quartus.jinja | 63 ++++++++++++++++----- pyfpga/templates/vivado-prog.jinja | 2 + pyfpga/templates/vivado.jinja | 47 +++++++++++++--- 10 files changed, 228 insertions(+), 50 deletions(-) diff --git a/pyfpga/templates/ise-prog.jinja b/pyfpga/templates/ise-prog.jinja index 2ba0c193..0f0e38c9 100644 --- a/pyfpga/templates/ise-prog.jinja +++ b/pyfpga/templates/ise-prog.jinja @@ -1,11 +1,15 @@ +{# # # Copyright (C) 2015-2024 Rodrigo A. Melo # # SPDX-License-Identifier: GPL-3.0-or-later # +#} cleancablelock +{# ------------------------------------------------------------------------- #} + {% if FPGA %} setMode -bs setCable -port auto @@ -14,6 +18,8 @@ assignFile -p {{ POSITION }} -file {{ BITSTREAM }} Program -p {{ POSITION }} {% endif %} +{# ------------------------------------------------------------------------- #} + {% if SPI %} setMode -pff addConfigDevice -name {{ NAME }} -path . @@ -31,6 +37,8 @@ assignfiletoattachedflash -position {{ POSITION }} -file ./{{ NAME }}.mcs Program -p {{ POSITION }} -dataWidth {{ WIDTH }} -spionly -e -v -loadfpga {% endif %} +{# ------------------------------------------------------------------------- #} + {% if BPI %} setMode -pff addConfigDevice -name {{ NAME }} -path . @@ -49,4 +57,6 @@ assignfiletoattachedflash -position {{ POSITION }} -file ./{{ NAME }}.mcs Program -p {{ POSITION }} -dataWidth {{ WIDTH }} -rs1 NONE -rs0 NONE -bpionly -e -v -loadfpga {% endif %} +{# ------------------------------------------------------------------------- #} + quit diff --git a/pyfpga/templates/ise.jinja b/pyfpga/templates/ise.jinja index d72a26ae..10811ebf 100644 --- a/pyfpga/templates/ise.jinja +++ b/pyfpga/templates/ise.jinja @@ -1,12 +1,17 @@ +{# # # Copyright (C) 2015-2024 Rodrigo A. Melo # # SPDX-License-Identifier: GPL-3.0-or-later # +#} -#--[ Project configuration ]--------------------------------------------------- +{# ------------------------------------------------------------------------- #} +{# Project configuration #} +{# ------------------------------------------------------------------------- #} {% if CFG %} + if { [ file exists {{ PROJECT }}.xise ] } { file delete {{ PROJECT }}.xise } project new {{ PROJECT }}.xise project set family {{ FAMILY }} @@ -16,19 +21,26 @@ project set speed -{{ SPEED }} {{ PRECFG }} -{% if FILES %}{% for name, attr in FILES.items() %} +{% if FILES %} +{% for name, attr in FILES.items() %} {% if 'lib' in attr %}lib_vhdl new {{ attr.lib }}{% endif %} xfile add {{ name }}{% if 'lib' in attr %} -lib_vhdl {{ attr.lib }}{% endif %} -{% endfor %}{% endif %} +{% endfor %} +{% endif %} -{% if CONSTRAINTS %}{% for name, attr in CONSTRAINTS.items() %}{% set name_str = name|string %} +{% if CONSTRAINTS %} +{% for name, attr in CONSTRAINTS.items() %} +{% set name_str = name|string %} xfile add {{ name_str }} {% if name_str.endswith('.xcf') %} project set "Synthesis Constraints File" "{{ name_str }}" -process "Synthesize - XST" {% endif %} -{% endfor %}{% endif %} +{% endfor %} +{% endif %} -{% if TOP %}project set top {{ TOP }}{% endif %} +{% if TOP %} +project set top {{ TOP }} +{% endif %} {% if INCLUDES %} project set "Verilog Include Directories" "{{ INCLUDES | join('|') }}" -process "Synthesize - XST" @@ -45,14 +57,21 @@ project set "Generics, Parameters" "{{ PARAMS.items() | map('join', '=') | join( {{ POSTCFG }} project close + {% endif %} -#--[ Design flow ]------------------------------------------------------------- +{# ------------------------------------------------------------------------- #} +{# Design flow #} +{# ------------------------------------------------------------------------- #} {% if SYN or PAR or BIT %} + project open {{ PROJECT }}.xise +{# Synthesis --------------------------------------------------------------- #} + {% if SYN %} + {{ PRESYN }} # PRESYNTH @@ -62,9 +81,13 @@ process run "Synthesize" if { [process get "Synthesize" status] == "errors" } { exit 1 } {{ POSTSYN }} + {% endif %} +{# Place and Route --------------------------------------------------------- #} + {% if PAR %} + {{ PREPAR }} process run "Translate" @@ -75,9 +98,13 @@ process run "Place & Route" if { [process get "Place & Route" status] == "errors" } { exit 1 } {{ POSTPAR }} + {% endif %} +{# Bitstream generation ---------------------------------------------------- #} + {% if BIT %} + {{ PREBIT }} process run "Generate Programming File" @@ -85,7 +112,11 @@ if { [process get "Generate Programming File" status] == "errors" } { exit 1 } catch { file rename -force {{ TOP }}.bit {{ PROJECT }}.bit } {{ POSTBIT }} + {% endif %} +{# ------------------------------------------------------------------------- #} + project close + {% endif %} diff --git a/pyfpga/templates/libero-prog.jinja b/pyfpga/templates/libero-prog.jinja index 95b740f0..5bd27ab5 100644 --- a/pyfpga/templates/libero-prog.jinja +++ b/pyfpga/templates/libero-prog.jinja @@ -3,6 +3,7 @@ # # SPDX-License-Identifier: GPL-3.0-or-later # +#} # open_project -file {$TEMPDIR/libero.prjx} # run_tool -name {CONFIGURE_CHAIN} -script {$TEMPDIR/flashpro.tcl} diff --git a/pyfpga/templates/libero.jinja b/pyfpga/templates/libero.jinja index 3d961e51..f62d78dc 100644 --- a/pyfpga/templates/libero.jinja +++ b/pyfpga/templates/libero.jinja @@ -1,36 +1,55 @@ +{# # # Copyright (C) 2015-2024 Rodrigo A. Melo # # SPDX-License-Identifier: GPL-3.0-or-later # +#} -#--[ Project configuration ]--------------------------------------------------- +{# ------------------------------------------------------------------------- #} +{# Project configuration #} +{# ------------------------------------------------------------------------- #} {% if CFG %} + if { [ file exists {{ PROJECT }} ] } { file delete -force -- {{ PROJECT }} } new_project -name {{ PROJECT }} -location {libero} -hdl {VERILOG} -family {SmartFusion2} set_device -family {{ FAMILY }} -die {{ DEVICE }} -package {{ PACKAGE }} -speed {{ SPEED }} {{ PRECFG }} -{% if INCLUDES %}set_global_include_path_order -paths "{{ INCLUDES | join(' ') }}"{% endif %} +{% if INCLUDES %} +set_global_include_path_order -paths "{{ INCLUDES | join(' ') }}" +{% endif %} -{% if FILES %}{% for name, attr in FILES.items() %} +{% if FILES %} +{% for name, attr in FILES.items() %} create_links -hdl_source {{ name }}{% if 'lib' in attr %} -library {{ attr.lib }}{% endif %} -{% endfor %}{% endif %} +{% endfor %} +{% endif %} -{% if CONSTRAINTS %}{% for name, attr in CONSTRAINTS.items() %}{% set name_str = name|string %} +{% if CONSTRAINTS %} +{% for name, attr in CONSTRAINTS.items() %} +{% set name_str = name|string %} create_links {% if name_str.endswith('.sdc') %}-sdc{% else %}-io_pdc{% endif %} {{ name_str }} -{% endfor %}{% endif %} +{% endfor %} +{% endif %} build_design_hierarchy -{% if TOP %}set_root {{ TOP }}{% endif %} +{% if TOP %} +set_root {{ TOP }} +{% endif %} {% if CONSTRAINTS %} -{% set sdc_files = [] %}{% set pdc_files = [] %} -{% for name, attr in CONSTRAINTS.items() %}{% set name_str = name|string %} -{% if name_str.endswith('.sdc') %}{% set _ = sdc_files.append(name_str) %}{% endif %}{% set _ = pdc_files.append(name_str) %} +{% set sdc_files = [] %} +{% set pdc_files = [] %} +{% for name, attr in CONSTRAINTS.items() %} +{% set name_str = name|string %} +{% if name_str.endswith('.sdc') %} +{% set _ = sdc_files.append(name_str) %} +{% endif %} +{% set _ = pdc_files.append(name_str) %} {% endfor %} {% endif %} @@ -40,52 +59,81 @@ build_design_hierarchy {% if INCLUDES or DEFINES or PARAMS %} configure_tool -name {SYNTHESIZE} -params {SYNPLIFY_OPTIONS: -{% if INCLUDES %}{% for INCLUDE in INCLUDES %} + +{% if INCLUDES %} +{% for INCLUDE in INCLUDES %} set_option -include_path "{{ INCLUDE }}" -{% endfor %}{% endif %} -{% if DEFINES %}{% for key, value in DEFINES.items() %} +{% endfor %} +{% endif %} + +{% if DEFINES %} +{% for key, value in DEFINES.items() %} set_option -hdl_define -set {{ key }}={{ value }} -{% endfor %}{% endif %} -{% if PARAMS %}{% for key, value in PARAMS.items() %} +{% endfor %} +{% endif %} + +{% if PARAMS %} +{% for key, value in PARAMS.items() %} set_option -hdl_param -set {{ key }}={{ value }} -{% endfor %}{% endif %} +{% endfor %} +{% endif %} + } {% endif %} {{ POSTCFG }} close_project + {% endif %} -#--[ Design flow ]------------------------------------------------------------- +{# ------------------------------------------------------------------------- #} +{# Design flow #} +{# ------------------------------------------------------------------------- #} {% if SYN or PAR or BIT %} + open_project {{ PROJECT }}/{{ PROJECT }}.prjx +{# Synthesis --------------------------------------------------------------- #} + {% if SYN %} + {{ PRESYN }} run_tool -name {SYNTHESIZE} {{ POSTSYN }} + {% endif %} +{# Place and Route --------------------------------------------------------- #} + {% if PAR %} + {{ PREPAR }} run_tool -name {PLACEROUTE} run_tool -name {VERIFYTIMING} {{ POSTPAR }} + {% endif %} +{# Bitstream generation ---------------------------------------------------- #} + {% if BIT %} + {{ PREBIT }} run_tool -name {GENERATEPROGRAMMINGFILE} {{ POSTBIT }} + {% endif %} +{# ------------------------------------------------------------------------- #} + close_project + {% endif %} diff --git a/pyfpga/templates/openflow-prog.jinja b/pyfpga/templates/openflow-prog.jinja index fc1c1105..637a3c7c 100644 --- a/pyfpga/templates/openflow-prog.jinja +++ b/pyfpga/templates/openflow-prog.jinja @@ -3,6 +3,7 @@ # # SPDX-License-Identifier: GPL-3.0-or-later # +#} set -e diff --git a/pyfpga/templates/openflow.jinja b/pyfpga/templates/openflow.jinja index d29cbbc5..338cb74e 100644 --- a/pyfpga/templates/openflow.jinja +++ b/pyfpga/templates/openflow.jinja @@ -1,35 +1,48 @@ +{# # # Copyright (C) 2020-2024 Rodrigo A. Melo # # SPDX-License-Identifier: GPL-3.0-or-later # +#} set -e DOCKER="docker run --user $(id -u):$(id -g) --rm -v $HOME:$HOME -w $PWD" -# Synthesis ------------------------------------------------------------------- +{# ------------------------------------------------------------------------- #} +{# Design flow #} +{# ------------------------------------------------------------------------- #} + +{# Synthesis --------------------------------------------------------------- #} {% if SYN %} $DOCKER hdlc/ghdl:yosys /bin/bash -c " {{ PRESYN }} yosys -Q -m ghdl -p ' + {% if INCLUDES %} verilog_defaults -add{% for path in INCLUDES %} -I{{ path }}{% endfor %} {% endif %} + {% if DEFINES %} verilog_defines{% for key, value in DEFINES.items() %} -D{{ key }}={{ value }}{% endfor %} {% endif %} -{% if FILES %}{% for name, attr in FILES.items() %} + +{% if FILES %} +{% for name, attr in FILES.items() %} {% if attr.hdl == "vlog" %} read_verilog -defer {{ name }} {% elif attr.hdl == "slog" %} read_verilog -defer -sv {{ name }} {% endif %} -{% endfor %}{% endif %} +{% endfor %} +{% endif %} + {% if PARAMS %} chparam{% for key, value in PARAMS.items() %} -set {{ key }} {{ value }}{% endfor %} {% endif %} + synth -top {{ TOP }} synth_{{ FAMILY }} -top {{ TOP }} -json {{ PROJECT }}.json ' @@ -37,6 +50,7 @@ synth_{{ FAMILY }} -top {{ TOP }} -json {{ PROJECT }}.json " {% endif %} +{# #SYNTH= #WRITE= #if [[ $BACKEND == "vivado" ]]; then @@ -53,8 +67,9 @@ synth_{{ FAMILY }} -top {{ TOP }} -json {{ PROJECT }}.json # SYNTH="synth -top $TOP" # WRITE="write_verilog $PROJECT.v" #fi +#} -# Place and Route ------------------------------------------------------------- +{# Place and Route --------------------------------------------------------- #} {% if PAR %} @@ -89,7 +104,7 @@ nextpnr-ecp5 --{{ DEVICE }} --package {{ PACKAGE }} $CONSTRAINT --json {{ PROJEC {% endif %} -# Bitstream ------------------------------------------------------------------- +{# Bitstream generation ---------------------------------------------------- #} {% if BIT %} diff --git a/pyfpga/templates/quartus-prog.jinja b/pyfpga/templates/quartus-prog.jinja index 62839a6b..f33a7fb6 100644 --- a/pyfpga/templates/quartus-prog.jinja +++ b/pyfpga/templates/quartus-prog.jinja @@ -1,8 +1,10 @@ +{# # # Copyright (C) 2015-2024 Rodrigo A. Melo # # SPDX-License-Identifier: GPL-3.0-or-later # +#} RESULT=$(jtagconfig) echo "$RESULT" diff --git a/pyfpga/templates/quartus.jinja b/pyfpga/templates/quartus.jinja index 1caa2108..acd221ea 100644 --- a/pyfpga/templates/quartus.jinja +++ b/pyfpga/templates/quartus.jinja @@ -1,19 +1,25 @@ +{# # # Copyright (C) 2015-2024 Rodrigo A. Melo # # SPDX-License-Identifier: GPL-3.0-or-later # +#} -#--[ Project configuration ]--------------------------------------------------- +{# ------------------------------------------------------------------------- #} +{# Project configuration #} +{# ------------------------------------------------------------------------- #} {% if CFG %} + package require ::quartus::project project_new {{ PROJECT }} -overwrite set_global_assignment -name DEVICE {{ PART }} {{ PRECFG }} -{% if FILES %}{% for name, attr in FILES.items() %} +{% if FILES %} +{% for name, attr in FILES.items() %} {% if attr.hdl == "vhdl" %} set_global_assignment -name VHDL_FILE {{ name }} {% if 'lib' in attr %} -library {{ attr.lib }}{% endif %} {% elif attr.hdl == "vlog" %} @@ -21,66 +27,97 @@ set_global_assignment -name VERILOG_FILE {{ name }} {% elif attr.hdl == "slog" %} set_global_assignment -name SYSTEMVERILOG_FILE {{ name }} {% endif %} -{% endfor %}{% endif %} +{% endfor %} +{% endif %} -{% if CONSTRAINTS %}{% for name, attr in CONSTRAINTS.items() %}{% set name_str = name|string %} +{% if CONSTRAINTS %} +{% for name, attr in CONSTRAINTS.items() %} +{% set name_str = name|string %} {% if name_str.endswith('.sdc') %} set_global_assignment -name SDC_FILE {{ name_str }} {% else %} source {{ name_str }} {% endif %} -{% endfor %}{% endif %} +{% endfor %} +{% endif %} -{% if TOP %}set_global_assignment -name TOP_LEVEL_ENTITY {{ TOP }}{% endif %} +{% if TOP %} +set_global_assignment -name TOP_LEVEL_ENTITY {{ TOP }} +{% endif %} -{% if INCLUDES %}{% for INCLUDE in INCLUDES %} +{% if INCLUDES %} +{% for INCLUDE in INCLUDES %} set_global_assignment -name SEARCH_PATH {{ INCLUDE }} -{% endfor %}{% endif %} +{% endfor %} +{% endif %} -{% if DEFINES %}{% for key, value in DEFINES.items() %} +{% if DEFINES %} +{% for key, value in DEFINES.items() %} set_global_assignment -name VERILOG_MACRO {{ key }}={{ value }} -{% endfor %}{% endif %} +{% endfor %} +{% endif %} -{% if PARAMS %}{% for key, value in PARAMS.items() %} +{% if PARAMS %} +{% for key, value in PARAMS.items() %} set_parameter -name {{ key }} {{ value }} -{% endfor %}{% endif %} +{% endfor %} +{% endif %} {{ POSTCFG }} project_close + {% endif %} -#--[ Design flow ]------------------------------------------------------------- +{# ------------------------------------------------------------------------- #} +{# Design flow #} +{# ------------------------------------------------------------------------- #} {% if SYN or PAR or BIT %} + package require ::quartus::flow project_open -force {{ PROJECT }}.qpf # set_global_assignment -name NUM_PARALLEL_PROCESSORS ALL +{# Synthesis --------------------------------------------------------------- #} + {% if SYN %} + {{ PRESYN }} execute_module -tool map {{ POSTSYN }} + {% endif %} +{# Place and Route --------------------------------------------------------- #} + {% if PAR %} + {{ PREPAR }} execute_module -tool fit execute_module -tool sta {{ POSTPAR }} + {% endif %} +{# Bitstream generation ---------------------------------------------------- #} + {% if BIT %} + {{ PREBIT }} execute_module -tool asm {{ POSTBIT }} + {% endif %} +{# ------------------------------------------------------------------------- #} + project_close + {% endif %} diff --git a/pyfpga/templates/vivado-prog.jinja b/pyfpga/templates/vivado-prog.jinja index 7b148b22..c2d9a9cb 100644 --- a/pyfpga/templates/vivado-prog.jinja +++ b/pyfpga/templates/vivado-prog.jinja @@ -1,8 +1,10 @@ +{# # # Copyright (C) 2015-2024 Rodrigo A. Melo # # SPDX-License-Identifier: GPL-3.0-or-later # +#} if { [ catch { open_hw_manager } ] } { open_hw } connect_hw_server diff --git a/pyfpga/templates/vivado.jinja b/pyfpga/templates/vivado.jinja index 6a40c459..6c303254 100644 --- a/pyfpga/templates/vivado.jinja +++ b/pyfpga/templates/vivado.jinja @@ -1,12 +1,17 @@ +{# # # Copyright (C) 2015-2024 Rodrigo A. Melo # # SPDX-License-Identifier: GPL-3.0-or-later # +#} -#--[ Project configuration ]--------------------------------------------------- +{# ------------------------------------------------------------------------- #} +{# Project configuration #} +{# ------------------------------------------------------------------------- #} {% if CFG %} + create_project -force {{ PROJECT }} set_property SOURCE_MGMT_MODE None [current_project] set_property STEPS.SYNTH_DESIGN.ARGS.ASSERT true [get_runs synth_1] @@ -14,12 +19,15 @@ set_property PART {{ PART }} [current_project] {{ PRECFG }} -{% if FILES %}{% for name, attr in FILES.items() %} +{% if FILES %} +{% for name, attr in FILES.items() %} add_file {{ name }} {% if 'lib' in attr %}set_property library {{ attr.lib }} [get_files {{ name }}]{% endif %} -{% endfor %}{% endif %} +{% endfor %} +{% endif %} -{% if CONSTRAINTS %}{% for name, attr in CONSTRAINTS.items() %} +{% if CONSTRAINTS %} +{% for name, attr in CONSTRAINTS.items() %} add_file -fileset constrs_1 {{ name }} {% if attr == "syn" %} set_property USED_IN_IMPLEMENTATION FALSE [get_files {{ name }}] @@ -27,16 +35,19 @@ set_property USED_IN_IMPLEMENTATION FALSE [get_files {{ name }}] set_property USED_IN_SYNTHESIS FALSE [get_files {{ name }}] {% endif %} {% if loop.first %}set_property TARGET_CONSTRS_FILE {{ name }} [current_fileset -constrset]{% endif %} -{% endfor %}{% endif %} +{% endfor %} +{% endif %} -{% if TOP %}set_property TOP {{ TOP }} [current_fileset]{% endif %} +{% if TOP %} +set_property TOP {{ TOP }} [current_fileset] +{% endif %} {% if INCLUDES %} set_property INCLUDE_DIRS { {{ INCLUDES | join(' ') }} } [current_fileset] {% endif %} {% if DEFINES %} -set_property VERILOG_DEFINE { {{ DEFINES.items() | map('join', '=') | join(' ') }} } [current_fileset] +set_property VERILOG_DEFINE { {{ DEFINES.items() | map('join', '=') | join(' ') }} } [current_fileset] {% endif %} {% if PARAMS %} @@ -46,14 +57,21 @@ set_property GENERIC { {{ PARAMS.items() | map('join', '=') | join(' ') }} } -ob {{ POSTCFG }} close_project + {% endif %} -#--[ Design flow ]------------------------------------------------------------- +{# ------------------------------------------------------------------------- #} +{# Design flow #} +{# ------------------------------------------------------------------------- #} {% if SYN or PAR or BIT %} + open_project {{ PROJECT }} +{# Synthesis --------------------------------------------------------------- #} + {% if SYN %} + {{ PRESYN }} # PRESYNTH @@ -62,12 +80,17 @@ reset_run synth_1 launch_runs synth_1 wait_on_run synth_1 #report_property [get_runs synth_1] + if { [get_property STATUS [get_runs synth_1]] ne "synth_design Complete!" } { exit 1 } {{ POSTSYN }} + {% endif %} +{# Place and Route --------------------------------------------------------- #} + {% if PAR %} + {{ PREPAR }} reset_run impl_1 @@ -77,9 +100,13 @@ wait_on_run impl_1 if { [get_property STATUS [get_runs impl_1]] ne "route_design Complete!" } { exit 1 } {{ POSTPAR }} + {% endif %} +{# Bitstream generation ---------------------------------------------------- #} + {% if BIT %} + {{ PREBIT }} open_run impl_1 @@ -87,7 +114,11 @@ write_bitstream -force {{ PROJECT }} write_debug_probes -force -quiet {{ PROJECT }}.ltx {{ POSTBIT }} + {% endif %} +{# ------------------------------------------------------------------------- #} + close_project + {% endif %} From 0efbd0e098eb683f1629455cbf3946e5a9489ea9 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sun, 7 Jul 2024 22:17:25 -0300 Subject: [PATCH 160/254] Copyright notice updated --- pyfpga/helpers/bitprog.py | 16 ++-------------- pyfpga/helpers/hdl2bit.py | 16 ++-------------- pyfpga/helpers/prj2bit.py | 16 ++-------------- pyfpga/ise.py | 2 +- pyfpga/libero.py | 2 +- pyfpga/openflow.py | 2 +- pyfpga/project.py | 2 +- pyfpga/quartus.py | 2 +- pyfpga/templates/ise-prog.jinja | 2 +- pyfpga/templates/ise.jinja | 2 +- pyfpga/templates/libero-prog.jinja | 2 +- pyfpga/templates/libero.jinja | 2 +- pyfpga/templates/openflow-prog.jinja | 2 +- pyfpga/templates/openflow.jinja | 2 +- pyfpga/templates/quartus-prog.jinja | 2 +- pyfpga/templates/quartus.jinja | 2 +- pyfpga/templates/vivado-prog.jinja | 2 +- pyfpga/templates/vivado.jinja | 2 +- pyfpga/vivado.py | 2 +- tests/mocks/impact | 2 +- tests/mocks/libero | 2 +- tests/mocks/quartus_pgm | 2 +- tests/mocks/quartus_sh | 2 +- tests/mocks/vivado | 2 +- tests/mocks/xtclsh | 2 +- 25 files changed, 28 insertions(+), 64 deletions(-) diff --git a/pyfpga/helpers/bitprog.py b/pyfpga/helpers/bitprog.py index a15e18a3..0282f263 100644 --- a/pyfpga/helpers/bitprog.py +++ b/pyfpga/helpers/bitprog.py @@ -1,20 +1,8 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020 INTI -# Copyright (C) 2020 Rodrigo A. Melo +# Copyright (C) 2020 PyFPGA Project # -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . +# SPDX-License-Identifier: GPL-3.0-or-later # """ diff --git a/pyfpga/helpers/hdl2bit.py b/pyfpga/helpers/hdl2bit.py index 87ed4159..8b6d9173 100644 --- a/pyfpga/helpers/hdl2bit.py +++ b/pyfpga/helpers/hdl2bit.py @@ -1,20 +1,8 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020 INTI -# Copyright (C) 2020 Rodrigo A. Melo +# Copyright (C) 2020 PyFPGA Project # -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . +# SPDX-License-Identifier: GPL-3.0-or-later # """ diff --git a/pyfpga/helpers/prj2bit.py b/pyfpga/helpers/prj2bit.py index 1cd8a74e..4a0a6886 100644 --- a/pyfpga/helpers/prj2bit.py +++ b/pyfpga/helpers/prj2bit.py @@ -1,20 +1,8 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020 INTI -# Copyright (C) 2020 Rodrigo A. Melo +# Copyright (C) 2020 PyFPGA Project # -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . +# SPDX-License-Identifier: GPL-3.0-or-later # """ diff --git a/pyfpga/ise.py b/pyfpga/ise.py index 9e173f2f..b8f33706 100644 --- a/pyfpga/ise.py +++ b/pyfpga/ise.py @@ -1,5 +1,5 @@ # -# Copyright (C) 2019-2024 Rodrigo A. Melo +# Copyright (C) 2019-2024 PyFPGA Project # # SPDX-License-Identifier: GPL-3.0-or-later # diff --git a/pyfpga/libero.py b/pyfpga/libero.py index 813eb3da..7c07a6a1 100644 --- a/pyfpga/libero.py +++ b/pyfpga/libero.py @@ -1,5 +1,5 @@ # -# Copyright (C) 2019-2024 Rodrigo A. Melo +# Copyright (C) 2019-2024 PyFPGA Project # # SPDX-License-Identifier: GPL-3.0-or-later # diff --git a/pyfpga/openflow.py b/pyfpga/openflow.py index a2e4092d..63712a85 100644 --- a/pyfpga/openflow.py +++ b/pyfpga/openflow.py @@ -1,5 +1,5 @@ # -# Copyright (C) 2020-2024 Rodrigo A. Melo +# Copyright (C) 2020-2024 PyFPGA Project # # SPDX-License-Identifier: GPL-3.0-or-later # diff --git a/pyfpga/project.py b/pyfpga/project.py index b77b8159..19570036 100644 --- a/pyfpga/project.py +++ b/pyfpga/project.py @@ -1,5 +1,5 @@ # -# Copyright (C) 2019-2024 Rodrigo A. Melo +# Copyright (C) 2019-2024 PyFPGA Project # # SPDX-License-Identifier: GPL-3.0-or-later # diff --git a/pyfpga/quartus.py b/pyfpga/quartus.py index 10a5927e..0cc5f93d 100644 --- a/pyfpga/quartus.py +++ b/pyfpga/quartus.py @@ -1,5 +1,5 @@ # -# Copyright (C) 2019-2024 Rodrigo A. Melo +# Copyright (C) 2019-2024 PyFPGA Project # # SPDX-License-Identifier: GPL-3.0-or-later # diff --git a/pyfpga/templates/ise-prog.jinja b/pyfpga/templates/ise-prog.jinja index 0f0e38c9..1c2203ee 100644 --- a/pyfpga/templates/ise-prog.jinja +++ b/pyfpga/templates/ise-prog.jinja @@ -1,6 +1,6 @@ {# # -# Copyright (C) 2015-2024 Rodrigo A. Melo +# Copyright (C) 2015-2024 PyFPGA Project # # SPDX-License-Identifier: GPL-3.0-or-later # diff --git a/pyfpga/templates/ise.jinja b/pyfpga/templates/ise.jinja index 10811ebf..73c5fce9 100644 --- a/pyfpga/templates/ise.jinja +++ b/pyfpga/templates/ise.jinja @@ -1,6 +1,6 @@ {# # -# Copyright (C) 2015-2024 Rodrigo A. Melo +# Copyright (C) 2015-2024 PyFPGA Project # # SPDX-License-Identifier: GPL-3.0-or-later # diff --git a/pyfpga/templates/libero-prog.jinja b/pyfpga/templates/libero-prog.jinja index 5bd27ab5..f2369645 100644 --- a/pyfpga/templates/libero-prog.jinja +++ b/pyfpga/templates/libero-prog.jinja @@ -1,5 +1,5 @@ # -# Copyright (C) 2015-2024 Rodrigo A. Melo +# Copyright (C) 2015-2024 PyFPGA Project # # SPDX-License-Identifier: GPL-3.0-or-later # diff --git a/pyfpga/templates/libero.jinja b/pyfpga/templates/libero.jinja index f62d78dc..3f061cdf 100644 --- a/pyfpga/templates/libero.jinja +++ b/pyfpga/templates/libero.jinja @@ -1,6 +1,6 @@ {# # -# Copyright (C) 2015-2024 Rodrigo A. Melo +# Copyright (C) 2015-2024 PyFPGA Project # # SPDX-License-Identifier: GPL-3.0-or-later # diff --git a/pyfpga/templates/openflow-prog.jinja b/pyfpga/templates/openflow-prog.jinja index 637a3c7c..81942426 100644 --- a/pyfpga/templates/openflow-prog.jinja +++ b/pyfpga/templates/openflow-prog.jinja @@ -1,5 +1,5 @@ # -# Copyright (C) 2020-2024 Rodrigo A. Melo +# Copyright (C) 2020-2024 PyFPGA Project # # SPDX-License-Identifier: GPL-3.0-or-later # diff --git a/pyfpga/templates/openflow.jinja b/pyfpga/templates/openflow.jinja index 338cb74e..15a82f4a 100644 --- a/pyfpga/templates/openflow.jinja +++ b/pyfpga/templates/openflow.jinja @@ -1,6 +1,6 @@ {# # -# Copyright (C) 2020-2024 Rodrigo A. Melo +# Copyright (C) 2020-2024 PyFPGA Project # # SPDX-License-Identifier: GPL-3.0-or-later # diff --git a/pyfpga/templates/quartus-prog.jinja b/pyfpga/templates/quartus-prog.jinja index f33a7fb6..23786f22 100644 --- a/pyfpga/templates/quartus-prog.jinja +++ b/pyfpga/templates/quartus-prog.jinja @@ -1,6 +1,6 @@ {# # -# Copyright (C) 2015-2024 Rodrigo A. Melo +# Copyright (C) 2015-2024 PyFPGA Project # # SPDX-License-Identifier: GPL-3.0-or-later # diff --git a/pyfpga/templates/quartus.jinja b/pyfpga/templates/quartus.jinja index acd221ea..fa26aa9a 100644 --- a/pyfpga/templates/quartus.jinja +++ b/pyfpga/templates/quartus.jinja @@ -1,6 +1,6 @@ {# # -# Copyright (C) 2015-2024 Rodrigo A. Melo +# Copyright (C) 2015-2024 PyFPGA Project # # SPDX-License-Identifier: GPL-3.0-or-later # diff --git a/pyfpga/templates/vivado-prog.jinja b/pyfpga/templates/vivado-prog.jinja index c2d9a9cb..72ccb91d 100644 --- a/pyfpga/templates/vivado-prog.jinja +++ b/pyfpga/templates/vivado-prog.jinja @@ -1,6 +1,6 @@ {# # -# Copyright (C) 2015-2024 Rodrigo A. Melo +# Copyright (C) 2015-2024 PyFPGA Project # # SPDX-License-Identifier: GPL-3.0-or-later # diff --git a/pyfpga/templates/vivado.jinja b/pyfpga/templates/vivado.jinja index 6c303254..5b80a7d5 100644 --- a/pyfpga/templates/vivado.jinja +++ b/pyfpga/templates/vivado.jinja @@ -1,6 +1,6 @@ {# # -# Copyright (C) 2015-2024 Rodrigo A. Melo +# Copyright (C) 2015-2024 PyFPGA Project # # SPDX-License-Identifier: GPL-3.0-or-later # diff --git a/pyfpga/vivado.py b/pyfpga/vivado.py index c0a48864..3cc39d6e 100644 --- a/pyfpga/vivado.py +++ b/pyfpga/vivado.py @@ -1,5 +1,5 @@ # -# Copyright (C) 2019-2024 Rodrigo A. Melo +# Copyright (C) 2019-2024 PyFPGA Project # # SPDX-License-Identifier: GPL-3.0-or-later # diff --git a/tests/mocks/impact b/tests/mocks/impact index 0fbbacbc..1446c5e0 100755 --- a/tests/mocks/impact +++ b/tests/mocks/impact @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -# Copyright (C) 2022-2024 Rodrigo A. Melo +# Copyright (C) 2022-2024 PyFPGA Project # # SPDX-License-Identifier: GPL-3.0-or-later # diff --git a/tests/mocks/libero b/tests/mocks/libero index a4663e9d..510b9926 100755 --- a/tests/mocks/libero +++ b/tests/mocks/libero @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -# Copyright (C) 2022-2024 Rodrigo A. Melo +# Copyright (C) 2022-2024 PyFPGA Project # # SPDX-License-Identifier: GPL-3.0-or-later # diff --git a/tests/mocks/quartus_pgm b/tests/mocks/quartus_pgm index 8993ca10..150e1ae9 100755 --- a/tests/mocks/quartus_pgm +++ b/tests/mocks/quartus_pgm @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -# Copyright (C) 2022-2024 Rodrigo A. Melo +# Copyright (C) 2022-2024 PyFPGA Project # # SPDX-License-Identifier: GPL-3.0-or-later # diff --git a/tests/mocks/quartus_sh b/tests/mocks/quartus_sh index 472f639f..7f437145 100755 --- a/tests/mocks/quartus_sh +++ b/tests/mocks/quartus_sh @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -# Copyright (C) 2022-2024 Rodrigo A. Melo +# Copyright (C) 2022-2024 PyFPGA Project # # SPDX-License-Identifier: GPL-3.0-or-later # diff --git a/tests/mocks/vivado b/tests/mocks/vivado index fb59d819..3bc95381 100755 --- a/tests/mocks/vivado +++ b/tests/mocks/vivado @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -# Copyright (C) 2022-2024 Rodrigo A. Melo +# Copyright (C) 2022-2024 PyFPGA Project # # SPDX-License-Identifier: GPL-3.0-or-later # diff --git a/tests/mocks/xtclsh b/tests/mocks/xtclsh index 7621c4d9..22f66a77 100755 --- a/tests/mocks/xtclsh +++ b/tests/mocks/xtclsh @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -# Copyright (C) 2022-2024 Rodrigo A. Melo +# Copyright (C) 2022-2024 PyFPGA Project # # SPDX-License-Identifier: GPL-3.0-or-later # From fa84608232053ef59e84b6862954c45acb72f194 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sun, 7 Jul 2024 23:53:39 -0300 Subject: [PATCH 161/254] Fix paths in TCL on Windows --- pyfpga/project.py | 6 +++--- pyfpga/templates/ise.jinja | 7 +++---- pyfpga/templates/libero.jinja | 10 ++++----- pyfpga/templates/quartus.jinja | 7 +++---- tests/test_data.py | 38 ++++++++++++++++++---------------- 5 files changed, 33 insertions(+), 35 deletions(-) diff --git a/pyfpga/project.py b/pyfpga/project.py index 19570036..16ee090e 100644 --- a/pyfpga/project.py +++ b/pyfpga/project.py @@ -66,7 +66,7 @@ def add_include(self, path): path = Path(path).resolve() if not path.is_dir(): raise NotADirectoryError(path) - self.data.setdefault('includes', []).append(path) + self.data.setdefault('includes', []).append(path.as_posix()) def _add_file(self, pathname, hdl=None, lib=None): files = glob.glob(pathname, recursive=True) @@ -79,7 +79,7 @@ def _add_file(self, pathname, hdl=None, lib=None): attr['hdl'] = hdl if lib: attr['lib'] = lib - self.data.setdefault('files', {})[path] = attr + self.data.setdefault('files', {})[path.as_posix()] = attr def add_slog(self, pathname): """Add System Verilog file/s. @@ -128,7 +128,7 @@ def add_cons(self, path, when='all'): raise FileNotFoundError(path) if when not in ['all', 'syn', 'par']: raise ValueError('Invalid only.') - self.data.setdefault('constraints', {})[path] = when + self.data.setdefault('constraints', {})[path.as_posix()] = when def add_param(self, name, value): """Add a Parameter/Generic Value. diff --git a/pyfpga/templates/ise.jinja b/pyfpga/templates/ise.jinja index 73c5fce9..647856e2 100644 --- a/pyfpga/templates/ise.jinja +++ b/pyfpga/templates/ise.jinja @@ -30,10 +30,9 @@ xfile add {{ name }}{% if 'lib' in attr %} -lib_vhdl {{ attr.lib }}{% endif %} {% if CONSTRAINTS %} {% for name, attr in CONSTRAINTS.items() %} -{% set name_str = name|string %} -xfile add {{ name_str }} -{% if name_str.endswith('.xcf') %} -project set "Synthesis Constraints File" "{{ name_str }}" -process "Synthesize - XST" +xfile add {{ name }} +{% if name.endswith('.xcf') %} +project set "Synthesis Constraints File" "{{ name }}" -process "Synthesize - XST" {% endif %} {% endfor %} {% endif %} diff --git a/pyfpga/templates/libero.jinja b/pyfpga/templates/libero.jinja index 3f061cdf..b4a36d64 100644 --- a/pyfpga/templates/libero.jinja +++ b/pyfpga/templates/libero.jinja @@ -30,8 +30,7 @@ create_links -hdl_source {{ name }}{% if 'lib' in attr %} -library {{ attr.lib } {% if CONSTRAINTS %} {% for name, attr in CONSTRAINTS.items() %} -{% set name_str = name|string %} -create_links {% if name_str.endswith('.sdc') %}-sdc{% else %}-io_pdc{% endif %} {{ name_str }} +create_links {% if name.endswith('.sdc') %}-sdc{% else %}-io_pdc{% endif %} {{ name }} {% endfor %} {% endif %} @@ -45,11 +44,10 @@ set_root {{ TOP }} {% set sdc_files = [] %} {% set pdc_files = [] %} {% for name, attr in CONSTRAINTS.items() %} -{% set name_str = name|string %} -{% if name_str.endswith('.sdc') %} -{% set _ = sdc_files.append(name_str) %} +{% if name.endswith('.sdc') %} +{% set _ = sdc_files.append(name) %} {% endif %} -{% set _ = pdc_files.append(name_str) %} +{% set _ = pdc_files.append(name) %} {% endfor %} {% endif %} diff --git a/pyfpga/templates/quartus.jinja b/pyfpga/templates/quartus.jinja index fa26aa9a..29826df1 100644 --- a/pyfpga/templates/quartus.jinja +++ b/pyfpga/templates/quartus.jinja @@ -32,11 +32,10 @@ set_global_assignment -name SYSTEMVERILOG_FILE {{ name }} {% if CONSTRAINTS %} {% for name, attr in CONSTRAINTS.items() %} -{% set name_str = name|string %} -{% if name_str.endswith('.sdc') %} -set_global_assignment -name SDC_FILE {{ name_str }} +{% if name.endswith('.sdc') %} +set_global_assignment -name SDC_FILE {{ name }} {% else %} -source {{ name_str }} +source {{ name }} {% endif %} {% endfor %} {% endif %} diff --git a/tests/test_data.py b/tests/test_data.py index 250ea793..34f4c633 100644 --- a/tests/test_data.py +++ b/tests/test_data.py @@ -5,35 +5,37 @@ pattern = { 'part': 'PARTNAME', 'includes': [ - Path('fakedata/dir1').resolve(), - Path('fakedata/dir2').resolve(), - Path('fakedata/dir3').resolve() + Path('fakedata/dir1').resolve().as_posix(), + Path('fakedata/dir2').resolve().as_posix(), + Path('fakedata/dir3').resolve().as_posix() ], 'files': { - Path('fakedata/vhdl0.vhdl').resolve(): {'hdl': 'vhdl', 'lib': 'LIB'}, - Path('fakedata/dir1/vhdl1.vhdl').resolve(): { + Path('fakedata/vhdl0.vhdl').resolve().as_posix(): { 'hdl': 'vhdl', 'lib': 'LIB' }, - Path('fakedata/dir2/vhdl2.vhdl').resolve(): { + Path('fakedata/dir1/vhdl1.vhdl').resolve().as_posix(): { 'hdl': 'vhdl', 'lib': 'LIB' }, - Path('fakedata/dir3/vhdl3.vhdl').resolve(): { + Path('fakedata/dir2/vhdl2.vhdl').resolve().as_posix(): { 'hdl': 'vhdl', 'lib': 'LIB' }, - Path('fakedata/vlog0.v').resolve(): {'hdl': 'vlog'}, - Path('fakedata/dir1/vlog1.v').resolve(): {'hdl': 'vlog'}, - Path('fakedata/dir2/vlog2.v').resolve(): {'hdl': 'vlog'}, - Path('fakedata/dir3/vlog3.v').resolve(): {'hdl': 'vlog'}, - Path('fakedata/slog0.sv').resolve(): {'hdl': 'slog'}, - Path('fakedata/dir1/slog1.sv').resolve(): {'hdl': 'slog'}, - Path('fakedata/dir2/slog2.sv').resolve(): {'hdl': 'slog'}, - Path('fakedata/dir3/slog3.sv').resolve(): {'hdl': 'slog'} + Path('fakedata/dir3/vhdl3.vhdl').resolve().as_posix(): { + 'hdl': 'vhdl', 'lib': 'LIB' + }, + Path('fakedata/vlog0.v').resolve().as_posix(): {'hdl': 'vlog'}, + Path('fakedata/dir1/vlog1.v').resolve().as_posix(): {'hdl': 'vlog'}, + Path('fakedata/dir2/vlog2.v').resolve().as_posix(): {'hdl': 'vlog'}, + Path('fakedata/dir3/vlog3.v').resolve().as_posix(): {'hdl': 'vlog'}, + Path('fakedata/slog0.sv').resolve().as_posix(): {'hdl': 'slog'}, + Path('fakedata/dir1/slog1.sv').resolve().as_posix(): {'hdl': 'slog'}, + Path('fakedata/dir2/slog2.sv').resolve().as_posix(): {'hdl': 'slog'}, + Path('fakedata/dir3/slog3.sv').resolve().as_posix(): {'hdl': 'slog'} }, 'top': 'TOPNAME', 'constraints': { - Path('fakedata/cons/all.xdc').resolve(): 'all', - Path('fakedata/cons/syn.xdc').resolve(): 'syn', - Path('fakedata/cons/par.xdc').resolve(): 'par' + Path('fakedata/cons/all.xdc').resolve().as_posix(): 'all', + Path('fakedata/cons/syn.xdc').resolve().as_posix(): 'syn', + Path('fakedata/cons/par.xdc').resolve().as_posix(): 'par' }, 'params': { 'PAR1': 'VAL1', From f437eb7b7d4a97f77198b25a2d42b3a4e4055675 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Mon, 8 Jul 2024 22:34:45 -0300 Subject: [PATCH 162/254] Added comments to easily identify each section --- pyfpga/templates/ise.jinja | 38 +++++++++------------------- pyfpga/templates/libero.jinja | 44 +++++++++++---------------------- pyfpga/templates/openflow.jinja | 16 +++--------- pyfpga/templates/quartus.jinja | 38 +++++++++------------------- pyfpga/templates/vivado.jinja | 38 +++++++++------------------- 5 files changed, 50 insertions(+), 124 deletions(-) diff --git a/pyfpga/templates/ise.jinja b/pyfpga/templates/ise.jinja index 647856e2..6af287ca 100644 --- a/pyfpga/templates/ise.jinja +++ b/pyfpga/templates/ise.jinja @@ -6,11 +6,7 @@ # #} -{# ------------------------------------------------------------------------- #} -{# Project configuration #} -{# ------------------------------------------------------------------------- #} - -{% if CFG %} +{% if CFG %}# Project configuration ------------------------------------------------------- if { [ file exists {{ PROJECT }}.xise ] } { file delete {{ PROJECT }}.xise } project new {{ PROJECT }}.xise @@ -21,14 +17,14 @@ project set speed -{{ SPEED }} {{ PRECFG }} -{% if FILES %} +{% if FILES %}# Files inclusion {% for name, attr in FILES.items() %} {% if 'lib' in attr %}lib_vhdl new {{ attr.lib }}{% endif %} xfile add {{ name }}{% if 'lib' in attr %} -lib_vhdl {{ attr.lib }}{% endif %} {% endfor %} {% endif %} -{% if CONSTRAINTS %} +{% if CONSTRAINTS %}# Constraints inclusion {% for name, attr in CONSTRAINTS.items() %} xfile add {{ name }} {% if name.endswith('.xcf') %} @@ -37,19 +33,19 @@ project set "Synthesis Constraints File" "{{ name }}" -process "Synthesize - XST {% endfor %} {% endif %} -{% if TOP %} +{% if TOP %}# Top-level specification project set top {{ TOP }} {% endif %} -{% if INCLUDES %} +{% if INCLUDES %}# Verilog Includes project set "Verilog Include Directories" "{{ INCLUDES | join('|') }}" -process "Synthesize - XST" {% endif %} -{% if DEFINES %} +{% if DEFINES %}# Verilog Defines project set "Verilog Macros" "{{ DEFINES.items() | map('join', '=') | join(' | ') }}" -process "Synthesize - XST" {% endif %} -{% if PARAMS %} +{% if PARAMS %}# Verilog Parameters / VHDL Generics project set "Generics, Parameters" "{{ PARAMS.items() | map('join', '=') | join(' ') }}" -process "Synthesize - XST" {% endif %} @@ -59,17 +55,11 @@ project close {% endif %} -{# ------------------------------------------------------------------------- #} -{# Design flow #} -{# ------------------------------------------------------------------------- #} - -{% if SYN or PAR or BIT %} +{% if SYN or PAR or BIT %}# Design flow ----------------------------------------------------------------- project open {{ PROJECT }}.xise -{# Synthesis --------------------------------------------------------------- #} - -{% if SYN %} +{% if SYN %}# Synthesis {{ PRESYN }} @@ -83,9 +73,7 @@ if { [process get "Synthesize" status] == "errors" } { exit 1 } {% endif %} -{# Place and Route --------------------------------------------------------- #} - -{% if PAR %} +{% if PAR %}# Place and Route {{ PREPAR }} @@ -100,9 +88,7 @@ if { [process get "Place & Route" status] == "errors" } { exit 1 } {% endif %} -{# Bitstream generation ---------------------------------------------------- #} - -{% if BIT %} +{% if BIT %}# Bitstream generation {{ PREBIT }} @@ -114,8 +100,6 @@ catch { file rename -force {{ TOP }}.bit {{ PROJECT }}.bit } {% endif %} -{# ------------------------------------------------------------------------- #} - project close {% endif %} diff --git a/pyfpga/templates/libero.jinja b/pyfpga/templates/libero.jinja index b4a36d64..a4be188f 100644 --- a/pyfpga/templates/libero.jinja +++ b/pyfpga/templates/libero.jinja @@ -6,11 +6,7 @@ # #} -{# ------------------------------------------------------------------------- #} -{# Project configuration #} -{# ------------------------------------------------------------------------- #} - -{% if CFG %} +{% if CFG %}# Project configuration ------------------------------------------------------- if { [ file exists {{ PROJECT }} ] } { file delete -force -- {{ PROJECT }} } new_project -name {{ PROJECT }} -location {libero} -hdl {VERILOG} -family {SmartFusion2} @@ -18,17 +14,17 @@ set_device -family {{ FAMILY }} -die {{ DEVICE }} -package {{ PACKAGE }} -speed {{ PRECFG }} -{% if INCLUDES %} +{% if INCLUDES %}# Verilog Includes (Libero) set_global_include_path_order -paths "{{ INCLUDES | join(' ') }}" {% endif %} -{% if FILES %} +{% if FILES %}# Files inclusion {% for name, attr in FILES.items() %} create_links -hdl_source {{ name }}{% if 'lib' in attr %} -library {{ attr.lib }}{% endif %} {% endfor %} {% endif %} -{% if CONSTRAINTS %} +{% if CONSTRAINTS %}# Constraints inclusion {% for name, attr in CONSTRAINTS.items() %} create_links {% if name.endswith('.sdc') %}-sdc{% else %}-io_pdc{% endif %} {{ name }} {% endfor %} @@ -36,11 +32,11 @@ create_links {% if name.endswith('.sdc') %}-sdc{% else %}-io_pdc{% endif %} {{ n build_design_hierarchy -{% if TOP %} +{% if TOP %}# Top-level specification set_root {{ TOP }} {% endif %} -{% if CONSTRAINTS %} +{% if CONSTRAINTS %}# Constraints configuration {% set sdc_files = [] %} {% set pdc_files = [] %} {% for name, attr in CONSTRAINTS.items() %} @@ -55,22 +51,22 @@ set_root {{ TOP }} {% if pdc_files %}organize_tool_files -tool {PLACEROUTE} -file {{ pdc_files | join(' -file ') }} -module {{ TOP }} -input_type {constraint}{% endif %} {% if sdc_files %}organize_tool_files -tool {VERIFYTIMING} -file {{ sdc_files | join(' -file ') }} -module {{ TOP }} -input_type {constraint}{% endif %} -{% if INCLUDES or DEFINES or PARAMS %} +{% if INCLUDES or DEFINES or PARAMS %}# Synopsys configuration configure_tool -name {SYNTHESIZE} -params {SYNPLIFY_OPTIONS: -{% if INCLUDES %} +{% if INCLUDES %}# Verilog Includes (Synopsys) {% for INCLUDE in INCLUDES %} set_option -include_path "{{ INCLUDE }}" {% endfor %} {% endif %} -{% if DEFINES %} +{% if DEFINES %}# Verilog Defines (Synopsys) {% for key, value in DEFINES.items() %} set_option -hdl_define -set {{ key }}={{ value }} {% endfor %} {% endif %} -{% if PARAMS %} +{% if PARAMS %}# Verilog Parameters / VHDL Generics (Synopsys) {% for key, value in PARAMS.items() %} set_option -hdl_param -set {{ key }}={{ value }} {% endfor %} @@ -85,17 +81,11 @@ close_project {% endif %} -{# ------------------------------------------------------------------------- #} -{# Design flow #} -{# ------------------------------------------------------------------------- #} - -{% if SYN or PAR or BIT %} +{% if SYN or PAR or BIT %}# Design flow ----------------------------------------------------------------- open_project {{ PROJECT }}/{{ PROJECT }}.prjx -{# Synthesis --------------------------------------------------------------- #} - -{% if SYN %} +{% if SYN %}# Synthesis {{ PRESYN }} @@ -105,9 +95,7 @@ run_tool -name {SYNTHESIZE} {% endif %} -{# Place and Route --------------------------------------------------------- #} - -{% if PAR %} +{% if PAR %}# Place and Route {{ PREPAR }} @@ -118,9 +106,7 @@ run_tool -name {VERIFYTIMING} {% endif %} -{# Bitstream generation ---------------------------------------------------- #} - -{% if BIT %} +{% if BIT %}# Bitstream generation {{ PREBIT }} @@ -130,8 +116,6 @@ run_tool -name {GENERATEPROGRAMMINGFILE} {% endif %} -{# ------------------------------------------------------------------------- #} - close_project {% endif %} diff --git a/pyfpga/templates/openflow.jinja b/pyfpga/templates/openflow.jinja index 15a82f4a..02e57662 100644 --- a/pyfpga/templates/openflow.jinja +++ b/pyfpga/templates/openflow.jinja @@ -10,13 +10,7 @@ set -e DOCKER="docker run --user $(id -u):$(id -g) --rm -v $HOME:$HOME -w $PWD" -{# ------------------------------------------------------------------------- #} -{# Design flow #} -{# ------------------------------------------------------------------------- #} - -{# Synthesis --------------------------------------------------------------- #} - -{% if SYN %} +{% if SYN %}# Synthesis $DOCKER hdlc/ghdl:yosys /bin/bash -c " {{ PRESYN }} yosys -Q -m ghdl -p ' @@ -69,9 +63,7 @@ synth_{{ FAMILY }} -top {{ TOP }} -json {{ PROJECT }}.json #fi #} -{# Place and Route --------------------------------------------------------- #} - -{% if PAR %} +{% if PAR %}# Place and Route CONSTRAINTS="{{ CONSTRAINTS | join(' ') }}" @@ -104,9 +96,7 @@ nextpnr-ecp5 --{{ DEVICE }} --package {{ PACKAGE }} $CONSTRAINT --json {{ PROJEC {% endif %} -{# Bitstream generation ---------------------------------------------------- #} - -{% if BIT %} +{% if BIT %}# Bitstream generation {% if FAMILY == 'ice40' %} $DOCKER hdlc/icestorm /bin/bash -c " diff --git a/pyfpga/templates/quartus.jinja b/pyfpga/templates/quartus.jinja index 29826df1..dcbcb5d9 100644 --- a/pyfpga/templates/quartus.jinja +++ b/pyfpga/templates/quartus.jinja @@ -6,11 +6,7 @@ # #} -{# ------------------------------------------------------------------------- #} -{# Project configuration #} -{# ------------------------------------------------------------------------- #} - -{% if CFG %} +{% if CFG %}# Project configuration ------------------------------------------------------- package require ::quartus::project project_new {{ PROJECT }} -overwrite @@ -18,7 +14,7 @@ set_global_assignment -name DEVICE {{ PART }} {{ PRECFG }} -{% if FILES %} +{% if FILES %}# Files inclusion {% for name, attr in FILES.items() %} {% if attr.hdl == "vhdl" %} set_global_assignment -name VHDL_FILE {{ name }} {% if 'lib' in attr %} -library {{ attr.lib }}{% endif %} @@ -30,7 +26,7 @@ set_global_assignment -name SYSTEMVERILOG_FILE {{ name }} {% endfor %} {% endif %} -{% if CONSTRAINTS %} +{% if CONSTRAINTS %}# Constraints inclusion {% for name, attr in CONSTRAINTS.items() %} {% if name.endswith('.sdc') %} set_global_assignment -name SDC_FILE {{ name }} @@ -40,23 +36,23 @@ source {{ name }} {% endfor %} {% endif %} -{% if TOP %} +{% if TOP %}# Top-level specification set_global_assignment -name TOP_LEVEL_ENTITY {{ TOP }} {% endif %} -{% if INCLUDES %} +{% if INCLUDES %}# Verilog Includes {% for INCLUDE in INCLUDES %} set_global_assignment -name SEARCH_PATH {{ INCLUDE }} {% endfor %} {% endif %} -{% if DEFINES %} +{% if DEFINES %}# Verilog Defines {% for key, value in DEFINES.items() %} set_global_assignment -name VERILOG_MACRO {{ key }}={{ value }} {% endfor %} {% endif %} -{% if PARAMS %} +{% if PARAMS %}# Verilog Parameters / VHDL Generics {% for key, value in PARAMS.items() %} set_parameter -name {{ key }} {{ value }} {% endfor %} @@ -68,19 +64,13 @@ project_close {% endif %} -{# ------------------------------------------------------------------------- #} -{# Design flow #} -{# ------------------------------------------------------------------------- #} - -{% if SYN or PAR or BIT %} +{% if SYN or PAR or BIT %}# Design flow ----------------------------------------------------------------- package require ::quartus::flow project_open -force {{ PROJECT }}.qpf # set_global_assignment -name NUM_PARALLEL_PROCESSORS ALL -{# Synthesis --------------------------------------------------------------- #} - -{% if SYN %} +{% if SYN %}# Synthesis {{ PRESYN }} @@ -90,9 +80,7 @@ execute_module -tool map {% endif %} -{# Place and Route --------------------------------------------------------- #} - -{% if PAR %} +{% if PAR %}# Place and Route {{ PREPAR }} @@ -103,9 +91,7 @@ execute_module -tool sta {% endif %} -{# Bitstream generation ---------------------------------------------------- #} - -{% if BIT %} +{% if BIT %}# Bitstream generation {{ PREBIT }} @@ -115,8 +101,6 @@ execute_module -tool asm {% endif %} -{# ------------------------------------------------------------------------- #} - project_close {% endif %} diff --git a/pyfpga/templates/vivado.jinja b/pyfpga/templates/vivado.jinja index 5b80a7d5..19a53aee 100644 --- a/pyfpga/templates/vivado.jinja +++ b/pyfpga/templates/vivado.jinja @@ -6,11 +6,7 @@ # #} -{# ------------------------------------------------------------------------- #} -{# Project configuration #} -{# ------------------------------------------------------------------------- #} - -{% if CFG %} +{% if CFG %}# Project configuration ------------------------------------------------------- create_project -force {{ PROJECT }} set_property SOURCE_MGMT_MODE None [current_project] @@ -19,14 +15,14 @@ set_property PART {{ PART }} [current_project] {{ PRECFG }} -{% if FILES %} +{% if FILES %}# Files inclusion {% for name, attr in FILES.items() %} add_file {{ name }} {% if 'lib' in attr %}set_property library {{ attr.lib }} [get_files {{ name }}]{% endif %} {% endfor %} {% endif %} -{% if CONSTRAINTS %} +{% if CONSTRAINTS %}# Constraints inclusion {% for name, attr in CONSTRAINTS.items() %} add_file -fileset constrs_1 {{ name }} {% if attr == "syn" %} @@ -38,19 +34,19 @@ set_property USED_IN_SYNTHESIS FALSE [get_files {{ name }}] {% endfor %} {% endif %} -{% if TOP %} +{% if TOP %}# Top-level specification set_property TOP {{ TOP }} [current_fileset] {% endif %} -{% if INCLUDES %} +{% if INCLUDES %}# Verilog Includes set_property INCLUDE_DIRS { {{ INCLUDES | join(' ') }} } [current_fileset] {% endif %} -{% if DEFINES %} +{% if DEFINES %}# Verilog Defines set_property VERILOG_DEFINE { {{ DEFINES.items() | map('join', '=') | join(' ') }} } [current_fileset] {% endif %} -{% if PARAMS %} +{% if PARAMS %}# Verilog Parameters / VHDL Generics set_property GENERIC { {{ PARAMS.items() | map('join', '=') | join(' ') }} } -objects [get_filesets sources_1] {% endif %} @@ -60,17 +56,11 @@ close_project {% endif %} -{# ------------------------------------------------------------------------- #} -{# Design flow #} -{# ------------------------------------------------------------------------- #} - -{% if SYN or PAR or BIT %} +{% if SYN or PAR or BIT %}# Design flow ----------------------------------------------------------------- open_project {{ PROJECT }} -{# Synthesis --------------------------------------------------------------- #} - -{% if SYN %} +{% if SYN %}# Synthesis {{ PRESYN }} @@ -87,9 +77,7 @@ if { [get_property STATUS [get_runs synth_1]] ne "synth_design Complete!" } { ex {% endif %} -{# Place and Route --------------------------------------------------------- #} - -{% if PAR %} +{% if PAR %}# Place and Route {{ PREPAR }} @@ -103,9 +91,7 @@ if { [get_property STATUS [get_runs impl_1]] ne "route_design Complete!" } { exi {% endif %} -{# Bitstream generation ---------------------------------------------------- #} - -{% if BIT %} +{% if BIT %}# Bitstream generation {{ PREBIT }} @@ -117,8 +103,6 @@ write_debug_probes -force -quiet {{ PROJECT }}.ltx {% endif %} -{# ------------------------------------------------------------------------- #} - close_project {% endif %} From a34381909804057fde54dd131e877737343a6644 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Mon, 8 Jul 2024 23:37:14 -0300 Subject: [PATCH 163/254] Change template variables from uppercase to lowercase --- pyfpga/ise.py | 40 +++++++-------- pyfpga/libero.py | 38 +++++++------- pyfpga/openflow.py | 38 +++++++------- pyfpga/project.py | 2 +- pyfpga/quartus.py | 34 ++++++------- pyfpga/templates/ise-prog.jinja | 32 ++++++------ pyfpga/templates/ise.jinja | 66 ++++++++++++------------ pyfpga/templates/libero.jinja | 76 ++++++++++++++-------------- pyfpga/templates/openflow-prog.jinja | 8 +-- pyfpga/templates/openflow.jinja | 66 ++++++++++++------------ pyfpga/templates/quartus-prog.jinja | 2 +- pyfpga/templates/quartus.jinja | 58 ++++++++++----------- pyfpga/templates/vivado-prog.jinja | 2 +- pyfpga/templates/vivado.jinja | 60 +++++++++++----------- pyfpga/vivado.py | 32 ++++++------ 15 files changed, 277 insertions(+), 277 deletions(-) diff --git a/pyfpga/ise.py b/pyfpga/ise.py index b8f33706..86e1e05a 100644 --- a/pyfpga/ise.py +++ b/pyfpga/ise.py @@ -23,29 +23,29 @@ class Ise(Project): def _make_prepare(self, steps): info = get_info(self.data.get('part', 'xc7k160t-3-fbg484')) context = { - 'PROJECT': self.name or 'ise', - 'FAMILY': info['family'], - 'DEVICE': info['device'], - 'SPEED': info['speed'], - 'PACKAGE': info['package'] + 'project': self.name or 'ise', + 'family': info['family'], + 'device': info['device'], + 'speed': info['speed'], + 'package': info['package'] } for step in steps: context[step] = 1 - context['INCLUDES'] = self.data.get('includes', None) - context['FILES'] = self.data.get('files', None) - context['CONSTRAINTS'] = self.data.get('constraints', None) - context['TOP'] = self.data.get('top', None) - context['DEFINES'] = self.data.get('defines', None) - context['PARAMS'] = self.data.get('params', None) + context['includes'] = self.data.get('includes', None) + context['files'] = self.data.get('files', None) + context['constraints'] = self.data.get('constraints', None) + context['top'] = self.data.get('top', None) + context['defines'] = self.data.get('defines', None) + context['params'] = self.data.get('params', None) if 'hooks' in self.data: - context['PRECFG'] = self.data['hooks'].get('precfg', None) - context['POSTCFG'] = self.data['hooks'].get('postcfg', None) - context['PRESYN'] = self.data['hooks'].get('presyn', None) - context['POSTSYN'] = self.data['hooks'].get('postsyn', None) - context['PREPAR'] = self.data['hooks'].get('prepar', None) - context['POSTPAR'] = self.data['hooks'].get('postpar', None) - context['PRESBIT'] = self.data['hooks'].get('prebit', None) - context['POSTBIT'] = self.data['hooks'].get('postbit', None) + context['precfg'] = self.data['hooks'].get('precfg', None) + context['postcfg'] = self.data['hooks'].get('postcfg', None) + context['presyn'] = self.data['hooks'].get('presyn', None) + context['postsyn'] = self.data['hooks'].get('postsyn', None) + context['prepar'] = self.data['hooks'].get('prepar', None) + context['postpar'] = self.data['hooks'].get('postpar', None) + context['presbit'] = self.data['hooks'].get('prebit', None) + context['postbit'] = self.data['hooks'].get('postbit', None) self._create_file('ise', 'tcl', context) return 'xtclsh ise.tcl' @@ -53,7 +53,7 @@ def _prog_prepare(self, bitstream, position): if not bitstream: basename = self.name or 'ise' bitstream = f'{basename}.bit' - context = {'BITSTREAM': bitstream, 'POSITION': position} + context = {'bitstream': bitstream, 'position': position} self._create_file('vivado-prog', 'tcl', context) return 'impact -batch impact-prog' diff --git a/pyfpga/libero.py b/pyfpga/libero.py index 7c07a6a1..45536a6d 100644 --- a/pyfpga/libero.py +++ b/pyfpga/libero.py @@ -23,29 +23,29 @@ class Libero(Project): def _make_prepare(self, steps): info = get_info(self.data.get('part', 'mpf100t-1-fcg484')) context = { - 'PROJECT': self.name or 'libero', - 'FAMILY': info['family'], - 'DEVICE': info['device'], - 'SPEED': info['speed'], - 'PACKAGE': info['package'] + 'project': self.name or 'libero', + 'family': info['family'], + 'device': info['device'], + 'speed': info['speed'], + 'package': info['package'] } for step in steps: context[step] = 1 - context['INCLUDES'] = self.data.get('includes', None) - context['FILES'] = self.data.get('files', None) - context['CONSTRAINTS'] = self.data.get('constraints', None) - context['TOP'] = self.data.get('top', None) - context['DEFINES'] = self.data.get('defines', None) - context['PARAMS'] = self.data.get('params', None) + context['includes'] = self.data.get('includes', None) + context['files'] = self.data.get('files', None) + context['constraints'] = self.data.get('constraints', None) + context['top'] = self.data.get('top', None) + context['defines'] = self.data.get('defines', None) + context['params'] = self.data.get('params', None) if 'hooks' in self.data: - context['PRECFG'] = self.data['hooks'].get('precfg', None) - context['POSTCFG'] = self.data['hooks'].get('postcfg', None) - context['PRESYN'] = self.data['hooks'].get('presyn', None) - context['POSTSYN'] = self.data['hooks'].get('postsyn', None) - context['PREPAR'] = self.data['hooks'].get('prepar', None) - context['POSTPAR'] = self.data['hooks'].get('postpar', None) - context['PRESBIT'] = self.data['hooks'].get('prebit', None) - context['POSTBIT'] = self.data['hooks'].get('postbit', None) + context['precfg'] = self.data['hooks'].get('precfg', None) + context['postcfg'] = self.data['hooks'].get('postcfg', None) + context['presyn'] = self.data['hooks'].get('presyn', None) + context['postsyn'] = self.data['hooks'].get('postsyn', None) + context['prepar'] = self.data['hooks'].get('prepar', None) + context['postpar'] = self.data['hooks'].get('postpar', None) + context['presbit'] = self.data['hooks'].get('prebit', None) + context['postbit'] = self.data['hooks'].get('postbit', None) self._create_file('libero', 'tcl', context) return 'libero SCRIPT:libero.tcl' diff --git a/pyfpga/openflow.py b/pyfpga/openflow.py index 63712a85..c8663277 100644 --- a/pyfpga/openflow.py +++ b/pyfpga/openflow.py @@ -21,28 +21,28 @@ class Openflow(Project): def _make_prepare(self, steps): info = get_info(self.data.get('part', 'hx8k-ct256')) context = { - 'PROJECT': self.name or 'openflow', - 'FAMILY': info['family'], - 'DEVICE': info['device'], - 'PACKAGE': info['package'] + 'project': self.name or 'openflow', + 'family': info['family'], + 'device': info['device'], + 'package': info['package'] } for step in steps: context[step] = 1 - context['INCLUDES'] = self.data.get('includes', None) - context['FILES'] = self.data.get('files', None) - context['CONSTRAINTS'] = self.data.get('constraints', None) - context['TOP'] = self.data.get('top', None) - context['DEFINES'] = self.data.get('defines', None) - context['PARAMS'] = self.data.get('params', None) + context['includes'] = self.data.get('includes', None) + context['files'] = self.data.get('files', None) + context['constraints'] = self.data.get('constraints', None) + context['top'] = self.data.get('top', None) + context['defines'] = self.data.get('defines', None) + context['params'] = self.data.get('params', None) if 'hooks' in self.data: - context['PRECFG'] = self.data['hooks'].get('precfg', None) - context['POSTCFG'] = self.data['hooks'].get('postcfg', None) - context['PRESYN'] = self.data['hooks'].get('presyn', None) - context['POSTSYN'] = self.data['hooks'].get('postsyn', None) - context['PREPAR'] = self.data['hooks'].get('prepar', None) - context['POSTPAR'] = self.data['hooks'].get('postpar', None) - context['PRESBIT'] = self.data['hooks'].get('prebit', None) - context['POSTBIT'] = self.data['hooks'].get('postbit', None) + context['precfg'] = self.data['hooks'].get('precfg', None) + context['postcfg'] = self.data['hooks'].get('postcfg', None) + context['presyn'] = self.data['hooks'].get('presyn', None) + context['postsyn'] = self.data['hooks'].get('postsyn', None) + context['prepar'] = self.data['hooks'].get('prepar', None) + context['postpar'] = self.data['hooks'].get('postpar', None) + context['presbit'] = self.data['hooks'].get('prebit', None) + context['postbit'] = self.data['hooks'].get('postbit', None) self._create_file('openflow', 'sh', context) return 'bash openflow.sh' @@ -51,7 +51,7 @@ def _prog_prepare(self, bitstream, position): if not bitstream: basename = self.name or 'openflow' bitstream = f'{basename}.bit' - context = {'BITSTREAM': bitstream} + context = {'bitstream': bitstream} self._create_file('openflow-prog', 'sh', context) return 'bash openflow-prog.sh' diff --git a/pyfpga/project.py b/pyfpga/project.py index 16ee090e..3ad42b71 100644 --- a/pyfpga/project.py +++ b/pyfpga/project.py @@ -226,7 +226,7 @@ def make(self, first='cfg', last='bit'): if first == last: message = steps[first] self.logger.info('Running %s', message) - selected = [step.upper() for step in keys[index[0]:index[1]+1]] + selected = keys[index[0]:index[1]+1] self._run(self._make_prepare(selected), 'make.log') def prog(self, bitstream=None, position=1): diff --git a/pyfpga/quartus.py b/pyfpga/quartus.py index 0cc5f93d..5ffa3c71 100644 --- a/pyfpga/quartus.py +++ b/pyfpga/quartus.py @@ -20,26 +20,26 @@ class Quartus(Project): def _make_prepare(self, steps): context = { - 'PROJECT': self.name or 'quartus', - 'PART': self.data.get('part', '10M50SCE144I7G') + 'project': self.name or 'quartus', + 'part': self.data.get('part', '10M50SCE144I7G') } for step in steps: context[step] = 1 - context['INCLUDES'] = self.data.get('includes', None) - context['FILES'] = self.data.get('files', None) - context['CONSTRAINTS'] = self.data.get('constraints', None) - context['TOP'] = self.data.get('top', None) - context['DEFINES'] = self.data.get('defines', None) - context['PARAMS'] = self.data.get('params', None) + context['includes'] = self.data.get('includes', None) + context['files'] = self.data.get('files', None) + context['constraints'] = self.data.get('constraints', None) + context['top'] = self.data.get('top', None) + context['defines'] = self.data.get('defines', None) + context['params'] = self.data.get('params', None) if 'hooks' in self.data: - context['PRECFG'] = self.data['hooks'].get('precfg', None) - context['POSTCFG'] = self.data['hooks'].get('postcfg', None) - context['PRESYN'] = self.data['hooks'].get('presyn', None) - context['POSTSYN'] = self.data['hooks'].get('postsyn', None) - context['PREPAR'] = self.data['hooks'].get('prepar', None) - context['POSTPAR'] = self.data['hooks'].get('postpar', None) - context['PRESBIT'] = self.data['hooks'].get('prebit', None) - context['POSTBIT'] = self.data['hooks'].get('postbit', None) + context['precfg'] = self.data['hooks'].get('precfg', None) + context['postcfg'] = self.data['hooks'].get('postcfg', None) + context['presyn'] = self.data['hooks'].get('presyn', None) + context['postsyn'] = self.data['hooks'].get('postsyn', None) + context['prepar'] = self.data['hooks'].get('prepar', None) + context['postpar'] = self.data['hooks'].get('postpar', None) + context['presbit'] = self.data['hooks'].get('prebit', None) + context['postbit'] = self.data['hooks'].get('postbit', None) self._create_file('quartus', 'tcl', context) return 'quartus_sh --script quartus.tcl' @@ -49,6 +49,6 @@ def _prog_prepare(self, bitstream, position): if not bitstream: basename = self.name or 'quartus' bitstream = f'{basename}.sof' - context = {'BITSTREAM': bitstream, 'POSITION': position} + context = {'bitstream': bitstream, 'position': position} self._create_file('quartus-prog', 'tcl', context) return 'bash quartus-prog.sh' diff --git a/pyfpga/templates/ise-prog.jinja b/pyfpga/templates/ise-prog.jinja index 1c2203ee..abf4cb54 100644 --- a/pyfpga/templates/ise-prog.jinja +++ b/pyfpga/templates/ise-prog.jinja @@ -10,51 +10,51 @@ cleancablelock {# ------------------------------------------------------------------------- #} -{% if FPGA %} +{% if fpga %} setMode -bs setCable -port auto Identify -inferir -assignFile -p {{ POSITION }} -file {{ BITSTREAM }} -Program -p {{ POSITION }} +assignFile -p {{ position }} -file {{ bitstream }} +Program -p {{ position }} {% endif %} {# ------------------------------------------------------------------------- #} -{% if SPI %} +{% if spi %} setMode -pff -addConfigDevice -name {{ NAME }} -path . +addConfigDevice -name {{ name }} -path . setSubmode -pffspi addDesign -version 0 -name 0 addDeviceChain -index 0 -addDevice -p 1 -file {{ BITSTREAM }} +addDevice -p 1 -file {{ bitstream }} generate -generic setMode -bs setCable -port auto Identify -attachflash -position {{ POSITION }} -spi {{ NAME }} -assignfiletoattachedflash -position {{ POSITION }} -file ./{{ NAME }}.mcs -Program -p {{ POSITION }} -dataWidth {{ WIDTH }} -spionly -e -v -loadfpga +attachflash -position {{ position }} -spi {{ name }} +assignfiletoattachedflash -position {{ position }} -file ./{{ name }}.mcs +Program -p {{ position }} -dataWidth {{ width }} -spionly -e -v -loadfpga {% endif %} {# ------------------------------------------------------------------------- #} -{% if BPI %} +{% if bpi %} setMode -pff -addConfigDevice -name {{ NAME }} -path . +addConfigDevice -name {{ name }} -path . setSubmode -pffbpi addDesign -version 0 -name 0 addDeviceChain -index 0 -setAttribute -configdevice -attr flashDataWidth -value {{ WIDTH }} -addDevice -p 1 -file {{ BITSTREAM }} +setAttribute -configdevice -attr flashDataWidth -value {{ width }} +addDevice -p 1 -file {{ bitstream }} generate -generic setMode -bs setCable -port auto Identify -attachflash -position {{ POSITION }} -bpi {{ NAME }} -assignfiletoattachedflash -position {{ POSITION }} -file ./{{ NAME }}.mcs -Program -p {{ POSITION }} -dataWidth {{ WIDTH }} -rs1 NONE -rs0 NONE -bpionly -e -v -loadfpga +attachflash -position {{ position }} -bpi {{ name }} +assignfiletoattachedflash -position {{ position }} -file ./{{ name }}.mcs +Program -p {{ position }} -dataWidth {{ width }} -rs1 NONE -rs0 NONE -bpionly -e -v -loadfpga {% endif %} {# ------------------------------------------------------------------------- #} diff --git a/pyfpga/templates/ise.jinja b/pyfpga/templates/ise.jinja index 6af287ca..eec1c970 100644 --- a/pyfpga/templates/ise.jinja +++ b/pyfpga/templates/ise.jinja @@ -6,26 +6,26 @@ # #} -{% if CFG %}# Project configuration ------------------------------------------------------- +{% if cfg %}# Project configuration ------------------------------------------------------- -if { [ file exists {{ PROJECT }}.xise ] } { file delete {{ PROJECT }}.xise } -project new {{ PROJECT }}.xise -project set family {{ FAMILY }} -project set device {{ DEVICE }} -project set package {{ PACKAGE }} -project set speed -{{ SPEED }} +if { [ file exists {{ project }}.xise ] } { file delete {{ project }}.xise } +project new {{ project }}.xise +project set family {{ family }} +project set device {{ device }} +project set package {{ package }} +project set speed -{{ speed }} -{{ PRECFG }} +{{ precfg }} -{% if FILES %}# Files inclusion -{% for name, attr in FILES.items() %} +{% if files %}# Files inclusion +{% for name, attr in files.items() %} {% if 'lib' in attr %}lib_vhdl new {{ attr.lib }}{% endif %} xfile add {{ name }}{% if 'lib' in attr %} -lib_vhdl {{ attr.lib }}{% endif %} {% endfor %} {% endif %} -{% if CONSTRAINTS %}# Constraints inclusion -{% for name, attr in CONSTRAINTS.items() %} +{% if constraints %}# Constraints inclusion +{% for name, attr in constraints.items() %} xfile add {{ name }} {% if name.endswith('.xcf') %} project set "Synthesis Constraints File" "{{ name }}" -process "Synthesize - XST" @@ -33,35 +33,35 @@ project set "Synthesis Constraints File" "{{ name }}" -process "Synthesize - XST {% endfor %} {% endif %} -{% if TOP %}# Top-level specification -project set top {{ TOP }} +{% if top %}# Top-level specification +project set top {{ top }} {% endif %} -{% if INCLUDES %}# Verilog Includes -project set "Verilog Include Directories" "{{ INCLUDES | join('|') }}" -process "Synthesize - XST" +{% if includes %}# Verilog Includes +project set "Verilog Include Directories" "{{ includes | join('|') }}" -process "Synthesize - XST" {% endif %} -{% if DEFINES %}# Verilog Defines -project set "Verilog Macros" "{{ DEFINES.items() | map('join', '=') | join(' | ') }}" -process "Synthesize - XST" +{% if defines %}# Verilog Defines +project set "Verilog Macros" "{{ defines.items() | map('join', '=') | join(' | ') }}" -process "Synthesize - XST" {% endif %} -{% if PARAMS %}# Verilog Parameters / VHDL Generics -project set "Generics, Parameters" "{{ PARAMS.items() | map('join', '=') | join(' ') }}" -process "Synthesize - XST" +{% if params %}# Verilog Parameters / VHDL Generics +project set "Generics, Parameters" "{{ params.items() | map('join', '=') | join(' ') }}" -process "Synthesize - XST" {% endif %} -{{ POSTCFG }} +{{ postcfg }} project close {% endif %} -{% if SYN or PAR or BIT %}# Design flow ----------------------------------------------------------------- +{% if syn or par or bit %}# Design flow ----------------------------------------------------------------- -project open {{ PROJECT }}.xise +project open {{ project }}.xise -{% if SYN %}# Synthesis +{% if syn %}# Synthesis -{{ PRESYN }} +{{ presyn }} # PRESYNTH #project set top_level_module_type "EDIF" @@ -69,13 +69,13 @@ project clean process run "Synthesize" if { [process get "Synthesize" status] == "errors" } { exit 1 } -{{ POSTSYN }} +{{ postsyn }} {% endif %} -{% if PAR %}# Place and Route +{% if par %}# Place and Route -{{ PREPAR }} +{{ prepar }} process run "Translate" if { [process get "Translate" status] == "errors" } { exit 1 } @@ -84,19 +84,19 @@ if { [process get "Map" status] == "errors" } { exit 1 } process run "Place & Route" if { [process get "Place & Route" status] == "errors" } { exit 1 } -{{ POSTPAR }} +{{ postpar }} {% endif %} -{% if BIT %}# Bitstream generation +{% if bit %}# Bitstream generation -{{ PREBIT }} +{{ prebit }} process run "Generate Programming File" if { [process get "Generate Programming File" status] == "errors" } { exit 1 } -catch { file rename -force {{ TOP }}.bit {{ PROJECT }}.bit } +catch { file rename -force {{ top }}.bit {{ project }}.bit } -{{ POSTBIT }} +{{ postbit }} {% endif %} diff --git a/pyfpga/templates/libero.jinja b/pyfpga/templates/libero.jinja index a4be188f..77176050 100644 --- a/pyfpga/templates/libero.jinja +++ b/pyfpga/templates/libero.jinja @@ -6,40 +6,40 @@ # #} -{% if CFG %}# Project configuration ------------------------------------------------------- +{% if cfg %}# Project configuration ------------------------------------------------------- -if { [ file exists {{ PROJECT }} ] } { file delete -force -- {{ PROJECT }} } -new_project -name {{ PROJECT }} -location {libero} -hdl {VERILOG} -family {SmartFusion2} -set_device -family {{ FAMILY }} -die {{ DEVICE }} -package {{ PACKAGE }} -speed {{ SPEED }} +if { [ file exists {{ project }} ] } { file delete -force -- {{ project }} } +new_project -name {{ project }} -location {libero} -hdl {VERILOG} -family {SmartFusion2} +set_device -family {{ family }} -die {{ device }} -package {{ package }} -speed {{ speed }} -{{ PRECFG }} +{{ precfg }} -{% if INCLUDES %}# Verilog Includes (Libero) -set_global_include_path_order -paths "{{ INCLUDES | join(' ') }}" +{% if includes %}# Verilog Includes (Libero) +set_global_include_path_order -paths "{{ includes | join(' ') }}" {% endif %} -{% if FILES %}# Files inclusion -{% for name, attr in FILES.items() %} +{% if files %}# Files inclusion +{% for name, attr in files.items() %} create_links -hdl_source {{ name }}{% if 'lib' in attr %} -library {{ attr.lib }}{% endif %} {% endfor %} {% endif %} -{% if CONSTRAINTS %}# Constraints inclusion -{% for name, attr in CONSTRAINTS.items() %} +{% if constraints %}# Constraints inclusion +{% for name, attr in constraints.items() %} create_links {% if name.endswith('.sdc') %}-sdc{% else %}-io_pdc{% endif %} {{ name }} {% endfor %} {% endif %} build_design_hierarchy -{% if TOP %}# Top-level specification -set_root {{ TOP }} +{% if top %}# Top-level specification +set_root {{ top }} {% endif %} -{% if CONSTRAINTS %}# Constraints configuration +{% if constraints %}# Constraints configuration {% set sdc_files = [] %} {% set pdc_files = [] %} -{% for name, attr in CONSTRAINTS.items() %} +{% for name, attr in constraints.items() %} {% if name.endswith('.sdc') %} {% set _ = sdc_files.append(name) %} {% endif %} @@ -47,27 +47,27 @@ set_root {{ TOP }} {% endfor %} {% endif %} -{% if sdc_files %}organize_tool_files -tool {SYNTHESIZE} -file {{ sdc_files | join(' -file ') }} -module {{ TOP }} -input_type {constraint}{% endif %} -{% if pdc_files %}organize_tool_files -tool {PLACEROUTE} -file {{ pdc_files | join(' -file ') }} -module {{ TOP }} -input_type {constraint}{% endif %} -{% if sdc_files %}organize_tool_files -tool {VERIFYTIMING} -file {{ sdc_files | join(' -file ') }} -module {{ TOP }} -input_type {constraint}{% endif %} +{% if sdc_files %}organize_tool_files -tool {SYNTHESIZE} -file {{ sdc_files | join(' -file ') }} -module {{ top }} -input_type {constraint}{% endif %} +{% if pdc_files %}organize_tool_files -tool {PLACEROUTE} -file {{ pdc_files | join(' -file ') }} -module {{ top }} -input_type {constraint}{% endif %} +{% if sdc_files %}organize_tool_files -tool {VERIFYTIMING} -file {{ sdc_files | join(' -file ') }} -module {{ top }} -input_type {constraint}{% endif %} -{% if INCLUDES or DEFINES or PARAMS %}# Synopsys configuration +{% if includes or defines or params %}# Synopsys configuration configure_tool -name {SYNTHESIZE} -params {SYNPLIFY_OPTIONS: -{% if INCLUDES %}# Verilog Includes (Synopsys) -{% for INCLUDE in INCLUDES %} - set_option -include_path "{{ INCLUDE }}" +{% if includes %}# Verilog Includes (Synopsys) +{% for include in includes %} + set_option -include_path "{{ include }}" {% endfor %} {% endif %} -{% if DEFINES %}# Verilog Defines (Synopsys) -{% for key, value in DEFINES.items() %} +{% if defines %}# Verilog Defines (Synopsys) +{% for key, value in defines.items() %} set_option -hdl_define -set {{ key }}={{ value }} {% endfor %} {% endif %} -{% if PARAMS %}# Verilog Parameters / VHDL Generics (Synopsys) -{% for key, value in PARAMS.items() %} +{% if params %}# Verilog Parameters / VHDL Generics (Synopsys) +{% for key, value in params.items() %} set_option -hdl_param -set {{ key }}={{ value }} {% endfor %} {% endif %} @@ -75,44 +75,44 @@ configure_tool -name {SYNTHESIZE} -params {SYNPLIFY_OPTIONS: } {% endif %} -{{ POSTCFG }} +{{ postcfg }} close_project {% endif %} -{% if SYN or PAR or BIT %}# Design flow ----------------------------------------------------------------- +{% if syn or par or bit %}# Design flow ----------------------------------------------------------------- -open_project {{ PROJECT }}/{{ PROJECT }}.prjx +open_project {{ project }}/{{ project }}.prjx -{% if SYN %}# Synthesis +{% if syn %}# Synthesis -{{ PRESYN }} +{{ presyn }} run_tool -name {SYNTHESIZE} -{{ POSTSYN }} +{{ postsyn }} {% endif %} -{% if PAR %}# Place and Route +{% if par %}# Place and Route -{{ PREPAR }} +{{ prepar }} run_tool -name {PLACEROUTE} run_tool -name {VERIFYTIMING} -{{ POSTPAR }} +{{ postpar }} {% endif %} -{% if BIT %}# Bitstream generation +{% if bit %}# Bitstream generation -{{ PREBIT }} +{{ prebit }} run_tool -name {GENERATEPROGRAMMINGFILE} -{{ POSTBIT }} +{{ postbit }} {% endif %} diff --git a/pyfpga/templates/openflow-prog.jinja b/pyfpga/templates/openflow-prog.jinja index 81942426..47fad80d 100644 --- a/pyfpga/templates/openflow-prog.jinja +++ b/pyfpga/templates/openflow-prog.jinja @@ -9,10 +9,10 @@ set -e DOCKER="docker run --user $(id -u):$(id -g) --rm -v $HOME:$HOME -w $PWD" -{% if FAMILY == 'ice40' %} -$DOCKER --device /dev/bus/usb hdlc/prog iceprog {{ PROJECT }}.bit +{% if family == 'ice40' %} +$DOCKER --device /dev/bus/usb hdlc/prog iceprog {{ project }}.bit {% endif %} -{% 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" +{% 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" {% endif %} diff --git a/pyfpga/templates/openflow.jinja b/pyfpga/templates/openflow.jinja index 02e57662..5aa9b29b 100644 --- a/pyfpga/templates/openflow.jinja +++ b/pyfpga/templates/openflow.jinja @@ -10,21 +10,21 @@ set -e DOCKER="docker run --user $(id -u):$(id -g) --rm -v $HOME:$HOME -w $PWD" -{% if SYN %}# Synthesis +{% if syn %}# Synthesis $DOCKER hdlc/ghdl:yosys /bin/bash -c " -{{ PRESYN }} +{{ presyn }} yosys -Q -m ghdl -p ' -{% if INCLUDES %} -verilog_defaults -add{% for path in INCLUDES %} -I{{ path }}{% endfor %} +{% if includes %} +verilog_defaults -add{% for path in includes %} -I{{ path }}{% endfor %} {% endif %} -{% if DEFINES %} -verilog_defines{% for key, value in DEFINES.items() %} -D{{ key }}={{ value }}{% endfor %} +{% if defines %} +verilog_defines{% for key, value in defines.items() %} -D{{ key }}={{ value }}{% endfor %} {% endif %} -{% if FILES %} -{% for name, attr in FILES.items() %} +{% if files %} +{% for name, attr in files.items() %} {% if attr.hdl == "vlog" %} read_verilog -defer {{ name }} {% elif attr.hdl == "slog" %} @@ -33,14 +33,14 @@ read_verilog -defer -sv {{ name }} {% endfor %} {% endif %} -{% if PARAMS %} -chparam{% for key, value in PARAMS.items() %} -set {{ key }} {{ value }}{% endfor %} +{% if params %} +chparam{% for key, value in params.items() %} -set {{ key }} {{ value }}{% endfor %} {% endif %} -synth -top {{ TOP }} -synth_{{ FAMILY }} -top {{ TOP }} -json {{ PROJECT }}.json +synth -top {{ top }} +synth_{{ family }} -top {{ top }} -json {{ project }}.json ' -{{ POSTSYN }} +{{ postsyn }} " {% endif %} @@ -63,54 +63,54 @@ synth_{{ FAMILY }} -top {{ TOP }} -json {{ PROJECT }}.json #fi #} -{% if PAR %}# Place and Route +{% if par %}# Place and Route -CONSTRAINTS="{{ CONSTRAINTS | join(' ') }}" +CONSTRAINTS="{{ constraints | join(' ') }}" -{% if FAMILY == 'ice40' %} +{% if family == 'ice40' %} if [ -n "$CONSTRAINTS" ]; then cat $CONSTRAINTS > constraints.pcf CONSTRAINT="--pcf constraints.pcf" fi $DOCKER hdlc/nextpnr:ice40 /bin/bash -c " -{{ PREPAR }} -nextpnr-ice40 --{{ DEVICE }} --package {{ PACKAGE }} $CONSTRAINT --json {{ PROJECT }}.json --asc {{ PROJECT }}.asc -{{ POSTPAR }} +{{ prepar }} +nextpnr-ice40 --{{ device }} --package {{ package }} $CONSTRAINT --json {{ project }}.json --asc {{ project }}.asc +{{ postpar }} " $DOCKER hdlc/icestorm /bin/bash -c " -icetime -d {{ DEVICE }} -mtr {{ PROJECT }}.rpt {{ PROJECT }}.asc +icetime -d {{ device }} -mtr {{ project }}.rpt {{ project }}.asc " {% endif %} -{% if FAMILY == 'ecp5' %} +{% if family == 'ecp5' %} if [ -n "$CONSTRAINTS" ]; then cat $CONSTRAINTS > constraints.lpf CONSTRAINT="--lpf constraints.lpf" fi $DOCKER hdlc/nextpnr:ecp5 /bin/bash -c " -{{ PREPAR }} -nextpnr-ecp5 --{{ DEVICE }} --package {{ PACKAGE }} $CONSTRAINT --json {{ PROJECT }}.json --textcfg {{ PROJECT }}.config -{{ POSTPAR }} +{{ prepar }} +nextpnr-ecp5 --{{ device }} --package {{ package }} $CONSTRAINT --json {{ project }}.json --textcfg {{ project }}.config +{{ postpar }} " {% endif %} {% endif %} -{% if BIT %}# Bitstream generation +{% if bit %}# Bitstream generation -{% if FAMILY == 'ice40' %} +{% if family == 'ice40' %} $DOCKER hdlc/icestorm /bin/bash -c " -{{ PREBIT }} -icepack {{ PROJECT }}.asc {{ PROJECT }}.bit -{{ POSTBIT }} +{{ prebit }} +icepack {{ project }}.asc {{ project }}.bit +{{ postbit }} " {% endif %} -{% if FAMILY == 'ecp5' %} +{% if family == 'ecp5' %} $DOCKER hdlc/prjtrellis /bin/bash -c " -{{ PREBIT }} -ecppack --svf {{ PROJECT }}.svf {{ PROJECT }}.config {{ PROJECT }}.bit -{{ POSTBIT }} +{{ prebit }} +ecppack --svf {{ project }}.svf {{ project }}.config {{ project }}.bit +{{ postbit }} " {% endif %} diff --git a/pyfpga/templates/quartus-prog.jinja b/pyfpga/templates/quartus-prog.jinja index 23786f22..0d7af942 100644 --- a/pyfpga/templates/quartus-prog.jinja +++ b/pyfpga/templates/quartus-prog.jinja @@ -12,4 +12,4 @@ echo "$RESULT" # cable = re.match(r"1\) (.*) \[", result).groups()[0] CABLE=$(echo "$RESULT" | awk -F '1\\) | \\[' '/1\)/ {print $2}') -quartus_pgm -c $CABLE --mode jtag -o "p;{{ BITSTREAM }}@{{ POSITION }}" +quartus_pgm -c $CABLE --mode jtag -o "p;{{ bitstream }}@{{ position }}" diff --git a/pyfpga/templates/quartus.jinja b/pyfpga/templates/quartus.jinja index dcbcb5d9..7ffb78a6 100644 --- a/pyfpga/templates/quartus.jinja +++ b/pyfpga/templates/quartus.jinja @@ -6,16 +6,16 @@ # #} -{% if CFG %}# Project configuration ------------------------------------------------------- +{% if cfg %}# Project configuration ------------------------------------------------------- package require ::quartus::project -project_new {{ PROJECT }} -overwrite -set_global_assignment -name DEVICE {{ PART }} +project_new {{ project }} -overwrite +set_global_assignment -name DEVICE {{ part }} -{{ PRECFG }} +{{ precfg }} -{% if FILES %}# Files inclusion -{% for name, attr in FILES.items() %} +{% if files %}# Files inclusion +{% for name, attr in files.items() %} {% if attr.hdl == "vhdl" %} set_global_assignment -name VHDL_FILE {{ name }} {% if 'lib' in attr %} -library {{ attr.lib }}{% endif %} {% elif attr.hdl == "vlog" %} @@ -26,8 +26,8 @@ set_global_assignment -name SYSTEMVERILOG_FILE {{ name }} {% endfor %} {% endif %} -{% if CONSTRAINTS %}# Constraints inclusion -{% for name, attr in CONSTRAINTS.items() %} +{% if constraints %}# Constraints inclusion +{% for name, attr in constraints.items() %} {% if name.endswith('.sdc') %} set_global_assignment -name SDC_FILE {{ name }} {% else %} @@ -36,68 +36,68 @@ source {{ name }} {% endfor %} {% endif %} -{% if TOP %}# Top-level specification -set_global_assignment -name TOP_LEVEL_ENTITY {{ TOP }} +{% if top %}# Top-level specification +set_global_assignment -name TOP_LEVEL_ENTITY {{ top }} {% endif %} -{% if INCLUDES %}# Verilog Includes -{% for INCLUDE in INCLUDES %} -set_global_assignment -name SEARCH_PATH {{ INCLUDE }} +{% if includes %}# Verilog Includes +{% for include in includes %} +set_global_assignment -name SEARCH_PATH {{ include }} {% endfor %} {% endif %} -{% if DEFINES %}# Verilog Defines -{% for key, value in DEFINES.items() %} +{% if defines %}# Verilog Defines +{% for key, value in defines.items() %} set_global_assignment -name VERILOG_MACRO {{ key }}={{ value }} {% endfor %} {% endif %} -{% if PARAMS %}# Verilog Parameters / VHDL Generics -{% for key, value in PARAMS.items() %} +{% if params %}# Verilog Parameters / VHDL Generics +{% for key, value in params.items() %} set_parameter -name {{ key }} {{ value }} {% endfor %} {% endif %} -{{ POSTCFG }} +{{ postcfg }} project_close {% endif %} -{% if SYN or PAR or BIT %}# Design flow ----------------------------------------------------------------- +{% if syn or par or bit %}# Design flow ----------------------------------------------------------------- package require ::quartus::flow -project_open -force {{ PROJECT }}.qpf +project_open -force {{ project }}.qpf # set_global_assignment -name NUM_PARALLEL_PROCESSORS ALL -{% if SYN %}# Synthesis +{% if syn %}# Synthesis -{{ PRESYN }} +{{ presyn }} execute_module -tool map -{{ POSTSYN }} +{{ postsyn }} {% endif %} -{% if PAR %}# Place and Route +{% if par %}# Place and Route -{{ PREPAR }} +{{ prepar }} execute_module -tool fit execute_module -tool sta -{{ POSTPAR }} +{{ postpar }} {% endif %} -{% if BIT %}# Bitstream generation +{% if bit %}# Bitstream generation -{{ PREBIT }} +{{ prebit }} execute_module -tool asm -{{ POSTBIT }} +{{ postbit }} {% endif %} diff --git a/pyfpga/templates/vivado-prog.jinja b/pyfpga/templates/vivado-prog.jinja index 72ccb91d..eeeab0ca 100644 --- a/pyfpga/templates/vivado-prog.jinja +++ b/pyfpga/templates/vivado-prog.jinja @@ -11,5 +11,5 @@ connect_hw_server open_hw_target puts [get_hw_devices] set obj [lindex [get_hw_devices [current_hw_device]] 0] -set_property PROGRAM.FILE {{ BITSTREAM }} $obj +set_property PROGRAM.FILE {{ bitstream }} $obj program_hw_devices $obj diff --git a/pyfpga/templates/vivado.jinja b/pyfpga/templates/vivado.jinja index 19a53aee..6aa4e456 100644 --- a/pyfpga/templates/vivado.jinja +++ b/pyfpga/templates/vivado.jinja @@ -6,24 +6,24 @@ # #} -{% if CFG %}# Project configuration ------------------------------------------------------- +{% if cfg %}# Project configuration ------------------------------------------------------- -create_project -force {{ PROJECT }} +create_project -force {{ project }} set_property SOURCE_MGMT_MODE None [current_project] set_property STEPS.SYNTH_DESIGN.ARGS.ASSERT true [get_runs synth_1] -set_property PART {{ PART }} [current_project] +set_property PART {{ part }} [current_project] -{{ PRECFG }} +{{ precfg }} -{% if FILES %}# Files inclusion -{% for name, attr in FILES.items() %} +{% if files %}# Files inclusion +{% for name, attr in files.items() %} add_file {{ name }} {% if 'lib' in attr %}set_property library {{ attr.lib }} [get_files {{ name }}]{% endif %} {% endfor %} {% endif %} -{% if CONSTRAINTS %}# Constraints inclusion -{% for name, attr in CONSTRAINTS.items() %} +{% if constraints %}# Constraints inclusion +{% for name, attr in constraints.items() %} add_file -fileset constrs_1 {{ name }} {% if attr == "syn" %} set_property USED_IN_IMPLEMENTATION FALSE [get_files {{ name }}] @@ -34,35 +34,35 @@ set_property USED_IN_SYNTHESIS FALSE [get_files {{ name }}] {% endfor %} {% endif %} -{% if TOP %}# Top-level specification -set_property TOP {{ TOP }} [current_fileset] +{% if top %}# Top-level specification +set_property TOP {{ top }} [current_fileset] {% endif %} -{% if INCLUDES %}# Verilog Includes -set_property INCLUDE_DIRS { {{ INCLUDES | join(' ') }} } [current_fileset] +{% if includes %}# Verilog Includes +set_property INCLUDE_DIRS { {{ includes | join(' ') }} } [current_fileset] {% endif %} -{% if DEFINES %}# Verilog Defines -set_property VERILOG_DEFINE { {{ DEFINES.items() | map('join', '=') | join(' ') }} } [current_fileset] +{% if defines %}# Verilog Defines +set_property VERILOG_DEFINE { {{ defines.items() | map('join', '=') | join(' ') }} } [current_fileset] {% endif %} -{% if PARAMS %}# Verilog Parameters / VHDL Generics -set_property GENERIC { {{ PARAMS.items() | map('join', '=') | join(' ') }} } -objects [get_filesets sources_1] +{% if params %}# Verilog Parameters / VHDL Generics +set_property GENERIC { {{ params.items() | map('join', '=') | join(' ') }} } -objects [get_filesets sources_1] {% endif %} -{{ POSTCFG }} +{{ postcfg }} close_project {% endif %} -{% if SYN or PAR or BIT %}# Design flow ----------------------------------------------------------------- +{% if syn or par or bit %}# Design flow ----------------------------------------------------------------- -open_project {{ PROJECT }} +open_project {{ project }} -{% if SYN %}# Synthesis +{% if syn %}# Synthesis -{{ PRESYN }} +{{ presyn }} # PRESYNTH # set_property DESIGN_MODE GateLvl [current_fileset] @@ -73,13 +73,13 @@ wait_on_run synth_1 if { [get_property STATUS [get_runs synth_1]] ne "synth_design Complete!" } { exit 1 } -{{ POSTSYN }} +{{ postsyn }} {% endif %} -{% if PAR %}# Place and Route +{% if par %}# Place and Route -{{ PREPAR }} +{{ prepar }} reset_run impl_1 launch_runs impl_1 @@ -87,19 +87,19 @@ wait_on_run impl_1 #report_property [get_runs impl_1] if { [get_property STATUS [get_runs impl_1]] ne "route_design Complete!" } { exit 1 } -{{ POSTPAR }} +{{ postpar }} {% endif %} -{% if BIT %}# Bitstream generation +{% if bit %}# Bitstream generation -{{ PREBIT }} +{{ prebit }} open_run impl_1 -write_bitstream -force {{ PROJECT }} -write_debug_probes -force -quiet {{ PROJECT }}.ltx +write_bitstream -force {{ project }} +write_debug_probes -force -quiet {{ project }}.ltx -{{ POSTBIT }} +{{ postbit }} {% endif %} diff --git a/pyfpga/vivado.py b/pyfpga/vivado.py index 3cc39d6e..b5f59f4e 100644 --- a/pyfpga/vivado.py +++ b/pyfpga/vivado.py @@ -19,26 +19,26 @@ class Vivado(Project): def _make_prepare(self, steps): context = { - 'PROJECT': self.name or 'vivado', - 'PART': self.data.get('part', 'xc7k160t-3-fbg484') + 'project': self.name or 'vivado', + 'part': self.data.get('part', 'xc7k160t-3-fbg484') } for step in steps: context[step] = 1 - context['INCLUDES'] = self.data.get('includes', None) - context['FILES'] = self.data.get('files', None) - context['CONSTRAINTS'] = self.data.get('constraints', None) - context['TOP'] = self.data.get('top', None) - context['DEFINES'] = self.data.get('defines', None) - context['PARAMS'] = self.data.get('params', None) + context['includes'] = self.data.get('includes', None) + context['files'] = self.data.get('files', None) + context['constraints'] = self.data.get('constraints', None) + context['top'] = self.data.get('top', None) + context['defines'] = self.data.get('defines', None) + context['params'] = self.data.get('params', None) if 'hooks' in self.data: - context['PRECFG'] = self.data['hooks'].get('precfg', None) - context['POSTCFG'] = self.data['hooks'].get('postcfg', None) - context['PRESYN'] = self.data['hooks'].get('presyn', None) - context['POSTSYN'] = self.data['hooks'].get('postsyn', None) - context['PREPAR'] = self.data['hooks'].get('prepar', None) - context['POSTPAR'] = self.data['hooks'].get('postpar', None) - context['PRESBIT'] = self.data['hooks'].get('prebit', None) - context['POSTBIT'] = self.data['hooks'].get('postbit', None) + context['precfg'] = self.data['hooks'].get('precfg', None) + context['postcfg'] = self.data['hooks'].get('postcfg', None) + context['presyn'] = self.data['hooks'].get('presyn', None) + context['postsyn'] = self.data['hooks'].get('postsyn', None) + context['prepar'] = self.data['hooks'].get('prepar', None) + context['postpar'] = self.data['hooks'].get('postpar', None) + context['presbit'] = self.data['hooks'].get('prebit', None) + context['postbit'] = self.data['hooks'].get('postbit', None) self._create_file('vivado', 'tcl', context) return 'vivado -mode batch -notrace -quiet -source vivado.tcl' From 9e63f29f18f3b4935cf7596e612f7d42607af7d4 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Tue, 9 Jul 2024 00:25:15 -0300 Subject: [PATCH 164/254] Modify how the flow steps and hooks are used on the templates --- pyfpga/ise.py | 13 ++----------- pyfpga/libero.py | 13 ++----------- pyfpga/openflow.py | 13 ++----------- pyfpga/quartus.py | 13 ++----------- pyfpga/templates/ise.jinja | 26 +++++++++++++------------- pyfpga/templates/libero.jinja | 26 +++++++++++++------------- pyfpga/templates/openflow.jinja | 26 +++++++++++++------------- pyfpga/templates/quartus.jinja | 26 +++++++++++++------------- pyfpga/templates/vivado.jinja | 27 +++++++++++++-------------- pyfpga/vivado.py | 13 ++----------- 10 files changed, 75 insertions(+), 121 deletions(-) diff --git a/pyfpga/ise.py b/pyfpga/ise.py index 86e1e05a..b2d102a0 100644 --- a/pyfpga/ise.py +++ b/pyfpga/ise.py @@ -29,23 +29,14 @@ def _make_prepare(self, steps): 'speed': info['speed'], 'package': info['package'] } - for step in steps: - context[step] = 1 + context['steps'] = steps context['includes'] = self.data.get('includes', None) context['files'] = self.data.get('files', None) context['constraints'] = self.data.get('constraints', None) context['top'] = self.data.get('top', None) context['defines'] = self.data.get('defines', None) context['params'] = self.data.get('params', None) - if 'hooks' in self.data: - context['precfg'] = self.data['hooks'].get('precfg', None) - context['postcfg'] = self.data['hooks'].get('postcfg', None) - context['presyn'] = self.data['hooks'].get('presyn', None) - context['postsyn'] = self.data['hooks'].get('postsyn', None) - context['prepar'] = self.data['hooks'].get('prepar', None) - context['postpar'] = self.data['hooks'].get('postpar', None) - context['presbit'] = self.data['hooks'].get('prebit', None) - context['postbit'] = self.data['hooks'].get('postbit', None) + context['hooks'] = self.data.get('hooks', None) self._create_file('ise', 'tcl', context) return 'xtclsh ise.tcl' diff --git a/pyfpga/libero.py b/pyfpga/libero.py index 45536a6d..00014dc8 100644 --- a/pyfpga/libero.py +++ b/pyfpga/libero.py @@ -29,23 +29,14 @@ def _make_prepare(self, steps): 'speed': info['speed'], 'package': info['package'] } - for step in steps: - context[step] = 1 + context['steps'] = steps context['includes'] = self.data.get('includes', None) context['files'] = self.data.get('files', None) context['constraints'] = self.data.get('constraints', None) context['top'] = self.data.get('top', None) context['defines'] = self.data.get('defines', None) context['params'] = self.data.get('params', None) - if 'hooks' in self.data: - context['precfg'] = self.data['hooks'].get('precfg', None) - context['postcfg'] = self.data['hooks'].get('postcfg', None) - context['presyn'] = self.data['hooks'].get('presyn', None) - context['postsyn'] = self.data['hooks'].get('postsyn', None) - context['prepar'] = self.data['hooks'].get('prepar', None) - context['postpar'] = self.data['hooks'].get('postpar', None) - context['presbit'] = self.data['hooks'].get('prebit', None) - context['postbit'] = self.data['hooks'].get('postbit', None) + context['hooks'] = self.data.get('hooks', None) self._create_file('libero', 'tcl', context) return 'libero SCRIPT:libero.tcl' diff --git a/pyfpga/openflow.py b/pyfpga/openflow.py index c8663277..feb7a41e 100644 --- a/pyfpga/openflow.py +++ b/pyfpga/openflow.py @@ -26,23 +26,14 @@ def _make_prepare(self, steps): 'device': info['device'], 'package': info['package'] } - for step in steps: - context[step] = 1 + context['steps'] = steps context['includes'] = self.data.get('includes', None) context['files'] = self.data.get('files', None) context['constraints'] = self.data.get('constraints', None) context['top'] = self.data.get('top', None) context['defines'] = self.data.get('defines', None) context['params'] = self.data.get('params', None) - if 'hooks' in self.data: - context['precfg'] = self.data['hooks'].get('precfg', None) - context['postcfg'] = self.data['hooks'].get('postcfg', None) - context['presyn'] = self.data['hooks'].get('presyn', None) - context['postsyn'] = self.data['hooks'].get('postsyn', None) - context['prepar'] = self.data['hooks'].get('prepar', None) - context['postpar'] = self.data['hooks'].get('postpar', None) - context['presbit'] = self.data['hooks'].get('prebit', None) - context['postbit'] = self.data['hooks'].get('postbit', None) + context['hooks'] = self.data.get('hooks', None) self._create_file('openflow', 'sh', context) return 'bash openflow.sh' diff --git a/pyfpga/quartus.py b/pyfpga/quartus.py index 5ffa3c71..1f4cb2e5 100644 --- a/pyfpga/quartus.py +++ b/pyfpga/quartus.py @@ -23,23 +23,14 @@ def _make_prepare(self, steps): 'project': self.name or 'quartus', 'part': self.data.get('part', '10M50SCE144I7G') } - for step in steps: - context[step] = 1 + context['steps'] = steps context['includes'] = self.data.get('includes', None) context['files'] = self.data.get('files', None) context['constraints'] = self.data.get('constraints', None) context['top'] = self.data.get('top', None) context['defines'] = self.data.get('defines', None) context['params'] = self.data.get('params', None) - if 'hooks' in self.data: - context['precfg'] = self.data['hooks'].get('precfg', None) - context['postcfg'] = self.data['hooks'].get('postcfg', None) - context['presyn'] = self.data['hooks'].get('presyn', None) - context['postsyn'] = self.data['hooks'].get('postsyn', None) - context['prepar'] = self.data['hooks'].get('prepar', None) - context['postpar'] = self.data['hooks'].get('postpar', None) - context['presbit'] = self.data['hooks'].get('prebit', None) - context['postbit'] = self.data['hooks'].get('postbit', None) + context['hooks'] = self.data.get('hooks', None) self._create_file('quartus', 'tcl', context) return 'quartus_sh --script quartus.tcl' diff --git a/pyfpga/templates/ise.jinja b/pyfpga/templates/ise.jinja index eec1c970..b44e003a 100644 --- a/pyfpga/templates/ise.jinja +++ b/pyfpga/templates/ise.jinja @@ -6,7 +6,7 @@ # #} -{% if cfg %}# Project configuration ------------------------------------------------------- +{% if 'cfg' in steps %}# Project configuration ------------------------------------------------------- if { [ file exists {{ project }}.xise ] } { file delete {{ project }}.xise } project new {{ project }}.xise @@ -15,7 +15,7 @@ project set device {{ device }} project set package {{ package }} project set speed -{{ speed }} -{{ precfg }} +{{ hooks.precfg | join('\n') }} {% if files %}# Files inclusion {% for name, attr in files.items() %} @@ -49,19 +49,19 @@ project set "Verilog Macros" "{{ defines.items() | map('join', '=') | join(' | ' project set "Generics, Parameters" "{{ params.items() | map('join', '=') | join(' ') }}" -process "Synthesize - XST" {% endif %} -{{ postcfg }} +{{ hooks.postcfg | join('\n') }} project close {% endif %} -{% if syn or par or bit %}# Design flow ----------------------------------------------------------------- +{% if 'syn' in steps or 'par' in steps or 'bit' in steps %}# Design flow ----------------------------------------------------------------- project open {{ project }}.xise -{% if syn %}# Synthesis +{% if 'syn' in steps %}# Synthesis -{{ presyn }} +{{ hooks.presyn | join('\n') }} # PRESYNTH #project set top_level_module_type "EDIF" @@ -69,13 +69,13 @@ project clean process run "Synthesize" if { [process get "Synthesize" status] == "errors" } { exit 1 } -{{ postsyn }} +{{ hooks.postsyn | join('\n') }} {% endif %} -{% if par %}# Place and Route +{% if 'par' in steps %}# Place and Route -{{ prepar }} +{{ hooks.prepar | join('\n') }} process run "Translate" if { [process get "Translate" status] == "errors" } { exit 1 } @@ -84,19 +84,19 @@ if { [process get "Map" status] == "errors" } { exit 1 } process run "Place & Route" if { [process get "Place & Route" status] == "errors" } { exit 1 } -{{ postpar }} +{{ hooks.postpar | join('\n') }} {% endif %} -{% if bit %}# Bitstream generation +{% if 'bit' in steps %}# Bitstream generation -{{ prebit }} +{{ hooks.prebit | join('\n') }} process run "Generate Programming File" if { [process get "Generate Programming File" status] == "errors" } { exit 1 } catch { file rename -force {{ top }}.bit {{ project }}.bit } -{{ postbit }} +{{ hooks.postbit | join('\n') }} {% endif %} diff --git a/pyfpga/templates/libero.jinja b/pyfpga/templates/libero.jinja index 77176050..310807d5 100644 --- a/pyfpga/templates/libero.jinja +++ b/pyfpga/templates/libero.jinja @@ -6,13 +6,13 @@ # #} -{% if cfg %}# Project configuration ------------------------------------------------------- +{% if 'cfg' in steps %}# Project configuration ------------------------------------------------------- if { [ file exists {{ project }} ] } { file delete -force -- {{ project }} } new_project -name {{ project }} -location {libero} -hdl {VERILOG} -family {SmartFusion2} set_device -family {{ family }} -die {{ device }} -package {{ package }} -speed {{ speed }} -{{ precfg }} +{{ hooks.precfg | join('\n') }} {% if includes %}# Verilog Includes (Libero) set_global_include_path_order -paths "{{ includes | join(' ') }}" @@ -75,44 +75,44 @@ configure_tool -name {SYNTHESIZE} -params {SYNPLIFY_OPTIONS: } {% endif %} -{{ postcfg }} +{{ hooks.postcfg | join('\n') }} close_project {% endif %} -{% if syn or par or bit %}# Design flow ----------------------------------------------------------------- +{% if 'syn' in steps or 'par' in steps or 'bit' in steps %}# Design flow ----------------------------------------------------------------- open_project {{ project }}/{{ project }}.prjx -{% if syn %}# Synthesis +{% if 'syn' in steps %}# Synthesis -{{ presyn }} +{{ hooks.presyn | join('\n') }} run_tool -name {SYNTHESIZE} -{{ postsyn }} +{{ hooks.postsyn | join('\n') }} {% endif %} -{% if par %}# Place and Route +{% if 'par' in steps %}# Place and Route -{{ prepar }} +{{ hooks.prepar | join('\n') }} run_tool -name {PLACEROUTE} run_tool -name {VERIFYTIMING} -{{ postpar }} +{{ hooks.postpar | join('\n') }} {% endif %} -{% if bit %}# Bitstream generation +{% if 'bit' in steps %}# Bitstream generation -{{ prebit }} +{{ hooks.prebit | join('\n') }} run_tool -name {GENERATEPROGRAMMINGFILE} -{{ postbit }} +{{ hooks.postbit | join('\n') }} {% endif %} diff --git a/pyfpga/templates/openflow.jinja b/pyfpga/templates/openflow.jinja index 5aa9b29b..05d01344 100644 --- a/pyfpga/templates/openflow.jinja +++ b/pyfpga/templates/openflow.jinja @@ -10,9 +10,9 @@ set -e DOCKER="docker run --user $(id -u):$(id -g) --rm -v $HOME:$HOME -w $PWD" -{% if syn %}# Synthesis +{% if 'syn' in steps %}# Synthesis $DOCKER hdlc/ghdl:yosys /bin/bash -c " -{{ presyn }} +{{ hooks.presyn | join('\n') }} yosys -Q -m ghdl -p ' {% if includes %} @@ -40,7 +40,7 @@ chparam{% for key, value in params.items() %} -set {{ key }} {{ value }}{% endfo synth -top {{ top }} synth_{{ family }} -top {{ top }} -json {{ project }}.json ' -{{ postsyn }} +{{ hooks.postsyn | join('\n') }} " {% endif %} @@ -63,7 +63,7 @@ synth_{{ family }} -top {{ top }} -json {{ project }}.json #fi #} -{% if par %}# Place and Route +{% if 'par' in steps %}# Place and Route CONSTRAINTS="{{ constraints | join(' ') }}" @@ -73,9 +73,9 @@ if [ -n "$CONSTRAINTS" ]; then CONSTRAINT="--pcf constraints.pcf" fi $DOCKER hdlc/nextpnr:ice40 /bin/bash -c " -{{ prepar }} +{{ hooks.prepar | join('\n') }} nextpnr-ice40 --{{ device }} --package {{ package }} $CONSTRAINT --json {{ project }}.json --asc {{ project }}.asc -{{ postpar }} +{{ hooks.postpar | join('\n') }} " $DOCKER hdlc/icestorm /bin/bash -c " icetime -d {{ device }} -mtr {{ project }}.rpt {{ project }}.asc @@ -88,29 +88,29 @@ if [ -n "$CONSTRAINTS" ]; then CONSTRAINT="--lpf constraints.lpf" fi $DOCKER hdlc/nextpnr:ecp5 /bin/bash -c " -{{ prepar }} +{{ hooks.prepar | join('\n') }} nextpnr-ecp5 --{{ device }} --package {{ package }} $CONSTRAINT --json {{ project }}.json --textcfg {{ project }}.config -{{ postpar }} +{{ hooks.postpar | join('\n') }} " {% endif %} {% endif %} -{% if bit %}# Bitstream generation +{% if 'bit' in steps %}# Bitstream generation {% if family == 'ice40' %} $DOCKER hdlc/icestorm /bin/bash -c " -{{ prebit }} +{{ hooks.prebit | join('\n') }} icepack {{ project }}.asc {{ project }}.bit -{{ postbit }} +{{ hooks.postbit | join('\n') }} " {% endif %} {% if family == 'ecp5' %} $DOCKER hdlc/prjtrellis /bin/bash -c " -{{ prebit }} +{{ prebit | join('\n') }} ecppack --svf {{ project }}.svf {{ project }}.config {{ project }}.bit -{{ postbit }} +{{ postbit | join('\n') }} " {% endif %} diff --git a/pyfpga/templates/quartus.jinja b/pyfpga/templates/quartus.jinja index 7ffb78a6..468b88ca 100644 --- a/pyfpga/templates/quartus.jinja +++ b/pyfpga/templates/quartus.jinja @@ -6,13 +6,13 @@ # #} -{% if cfg %}# Project configuration ------------------------------------------------------- +{% if 'cfg' in steps %}# Project configuration ------------------------------------------------------- package require ::quartus::project project_new {{ project }} -overwrite set_global_assignment -name DEVICE {{ part }} -{{ precfg }} +{{ hooks.precfg | join('\n') }} {% if files %}# Files inclusion {% for name, attr in files.items() %} @@ -58,46 +58,46 @@ set_parameter -name {{ key }} {{ value }} {% endfor %} {% endif %} -{{ postcfg }} +{{ hooks.postcfg | join('\n') }} project_close {% endif %} -{% if syn or par or bit %}# Design flow ----------------------------------------------------------------- +{% if 'syn' in steps or 'par' in steps or 'bit' in steps %}# Design flow ----------------------------------------------------------------- package require ::quartus::flow project_open -force {{ project }}.qpf # set_global_assignment -name NUM_PARALLEL_PROCESSORS ALL -{% if syn %}# Synthesis +{% if 'syn' in steps %}# Synthesis -{{ presyn }} +{{ hooks.presyn | join('\n') }} execute_module -tool map -{{ postsyn }} +{{ hooks.postsyn | join('\n') }} {% endif %} -{% if par %}# Place and Route +{% if 'par' in steps %}# Place and Route -{{ prepar }} +{{ hooks.prepar | join('\n') }} execute_module -tool fit execute_module -tool sta -{{ postpar }} +{{ hooks.postpar | join('\n') }} {% endif %} -{% if bit %}# Bitstream generation +{% if 'bit' in steps %}# Bitstream generation -{{ prebit }} +{{ hooks.prebit | join('\n') }} execute_module -tool asm -{{ postbit }} +{{ hooks.postbit | join('\n') }} {% endif %} diff --git a/pyfpga/templates/vivado.jinja b/pyfpga/templates/vivado.jinja index 6aa4e456..573a7fcb 100644 --- a/pyfpga/templates/vivado.jinja +++ b/pyfpga/templates/vivado.jinja @@ -6,14 +6,14 @@ # #} -{% if cfg %}# Project configuration ------------------------------------------------------- +{% if 'cfg' in steps %}# Project configuration ------------------------------------------------------- create_project -force {{ project }} set_property SOURCE_MGMT_MODE None [current_project] set_property STEPS.SYNTH_DESIGN.ARGS.ASSERT true [get_runs synth_1] set_property PART {{ part }} [current_project] -{{ precfg }} +{{ hooks.precfg | join('\n') }} {% if files %}# Files inclusion {% for name, attr in files.items() %} @@ -50,19 +50,19 @@ set_property VERILOG_DEFINE { {{ defines.items() | map('join', '=') | join(' ') set_property GENERIC { {{ params.items() | map('join', '=') | join(' ') }} } -objects [get_filesets sources_1] {% endif %} -{{ postcfg }} +{{ hooks.postcfg | join('\n') }} close_project {% endif %} -{% if syn or par or bit %}# Design flow ----------------------------------------------------------------- +{% if 'syn' in steps or 'par' in steps or 'bit' in steps %}# Design flow ----------------------------------------------------------------- open_project {{ project }} -{% if syn %}# Synthesis +{% if 'syn' in steps %}# Synthesis -{{ presyn }} +{{ hooks.presyn | join('\n') }} # PRESYNTH # set_property DESIGN_MODE GateLvl [current_fileset] @@ -70,16 +70,15 @@ reset_run synth_1 launch_runs synth_1 wait_on_run synth_1 #report_property [get_runs synth_1] - if { [get_property STATUS [get_runs synth_1]] ne "synth_design Complete!" } { exit 1 } -{{ postsyn }} +{{ hooks.postsyn | join('\n') }} {% endif %} -{% if par %}# Place and Route +{% if 'par' in steps %}# Place and Route -{{ prepar }} +{{ hooks.prepar | join('\n') }} reset_run impl_1 launch_runs impl_1 @@ -87,19 +86,19 @@ wait_on_run impl_1 #report_property [get_runs impl_1] if { [get_property STATUS [get_runs impl_1]] ne "route_design Complete!" } { exit 1 } -{{ postpar }} +{{ hooks.postpar | join('\n') }} {% endif %} -{% if bit %}# Bitstream generation +{% if 'bit' in steps %}# Bitstream generation -{{ prebit }} +{{ hooks.prebit | join('\n') }} open_run impl_1 write_bitstream -force {{ project }} write_debug_probes -force -quiet {{ project }}.ltx -{{ postbit }} +{{ hooks.postbit | join('\n') }} {% endif %} diff --git a/pyfpga/vivado.py b/pyfpga/vivado.py index b5f59f4e..e68916f6 100644 --- a/pyfpga/vivado.py +++ b/pyfpga/vivado.py @@ -22,23 +22,14 @@ def _make_prepare(self, steps): 'project': self.name or 'vivado', 'part': self.data.get('part', 'xc7k160t-3-fbg484') } - for step in steps: - context[step] = 1 + context['steps'] = steps context['includes'] = self.data.get('includes', None) context['files'] = self.data.get('files', None) context['constraints'] = self.data.get('constraints', None) context['top'] = self.data.get('top', None) context['defines'] = self.data.get('defines', None) context['params'] = self.data.get('params', None) - if 'hooks' in self.data: - context['precfg'] = self.data['hooks'].get('precfg', None) - context['postcfg'] = self.data['hooks'].get('postcfg', None) - context['presyn'] = self.data['hooks'].get('presyn', None) - context['postsyn'] = self.data['hooks'].get('postsyn', None) - context['prepar'] = self.data['hooks'].get('prepar', None) - context['postpar'] = self.data['hooks'].get('postpar', None) - context['presbit'] = self.data['hooks'].get('prebit', None) - context['postbit'] = self.data['hooks'].get('postbit', None) + context['hooks'] = self.data.get('hooks', None) self._create_file('vivado', 'tcl', context) return 'vivado -mode batch -notrace -quiet -source vivado.tcl' From f4e9c73684a4ab3d26415337e2f3a8a8f9a566da Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Tue, 9 Jul 2024 19:51:48 -0300 Subject: [PATCH 165/254] Refactor tool classes to simplify implementation --- pyfpga/ise.py | 46 ++++++++++------------------ pyfpga/libero.py | 40 ++++++++++-------------- pyfpga/openflow.py | 43 ++++++++------------------ pyfpga/project.py | 29 ++++++++++++------ pyfpga/quartus.py | 39 +++++++---------------- pyfpga/templates/ise.jinja | 16 +++++----- pyfpga/templates/libero.jinja | 16 +++++----- pyfpga/templates/openflow-prog.jinja | 6 ++-- pyfpga/templates/openflow.jinja | 20 ++++++------ pyfpga/templates/quartus.jinja | 16 +++++----- pyfpga/templates/vivado.jinja | 16 +++++----- pyfpga/vivado.py | 38 ++++++++--------------- 12 files changed, 133 insertions(+), 192 deletions(-) diff --git a/pyfpga/ise.py b/pyfpga/ise.py index b2d102a0..03eb8707 100644 --- a/pyfpga/ise.py +++ b/pyfpga/ise.py @@ -8,10 +8,6 @@ Implements support for ISE. """ -# pylint: disable=too-many-locals -# pylint: disable=too-many-branches -# pylint: disable=duplicate-code - import re from pyfpga.project import Project @@ -20,39 +16,29 @@ class Ise(Project): """Class to support ISE projects.""" - def _make_prepare(self, steps): - info = get_info(self.data.get('part', 'xc7k160t-3-fbg484')) - context = { - 'project': self.name or 'ise', - 'family': info['family'], - 'device': info['device'], - 'speed': info['speed'], - 'package': info['package'] - } - context['steps'] = steps - context['includes'] = self.data.get('includes', None) - context['files'] = self.data.get('files', None) - context['constraints'] = self.data.get('constraints', None) - context['top'] = self.data.get('top', None) - context['defines'] = self.data.get('defines', None) - context['params'] = self.data.get('params', None) - context['hooks'] = self.data.get('hooks', None) - self._create_file('ise', 'tcl', context) - return 'xtclsh ise.tcl' + def _configure(self): + tool = 'ise' + 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_cmd'] = f'impact -batch {tool}-prog.tcl' + self.conf['prog_ext'] = 'tcl' - def _prog_prepare(self, bitstream, position): - if not bitstream: - basename = self.name or 'ise' - bitstream = f'{basename}.bit' - context = {'bitstream': bitstream, 'position': position} - self._create_file('vivado-prog', 'tcl', context) - return 'impact -batch impact-prog' + def _make_custom(self): + info = get_info(self.data.get('part', 'xc7k160t-3-fbg484')) + self.data['family'] = info['family'] + self.data['device'] = info['device'] + self.data['speed'] = info['speed'] + self.data['package'] = info['package'] def add_slog(self, pathname): """Add System Verilog file/s.""" raise NotImplementedError('ISE does not support SystemVerilog') +# pylint: disable=duplicate-code + def get_info(part): """Get info about the FPGA part. diff --git a/pyfpga/libero.py b/pyfpga/libero.py index 00014dc8..2f1e4bc4 100644 --- a/pyfpga/libero.py +++ b/pyfpga/libero.py @@ -8,10 +8,6 @@ Implements support for Libero. """ -# pylint: disable=too-many-locals -# pylint: disable=too-many-branches -# pylint: disable=duplicate-code - import re from pyfpga.project import Project @@ -20,30 +16,28 @@ class Libero(Project): """Class to support Libero.""" - def _make_prepare(self, steps): + def _configure(self): + tool = 'libero' + self.conf['tool'] = tool + self.conf['make_cmd'] = f'{tool} SCRIPT:{tool}.tcl' + self.conf['make_ext'] = 'tcl' + self.conf['prog_bit'] = None + self.conf['prog_cmd'] = None + self.conf['prog_ext'] = None + + def _make_custom(self): info = get_info(self.data.get('part', 'mpf100t-1-fcg484')) - context = { - 'project': self.name or 'libero', - 'family': info['family'], - 'device': info['device'], - 'speed': info['speed'], - 'package': info['package'] - } - context['steps'] = steps - context['includes'] = self.data.get('includes', None) - context['files'] = self.data.get('files', None) - context['constraints'] = self.data.get('constraints', None) - context['top'] = self.data.get('top', None) - context['defines'] = self.data.get('defines', None) - context['params'] = self.data.get('params', None) - context['hooks'] = self.data.get('hooks', None) - self._create_file('libero', 'tcl', context) - return 'libero SCRIPT:libero.tcl' + self.data['family'] = info['family'] + self.data['device'] = info['device'] + self.data['speed'] = info['speed'] + self.data['package'] = info['package'] - def _prog_prepare(self, bitstream, position): + def _prog_custom(self): raise NotImplementedError('Libero programming not supported') +# pylint: disable=duplicate-code + def get_info(part): """Get info about the FPGA part. diff --git a/pyfpga/openflow.py b/pyfpga/openflow.py index feb7a41e..07cefac4 100644 --- a/pyfpga/openflow.py +++ b/pyfpga/openflow.py @@ -8,43 +8,26 @@ Implements support for an Open Source development flow. """ -# pylint: disable=too-many-locals -# pylint: disable=too-many-branches -# pylint: disable=duplicate-code - from pyfpga.project import Project class Openflow(Project): """Class to support Open Source tools.""" - def _make_prepare(self, steps): - info = get_info(self.data.get('part', 'hx8k-ct256')) - context = { - 'project': self.name or 'openflow', - 'family': info['family'], - 'device': info['device'], - 'package': info['package'] - } - context['steps'] = steps - context['includes'] = self.data.get('includes', None) - context['files'] = self.data.get('files', None) - context['constraints'] = self.data.get('constraints', None) - context['top'] = self.data.get('top', None) - context['defines'] = self.data.get('defines', None) - context['params'] = self.data.get('params', None) - context['hooks'] = self.data.get('hooks', None) - self._create_file('openflow', 'sh', context) - return 'bash openflow.sh' + def _configure(self): + tool = 'openflow' + 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_cmd'] = f'bash {tool}-prog.sh' + self.conf['prog_ext'] = 'sh' - def _prog_prepare(self, bitstream, position): - _ = position # Not needed - if not bitstream: - basename = self.name or 'openflow' - bitstream = f'{basename}.bit' - context = {'bitstream': bitstream} - self._create_file('openflow-prog', 'sh', context) - return 'bash openflow-prog.sh' + def _make_custom(self): + info = get_info(self.data.get('part', 'hx8k-ct256')) + self.data['family'] = info['family'] + self.data['device'] = info['device'] + self.data['package'] = info['package'] def get_info(part): diff --git a/pyfpga/project.py b/pyfpga/project.py index 3ad42b71..1cdceaa0 100644 --- a/pyfpga/project.py +++ b/pyfpga/project.py @@ -29,8 +29,10 @@ class Project: def __init__(self, name=None, odir='results'): """Class constructor.""" + self.conf = {} self.data = {} - self.name = name + self._configure() + self.data['project'] = name or self.conf['tool'] self.odir = odir # logging config self.logger = logging.getLogger(self.__class__.__name__) @@ -226,8 +228,10 @@ def make(self, first='cfg', last='bit'): if first == last: message = steps[first] self.logger.info('Running %s', message) - selected = keys[index[0]:index[1]+1] - self._run(self._make_prepare(selected), 'make.log') + self.data['steps'] = keys[index[0]:index[1]+1] + self._make_custom() + self._create_file(self.conf['tool'], self.conf['make_ext']) + self._run(self.conf['make_cmd'], 'make.log') def prog(self, bitstream=None, position=1): """Program the FPGA @@ -243,20 +247,27 @@ def prog(self, bitstream=None, position=1): if position not in range(1, 9): raise ValueError('Invalid position.') self.logger.info('Programming') - self._run(self._prog_prepare(bitstream, position), 'prog.log') + if not bitstream: + bitstream = f'{self.data["project"]}.{self.conf["prog_bit"]}' + self._prog_custom() + self._create_file(f'{self.conf["tool"]}-prog', self.conf['prog_ext']) + self._run(self.conf['prog_cmd'], 'prog.log') - def _make_prepare(self, steps): + def _configure(self): raise NotImplementedError('Tool-dependent') - def _prog_prepare(self, bitstream, position): - raise NotImplementedError('Tool-dependent') + def _make_custom(self): + pass + + def _prog_custom(self): + pass - def _create_file(self, basename, extension, context): + def _create_file(self, basename, extension): tempdir = Path(__file__).parent.joinpath('templates') jinja_file_loader = FileSystemLoader(str(tempdir)) jinja_env = Environment(loader=jinja_file_loader) jinja_template = jinja_env.get_template(f'{basename}.jinja') - content = jinja_template.render(context) + content = jinja_template.render(self.data) directory = Path(self.odir) directory.mkdir(parents=True, exist_ok=True) filename = f'{basename}.{extension}' diff --git a/pyfpga/quartus.py b/pyfpga/quartus.py index 1f4cb2e5..8cd8f66a 100644 --- a/pyfpga/quartus.py +++ b/pyfpga/quartus.py @@ -8,38 +8,21 @@ Implements support for Quartus. """ -# pylint: disable=too-many-locals -# pylint: disable=too-many-branches -# pylint: disable=duplicate-code - from pyfpga.project import Project class Quartus(Project): """Class to support Quartus projects.""" - def _make_prepare(self, steps): - context = { - 'project': self.name or 'quartus', - 'part': self.data.get('part', '10M50SCE144I7G') - } - context['steps'] = steps - context['includes'] = self.data.get('includes', None) - context['files'] = self.data.get('files', None) - context['constraints'] = self.data.get('constraints', None) - context['top'] = self.data.get('top', None) - context['defines'] = self.data.get('defines', None) - context['params'] = self.data.get('params', None) - context['hooks'] = self.data.get('hooks', None) - self._create_file('quartus', 'tcl', context) - return 'quartus_sh --script quartus.tcl' + def _configure(self): + tool = 'quartus' + 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_cmd'] = f'bash {tool}-prog.tcl' + self.conf['prog_ext'] = 'tcl' - def _prog_prepare(self, bitstream, position): - # sof: SRAM Object File - # pof: Programming Object File - if not bitstream: - basename = self.name or 'quartus' - bitstream = f'{basename}.sof' - context = {'bitstream': bitstream, 'position': position} - self._create_file('quartus-prog', 'tcl', context) - return 'bash quartus-prog.sh' + def _make_custom(self): + if 'part' not in self.data: + self.data['part'] = '10M50SCE144I7G' diff --git a/pyfpga/templates/ise.jinja b/pyfpga/templates/ise.jinja index b44e003a..c90f2d5e 100644 --- a/pyfpga/templates/ise.jinja +++ b/pyfpga/templates/ise.jinja @@ -15,7 +15,7 @@ project set device {{ device }} project set package {{ package }} project set speed -{{ speed }} -{{ hooks.precfg | join('\n') }} +{% if hooks %}{{ hooks.precfg | join('\n') }}{% endif %} {% if files %}# Files inclusion {% for name, attr in files.items() %} @@ -49,7 +49,7 @@ project set "Verilog Macros" "{{ defines.items() | map('join', '=') | join(' | ' project set "Generics, Parameters" "{{ params.items() | map('join', '=') | join(' ') }}" -process "Synthesize - XST" {% endif %} -{{ hooks.postcfg | join('\n') }} +{% if hooks %}{{ hooks.postcfg | join('\n') }}{% endif %} project close @@ -61,7 +61,7 @@ project open {{ project }}.xise {% if 'syn' in steps %}# Synthesis -{{ hooks.presyn | join('\n') }} +{% if hooks %}{{ hooks.presyn | join('\n') }}{% endif %} # PRESYNTH #project set top_level_module_type "EDIF" @@ -69,13 +69,13 @@ project clean process run "Synthesize" if { [process get "Synthesize" status] == "errors" } { exit 1 } -{{ hooks.postsyn | join('\n') }} +{% if hooks %}{{ hooks.postsyn | join('\n') }}{% endif %} {% endif %} {% if 'par' in steps %}# Place and Route -{{ hooks.prepar | join('\n') }} +{% if hooks %}{{ hooks.prepar | join('\n') }}{% endif %} process run "Translate" if { [process get "Translate" status] == "errors" } { exit 1 } @@ -84,19 +84,19 @@ if { [process get "Map" status] == "errors" } { exit 1 } process run "Place & Route" if { [process get "Place & Route" status] == "errors" } { exit 1 } -{{ hooks.postpar | join('\n') }} +{% if hooks %}{{ hooks.postpar | join('\n') }}{% endif %} {% endif %} {% if 'bit' in steps %}# Bitstream generation -{{ hooks.prebit | join('\n') }} +{% if hooks %}{{ hooks.prebit | join('\n') }}{% endif %} process run "Generate Programming File" if { [process get "Generate Programming File" status] == "errors" } { exit 1 } catch { file rename -force {{ top }}.bit {{ project }}.bit } -{{ hooks.postbit | join('\n') }} +{% if hooks %}{{ hooks.postbit | join('\n') }}{% endif %} {% endif %} diff --git a/pyfpga/templates/libero.jinja b/pyfpga/templates/libero.jinja index 310807d5..6962f97d 100644 --- a/pyfpga/templates/libero.jinja +++ b/pyfpga/templates/libero.jinja @@ -12,7 +12,7 @@ if { [ file exists {{ project }} ] } { file delete -force -- {{ project }} } new_project -name {{ project }} -location {libero} -hdl {VERILOG} -family {SmartFusion2} set_device -family {{ family }} -die {{ device }} -package {{ package }} -speed {{ speed }} -{{ hooks.precfg | join('\n') }} +{% if hooks %}{{ hooks.precfg | join('\n') }}{% endif %} {% if includes %}# Verilog Includes (Libero) set_global_include_path_order -paths "{{ includes | join(' ') }}" @@ -75,7 +75,7 @@ configure_tool -name {SYNTHESIZE} -params {SYNPLIFY_OPTIONS: } {% endif %} -{{ hooks.postcfg | join('\n') }} +{% if hooks %}{{ hooks.postcfg | join('\n') }}{% endif %} close_project @@ -87,32 +87,32 @@ open_project {{ project }}/{{ project }}.prjx {% if 'syn' in steps %}# Synthesis -{{ hooks.presyn | join('\n') }} +{% if hooks %}{{ hooks.presyn | join('\n') }}{% endif %} run_tool -name {SYNTHESIZE} -{{ hooks.postsyn | join('\n') }} +{% if hooks %}{{ hooks.postsyn | join('\n') }}{% endif %} {% endif %} {% if 'par' in steps %}# Place and Route -{{ hooks.prepar | join('\n') }} +{% if hooks %}{{ hooks.prepar | join('\n') }}{% endif %} run_tool -name {PLACEROUTE} run_tool -name {VERIFYTIMING} -{{ hooks.postpar | join('\n') }} +{% if hooks %}{{ hooks.postpar | join('\n') }}{% endif %} {% endif %} {% if 'bit' in steps %}# Bitstream generation -{{ hooks.prebit | join('\n') }} +{% if hooks %}{{ hooks.prebit | join('\n') }}{% endif %} run_tool -name {GENERATEPROGRAMMINGFILE} -{{ hooks.postbit | join('\n') }} +{% if hooks %}{{ hooks.postbit | join('\n') }}{% endif %} {% endif %} diff --git a/pyfpga/templates/openflow-prog.jinja b/pyfpga/templates/openflow-prog.jinja index 47fad80d..7956b2b3 100644 --- a/pyfpga/templates/openflow-prog.jinja +++ b/pyfpga/templates/openflow-prog.jinja @@ -9,10 +9,8 @@ set -e DOCKER="docker run --user $(id -u):$(id -g) --rm -v $HOME:$HOME -w $PWD" -{% if family == 'ice40' %} -$DOCKER --device /dev/bus/usb hdlc/prog iceprog {{ project }}.bit -{% endif %} - {% 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" +{% else %} +$DOCKER --device /dev/bus/usb hdlc/prog iceprog {{ project }}.bit {% endif %} diff --git a/pyfpga/templates/openflow.jinja b/pyfpga/templates/openflow.jinja index 05d01344..175a68a7 100644 --- a/pyfpga/templates/openflow.jinja +++ b/pyfpga/templates/openflow.jinja @@ -12,7 +12,7 @@ DOCKER="docker run --user $(id -u):$(id -g) --rm -v $HOME:$HOME -w $PWD" {% if 'syn' in steps %}# Synthesis $DOCKER hdlc/ghdl:yosys /bin/bash -c " -{{ hooks.presyn | join('\n') }} +{% if hooks %}{{ hooks.presyn | join('\n') }}{% endif %} yosys -Q -m ghdl -p ' {% if includes %} @@ -40,7 +40,7 @@ chparam{% for key, value in params.items() %} -set {{ key }} {{ value }}{% endfo synth -top {{ top }} synth_{{ family }} -top {{ top }} -json {{ project }}.json ' -{{ hooks.postsyn | join('\n') }} +{% if hooks %}{{ hooks.postsyn | join('\n') }}{% endif %} " {% endif %} @@ -73,9 +73,9 @@ if [ -n "$CONSTRAINTS" ]; then CONSTRAINT="--pcf constraints.pcf" fi $DOCKER hdlc/nextpnr:ice40 /bin/bash -c " -{{ hooks.prepar | join('\n') }} +{% if hooks %}{{ hooks.prepar | join('\n') }}{% endif %} nextpnr-ice40 --{{ device }} --package {{ package }} $CONSTRAINT --json {{ project }}.json --asc {{ project }}.asc -{{ hooks.postpar | join('\n') }} +{% if hooks %}{{ hooks.postpar | join('\n') }}{% endif %} " $DOCKER hdlc/icestorm /bin/bash -c " icetime -d {{ device }} -mtr {{ project }}.rpt {{ project }}.asc @@ -88,9 +88,9 @@ if [ -n "$CONSTRAINTS" ]; then CONSTRAINT="--lpf constraints.lpf" fi $DOCKER hdlc/nextpnr:ecp5 /bin/bash -c " -{{ hooks.prepar | join('\n') }} +{% if hooks %}{{ hooks.prepar | join('\n') }}{% endif %} nextpnr-ecp5 --{{ device }} --package {{ package }} $CONSTRAINT --json {{ project }}.json --textcfg {{ project }}.config -{{ hooks.postpar | join('\n') }} +{% if hooks %}{{ hooks.postpar | join('\n') }}{% endif %} " {% endif %} @@ -100,17 +100,17 @@ nextpnr-ecp5 --{{ device }} --package {{ package }} $CONSTRAINT --json {{ projec {% if family == 'ice40' %} $DOCKER hdlc/icestorm /bin/bash -c " -{{ hooks.prebit | join('\n') }} +{% if hooks %}{{ hooks.prebit | join('\n') }}{% endif %} icepack {{ project }}.asc {{ project }}.bit -{{ hooks.postbit | join('\n') }} +{% if hooks %}{{ hooks.postbit | join('\n') }}{% endif %} " {% endif %} {% if family == 'ecp5' %} $DOCKER hdlc/prjtrellis /bin/bash -c " -{{ prebit | join('\n') }} +{% if hooks %}{{ prebit | join('\n') }}{% endif %} ecppack --svf {{ project }}.svf {{ project }}.config {{ project }}.bit -{{ postbit | join('\n') }} +{% if hooks %}{{ postbit | join('\n') }}{% endif %} " {% endif %} diff --git a/pyfpga/templates/quartus.jinja b/pyfpga/templates/quartus.jinja index 468b88ca..5013e1f8 100644 --- a/pyfpga/templates/quartus.jinja +++ b/pyfpga/templates/quartus.jinja @@ -12,7 +12,7 @@ package require ::quartus::project project_new {{ project }} -overwrite set_global_assignment -name DEVICE {{ part }} -{{ hooks.precfg | join('\n') }} +{% if hooks %}{{ hooks.precfg | join('\n') }}{% endif %} {% if files %}# Files inclusion {% for name, attr in files.items() %} @@ -58,7 +58,7 @@ set_parameter -name {{ key }} {{ value }} {% endfor %} {% endif %} -{{ hooks.postcfg | join('\n') }} +{% if hooks %}{{ hooks.postcfg | join('\n') }}{% endif %} project_close @@ -72,32 +72,32 @@ project_open -force {{ project }}.qpf {% if 'syn' in steps %}# Synthesis -{{ hooks.presyn | join('\n') }} +{% if hooks %}{{ hooks.presyn | join('\n') }}{% endif %} execute_module -tool map -{{ hooks.postsyn | join('\n') }} +{% if hooks %}{{ hooks.postsyn | join('\n') }}{% endif %} {% endif %} {% if 'par' in steps %}# Place and Route -{{ hooks.prepar | join('\n') }} +{% if hooks %}{{ hooks.prepar | join('\n') }}{% endif %} execute_module -tool fit execute_module -tool sta -{{ hooks.postpar | join('\n') }} +{% if hooks %}{{ hooks.postpar | join('\n') }}{% endif %} {% endif %} {% if 'bit' in steps %}# Bitstream generation -{{ hooks.prebit | join('\n') }} +{% if hooks %}{{ hooks.prebit | join('\n') }}{% endif %} execute_module -tool asm -{{ hooks.postbit | join('\n') }} +{% if hooks %}{{ hooks.postbit | join('\n') }}{% endif %} {% endif %} diff --git a/pyfpga/templates/vivado.jinja b/pyfpga/templates/vivado.jinja index 573a7fcb..babbf3b6 100644 --- a/pyfpga/templates/vivado.jinja +++ b/pyfpga/templates/vivado.jinja @@ -13,7 +13,7 @@ set_property SOURCE_MGMT_MODE None [current_project] set_property STEPS.SYNTH_DESIGN.ARGS.ASSERT true [get_runs synth_1] set_property PART {{ part }} [current_project] -{{ hooks.precfg | join('\n') }} +{% if hooks %}{{ hooks.precfg | join('\n') }}{% endif %} {% if files %}# Files inclusion {% for name, attr in files.items() %} @@ -50,7 +50,7 @@ set_property VERILOG_DEFINE { {{ defines.items() | map('join', '=') | join(' ') set_property GENERIC { {{ params.items() | map('join', '=') | join(' ') }} } -objects [get_filesets sources_1] {% endif %} -{{ hooks.postcfg | join('\n') }} +{% if hooks %}{{ hooks.postcfg | join('\n') }}{% endif %} close_project @@ -62,7 +62,7 @@ open_project {{ project }} {% if 'syn' in steps %}# Synthesis -{{ hooks.presyn | join('\n') }} +{% if hooks %}{{ hooks.presyn | join('\n') }}{% endif %} # PRESYNTH # set_property DESIGN_MODE GateLvl [current_fileset] @@ -72,13 +72,13 @@ wait_on_run synth_1 #report_property [get_runs synth_1] if { [get_property STATUS [get_runs synth_1]] ne "synth_design Complete!" } { exit 1 } -{{ hooks.postsyn | join('\n') }} +{% if hooks %}{{ hooks.postsyn | join('\n') }}{% endif %} {% endif %} {% if 'par' in steps %}# Place and Route -{{ hooks.prepar | join('\n') }} +{% if hooks %}{{ hooks.prepar | join('\n') }}{% endif %} reset_run impl_1 launch_runs impl_1 @@ -86,19 +86,19 @@ wait_on_run impl_1 #report_property [get_runs impl_1] if { [get_property STATUS [get_runs impl_1]] ne "route_design Complete!" } { exit 1 } -{{ hooks.postpar | join('\n') }} +{% if hooks %}{{ hooks.postpar | join('\n') }}{% endif %} {% endif %} {% if 'bit' in steps %}# Bitstream generation -{{ hooks.prebit | join('\n') }} +{% if hooks %}{{ hooks.prebit | join('\n') }}{% endif %} open_run impl_1 write_bitstream -force {{ project }} write_debug_probes -force -quiet {{ project }}.ltx -{{ hooks.postbit | join('\n') }} +{% if hooks %}{{ hooks.postbit | join('\n') }}{% endif %} {% endif %} diff --git a/pyfpga/vivado.py b/pyfpga/vivado.py index e68916f6..0391a17f 100644 --- a/pyfpga/vivado.py +++ b/pyfpga/vivado.py @@ -8,36 +8,22 @@ Implements support for Vivado. """ -# pylint: disable=too-many-locals -# pylint: disable=too-many-branches - from pyfpga.project import Project class Vivado(Project): """Class to support Vivado projects.""" - def _make_prepare(self, steps): - context = { - 'project': self.name or 'vivado', - 'part': self.data.get('part', 'xc7k160t-3-fbg484') - } - context['steps'] = steps - context['includes'] = self.data.get('includes', None) - context['files'] = self.data.get('files', None) - context['constraints'] = self.data.get('constraints', None) - context['top'] = self.data.get('top', None) - context['defines'] = self.data.get('defines', None) - context['params'] = self.data.get('params', None) - context['hooks'] = self.data.get('hooks', None) - self._create_file('vivado', 'tcl', context) - return 'vivado -mode batch -notrace -quiet -source vivado.tcl' + def _configure(self): + tool = 'vivado' + command = 'vivado -mode batch -notrace -quiet -source' + 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_cmd'] = f'{command} {tool}-prog.tcl' + self.conf['prog_ext'] = 'tcl' - def _prog_prepare(self, bitstream, position): - _ = position # Not needed - if not bitstream: - basename = self.name or 'vivado' - bitstream = f'{basename}.bit' - context = {'BITSTREAM': bitstream} - self._create_file('vivado-prog', 'tcl', context) - return 'vivado -mode batch -notrace -quiet -source vivado-prog.tcl' + def _make_custom(self): + if 'part' not in self.data: + self.data['part'] = 'xc7k160t-3-fbg484' From 8e3de7579a5b885e1d156223d6daf435b15691c5 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Tue, 9 Jul 2024 21:30:28 -0300 Subject: [PATCH 166/254] Fix after last changes --- tests/test_data.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/test_data.py b/tests/test_data.py index 34f4c633..3ba4e4a9 100644 --- a/tests/test_data.py +++ b/tests/test_data.py @@ -1,8 +1,9 @@ from pathlib import Path -from pyfpga.project import Project +from pyfpga.vivado import Vivado pattern = { + 'project': 'EXAMPLE', 'part': 'PARTNAME', 'includes': [ Path('fakedata/dir1').resolve().as_posix(), @@ -61,7 +62,7 @@ def test_data(): - prj = Project() + prj = Vivado('EXAMPLE') prj.set_part('PARTNAME') prj.set_top('TOPNAME') prj.add_include('fakedata/dir1') From 52985d91f257ed72bf434b0127c559313efef419 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Tue, 9 Jul 2024 21:30:53 -0300 Subject: [PATCH 167/254] Fix missing check of top --- pyfpga/templates/openflow.jinja | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyfpga/templates/openflow.jinja b/pyfpga/templates/openflow.jinja index 175a68a7..2cafa31d 100644 --- a/pyfpga/templates/openflow.jinja +++ b/pyfpga/templates/openflow.jinja @@ -37,7 +37,10 @@ read_verilog -defer -sv {{ name }} chparam{% for key, value in params.items() %} -set {{ key }} {{ value }}{% endfor %} {% endif %} +{% if top%} synth -top {{ top }} +{% endif %} + synth_{{ family }} -top {{ top }} -json {{ project }}.json ' {% if hooks %}{{ hooks.postsyn | join('\n') }}{% endif %} From 6cd70eb3df590ba6b173a95c769648f28647fa45 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Tue, 9 Jul 2024 21:31:45 -0300 Subject: [PATCH 168/254] Renamed (expanded) test_vivado.py as test_tools.py --- tests/test_tools.py | 71 ++++++++++++++++++++++++++++++++++++++++++++ tests/test_vivado.py | 28 ----------------- 2 files changed, 71 insertions(+), 28 deletions(-) create mode 100644 tests/test_tools.py delete mode 100644 tests/test_vivado.py diff --git a/tests/test_tools.py b/tests/test_tools.py new file mode 100644 index 00000000..d7924dd7 --- /dev/null +++ b/tests/test_tools.py @@ -0,0 +1,71 @@ +from pyfpga.ise import Ise +from pyfpga.libero import Libero +from pyfpga.openflow import Openflow +from pyfpga.quartus import Quartus +from pyfpga.vivado import Vivado + + +tools = { + 'ise': Ise, + 'libero': Libero, + 'openflow': Openflow, + 'quartus': Quartus, + 'vivado': Vivado +} + +def test_ise(): + generate('ise') + +def test_libero(): + generate('libero') + +def test_openflow(): + generate('openflow') + +def test_quartus(): + generate('quartus') + +def test_vivado(): + generate('vivado') + +def generate(tool): + prj = tools[tool](odir=f'results/{tool}') + prj.set_part('PARTNAME') + prj.set_top('TOPNAME') + prj.add_include('fakedata/dir1') + prj.add_include('fakedata/dir2') + if tool != 'ise': + prj.add_slog('fakedata/**/*.sv') + prj.add_vhdl('fakedata/**/*.vhdl', 'LIB') + prj.add_vlog('fakedata/**/*.v') + prj.add_cons('fakedata/cons/all.xdc') + prj.add_cons('fakedata/cons/syn.xdc', 'syn') + prj.add_cons('fakedata/cons/par.xdc', 'par') + prj.add_param('PAR1', 'VAL1') + prj.add_param('PAR2', 'VAL2') + prj.add_define('DEF1', 'VAL1') + prj.add_define('DEF2', 'VAL2') + prj.add_hook('precfg', 'HOOK01') + prj.add_hook('precfg', 'HOOK02') + prj.add_hook('postcfg', 'HOOK03') + prj.add_hook('postcfg', 'HOOK04') + prj.add_hook('presyn', 'HOOK05') + prj.add_hook('presyn', 'HOOK06') + prj.add_hook('postsyn', 'HOOK07') + prj.add_hook('postsyn', 'HOOK08') + prj.add_hook('prepar', 'HOOK09') + prj.add_hook('prepar', 'HOOK10') + prj.add_hook('postpar', 'HOOK11') + prj.add_hook('postpar', 'HOOK12') + prj.add_hook('prebit', 'HOOK13') + prj.add_hook('prebit', 'HOOK14') + prj.add_hook('postbit', 'HOOK15') + prj.add_hook('postbit', 'HOOK16') + try: + prj.make() + except Exception: + pass + try: + prj.prog() + except Exception: + pass diff --git a/tests/test_vivado.py b/tests/test_vivado.py deleted file mode 100644 index 57781a09..00000000 --- a/tests/test_vivado.py +++ /dev/null @@ -1,28 +0,0 @@ -from pyfpga.vivado import Vivado - - -def test_vivado(): - prj = Vivado() - prj.set_part('PARTNAME') - prj.set_top('TOPNAME') - prj.add_include('fakedata/dir1') - prj.add_include('fakedata/dir2') - prj.add_slog('fakedata/**/*.sv') - prj.add_vhdl('fakedata/**/*.vhdl', 'LIB') - prj.add_vlog('fakedata/**/*.v') - prj.add_cons('fakedata/cons/all.xdc') - prj.add_cons('fakedata/cons/syn.xdc', 'syn') - prj.add_cons('fakedata/cons/par.xdc', 'par') - prj.add_param('PAR1', 'VAL1') - prj.add_param('PAR2', 'VAL2') - prj.add_define('DEF1', 'VAL1') - prj.add_define('DEF2', 'VAL2') - prj.add_hook('precfg', 'PRECFG_HOOK') - prj.add_hook('postcfg', 'POSTCFG_HOOK') - prj.add_hook('presyn', 'PRESYN_HOOK') - prj.add_hook('postsyn', 'POSTSYN_HOOK') - prj.add_hook('prepar', 'PREPAR_HOOK') - prj.add_hook('postpar', 'POSTPAR_HOOK') - prj.add_hook('prebit', 'PREBIT_HOOK') - prj.add_hook('postbit', 'POSTBIT_HOOK') - prj.make() From 1629745c855b44d77a68ebeee88f01af3fdb3e87 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Tue, 9 Jul 2024 21:36:12 -0300 Subject: [PATCH 169/254] Fix lint issue --- tests/test_tools.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/test_tools.py b/tests/test_tools.py index d7924dd7..3eb7cc0d 100644 --- a/tests/test_tools.py +++ b/tests/test_tools.py @@ -13,21 +13,27 @@ 'vivado': Vivado } + def test_ise(): generate('ise') + def test_libero(): generate('libero') + def test_openflow(): generate('openflow') + def test_quartus(): generate('quartus') + def test_vivado(): generate('vivado') + def generate(tool): prj = tools[tool](odir=f'results/{tool}') prj.set_part('PARTNAME') From 664e26b7d0eb263c02a1b06e3017e67ce4513b30 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Tue, 9 Jul 2024 22:04:56 -0300 Subject: [PATCH 170/254] Add to check if resulting files exist --- tests/test_tools.py | 34 +++++++++++++++++++++++++++------- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/tests/test_tools.py b/tests/test_tools.py index 3eb7cc0d..56895931 100644 --- a/tests/test_tools.py +++ b/tests/test_tools.py @@ -1,3 +1,4 @@ +from pathlib import Path from pyfpga.ise import Ise from pyfpga.libero import Libero from pyfpga.openflow import Openflow @@ -15,28 +16,47 @@ def test_ise(): - generate('ise') + tool = 'ise' + 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_libero(): - generate('libero') + tool = 'libero' + generate(tool, 'DEVICE-PACKAGE-SPEED') + base = f'results/{tool}/{tool}' + assert Path(f'{base}.tcl').exists(), 'file not found' def test_openflow(): - generate('openflow') + tool = 'openflow' + generate(tool, 'DEVICE-PACKAGE') + base = f'results/{tool}/{tool}' + assert Path(f'{base}.sh').exists(), 'file not found' + assert Path(f'{base}-prog.sh').exists(), 'file not found' def test_quartus(): - generate('quartus') + tool = 'quartus' + generate(tool, 'PARTNAME') + 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_vivado(): - generate('vivado') + tool = 'vivado' + generate(tool, 'PARTNAME') + 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 generate(tool): +def generate(tool, part): prj = tools[tool](odir=f'results/{tool}') - prj.set_part('PARTNAME') + prj.set_part(part) prj.set_top('TOPNAME') prj.add_include('fakedata/dir1') prj.add_include('fakedata/dir2') From c6c655808823656d2569c74cec5779d93eb9fcf8 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Wed, 10 Jul 2024 20:33:08 -0300 Subject: [PATCH 171/254] Renamed parameter name as project --- pyfpga/project.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pyfpga/project.py b/pyfpga/project.py index 1cdceaa0..212fafbe 100644 --- a/pyfpga/project.py +++ b/pyfpga/project.py @@ -21,18 +21,18 @@ class Project: """Base class to manage an FPGA project. - :param name: project name (tool name when nothing specified) - :type name: str, optional + :param project: project name (tool name when nothing specified) + :type project: str, optional :param odir: output directory :type odir: str, optional """ - def __init__(self, name=None, odir='results'): + def __init__(self, project=None, odir='results'): """Class constructor.""" self.conf = {} self.data = {} self._configure() - self.data['project'] = name or self.conf['tool'] + self.data['project'] = project or self.conf['tool'] self.odir = odir # logging config self.logger = logging.getLogger(self.__class__.__name__) From 16799d8449278b5cac02e8bd010fed53abe4f80b Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Wed, 10 Jul 2024 20:41:45 -0300 Subject: [PATCH 172/254] Fix the name of a similar project --- README.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index cfc2e872..c3e12f21 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,10 @@ # PyFPGA [![License](https://img.shields.io/badge/License-GPL--3.0-darkgreen?style=flat-square)](LICENSE) -![Vivado](https://img.shields.io/badge/Vivado-2022.1-blue.svg?style=flat-square) -![Quartus](https://img.shields.io/badge/Quartus--Prime-23.1-blue.svg?style=flat-square) -![Libero](https://img.shields.io/badge/Libero--Soc-2024.1-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) +![Vivado](https://img.shields.io/badge/Vivado-2022.1-blue.svg?style=flat-square) + ![Openflow](https://img.shields.io/badge/Openflow-GHDL%20%7C%20Yosys%20%7C%20nextpnr%20%7C%20icestorm%20%7C%20prjtrellis-darkgreen.svg?style=flat-square) PyFPGA is an abstraction layer for working with FPGA development tools in a vendor-agnostic, programmatic way. It is a Python package that provides: @@ -72,4 +73,4 @@ pip install -e . * [Hdlmake](https://ohwr.org/project/hdl-make): tool for generating multi-purpose makefiles for FPGA projects. * HDL On Git ([Hog](https://gitlab.com/hog-cern/Hog)): a set of Tcl/Shell scripts plus a suitable methodology to handle HDL designs in a GitLab repository. * IPbus Builder ([IPBB](https://github.com/ipbus/ipbb)): a tool for streamlining the synthesis, implementation and simulation of modular firmware projects over multiple platforms. -* [tsfpa](https://github.com/tsfpga/tsfpga): a flexible and scalable development platform for modern FPGA projects. +* [tsfpga](https://github.com/tsfpga/tsfpga): a flexible and scalable development platform for modern FPGA projects. From a2e816de348f90aecea6a49e403f897be469d12f Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Fri, 12 Jul 2024 21:49:25 -0300 Subject: [PATCH 173/254] Add a class to deal with multiple tools in the same project --- pyfpga/factory.py | 40 +++++++++++++++++++++++++++++++++++++++ tests/projects/support.py | 37 ++++++++++++------------------------ tests/test_tools.py | 17 ++--------------- 3 files changed, 54 insertions(+), 40 deletions(-) create mode 100644 pyfpga/factory.py diff --git a/pyfpga/factory.py b/pyfpga/factory.py new file mode 100644 index 00000000..197b69d3 --- /dev/null +++ b/pyfpga/factory.py @@ -0,0 +1,40 @@ +# +# Copyright (C) 2024 Rodrigo A. Melo +# +# SPDX-License-Identifier: GPL-3.0-or-later +# + +""" +A factory class to create FPGA projects. +""" + +# pylint: disable=too-few-public-methods + +from pyfpga.ise import Ise +from pyfpga.libero import Libero +from pyfpga.openflow import Openflow +from pyfpga.quartus import Quartus +from pyfpga.vivado import Vivado + + +tools = { + 'ise': Ise, + 'libero': Libero, + 'openflow': Openflow, + 'quartus': Quartus, + 'vivado': Vivado +} + + +class Factory: + """A factory class to create FPGA projects.""" + + def __init__(self, tool='vivado', project=None, odir='results'): + """Class constructor.""" + if tool not in tools: + raise NotImplementedError(f'{tool} is unsupported') + self._instance = tools[tool](project, odir) + + def __getattr__(self, name): + """Delegate attribute access to the tool instance.""" + return getattr(self._instance, name) diff --git a/tests/projects/support.py b/tests/projects/support.py index 4aa0d009..2e26e3b5 100644 --- a/tests/projects/support.py +++ b/tests/projects/support.py @@ -3,20 +3,7 @@ import argparse import sys -from pyfpga.ise import Ise -from pyfpga.libero import Libero -from pyfpga.openflow import Openflow -from pyfpga.quartus import Quartus -from pyfpga.vivado import Vivado - - -tools = { - 'ise': Ise, - 'libero': Libero, - 'openflow': Openflow, - 'quartus': Quartus, - 'vivado': Vivado -} +from pyfpga.factory import Factory parser = argparse.ArgumentParser() parser.add_argument( @@ -28,13 +15,13 @@ print(f'INFO: the Tool Under Test is {args.tool}') print('INFO: checking basic Verilog Support') -prj = tools[args.tool]() +prj = Factory(args.tool) prj.add_vlog('../../examples/sources/vlog/blink.v') prj.set_top('Blink') prj.make(last='syn') print('INFO: checking advanced Verilog Support') -prj = tools[args.tool]() +prj = Factory(args.tool) prj.add_vlog('../../examples/sources/vlog/*.v') prj.set_top('Top') prj.add_include('../../examples/sources/vlog/include1') @@ -47,7 +34,7 @@ try: print('INFO: checking Verilog Includes Support') - prj = tools[args.tool]() + prj = Factory(args.tool) prj.add_vlog('../../examples/sources/vlog/*.v') prj.set_top('Top') prj.add_define('DEFINE1', '1') @@ -63,7 +50,7 @@ try: print('INFO: checking Verilog Defines Support') - prj = tools[args.tool]() + prj = Factory(args.tool) prj.add_vlog('../../examples/sources/vlog/*.v') prj.set_top('Top') prj.add_include('../../examples/sources/vlog/include1') @@ -79,7 +66,7 @@ try: print('INFO: checking Verilog Parameters Support') - prj = tools[args.tool]() + prj = Factory(args.tool) prj.add_vlog('../../examples/sources/vlog/*.v') prj.set_top('Top') prj.add_include('../../examples/sources/vlog/include1') @@ -95,13 +82,13 @@ if args.tool not in ['ise']: print('INFO: checking basic System Verilog Support') - prj = tools[args.tool]() + prj = Factory(args.tool) prj.add_slog('../../examples/sources/slog/blink.sv') prj.set_top('Blink') prj.make(last='syn') print('INFO: checking advanced System Verilog Support') - prj = tools[args.tool]() + prj = Factory(args.tool) prj.add_slog('../../examples/sources/slog/*.sv') prj.set_top('Top') prj.add_include('../../examples/sources/slog/include1') @@ -114,13 +101,13 @@ if args.tool not in ['openflow']: print('* INFO: checking basic VHDL Support') - prj = tools[args.tool]() + prj = Factory(args.tool) prj.add_vhdl('../../examples/sources/vhdl/blink.vhdl') prj.set_top('Blink') prj.make(last='syn') print('* INFO: checking advanced VHDL Support') - prj = tools[args.tool]() + prj = Factory(args.tool) prj.add_vhdl('../../examples/sources/vhdl/*.vhdl', 'blink_lib') prj.add_vhdl('../../examples/sources/vhdl/top.vhdl') prj.set_top('Top') @@ -130,7 +117,7 @@ try: print('INFO: checking VHDL Generics') - prj = tools[args.tool]() + prj = Factory(args.tool) prj.add_vhdl('../../examples/sources/vhdl/*.vhdl', 'blink_lib') prj.add_vhdl('../../examples/sources/vhdl/top.vhdl') prj.set_top('Top') @@ -141,4 +128,4 @@ except Exception: pass -print(f'INFO: Tool Under Test works as expected') +print(f'INFO: {args.tool} support works as expected') diff --git a/tests/test_tools.py b/tests/test_tools.py index 56895931..7c23c4a1 100644 --- a/tests/test_tools.py +++ b/tests/test_tools.py @@ -1,18 +1,5 @@ from pathlib import Path -from pyfpga.ise import Ise -from pyfpga.libero import Libero -from pyfpga.openflow import Openflow -from pyfpga.quartus import Quartus -from pyfpga.vivado import Vivado - - -tools = { - 'ise': Ise, - 'libero': Libero, - 'openflow': Openflow, - 'quartus': Quartus, - 'vivado': Vivado -} +from pyfpga.factory import Factory def test_ise(): @@ -55,7 +42,7 @@ def test_vivado(): def generate(tool, part): - prj = tools[tool](odir=f'results/{tool}') + prj = Factory(tool, odir=f'results/{tool}') prj.set_part(part) prj.set_top('TOPNAME') prj.add_include('fakedata/dir1') From 0657ee2a1ffafd008e9ac2c36bdc5bf3287ae296 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Fri, 12 Jul 2024 21:51:42 -0300 Subject: [PATCH 174/254] Remove arch specification --- docs/internals.rst | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/docs/internals.rst b/docs/internals.rst index bcaa371f..fd388ae4 100644 --- a/docs/internals.rst +++ b/docs/internals.rst @@ -9,26 +9,25 @@ Underlying tool steps create project config project part - precfg hook + precfg (hook) params defines includes files - arch top - postcfg hook + postcfg (hook) close project open project - presyn hook + presyn (hook) synthesis - postsyn hook - prepar hook + postsyn (hook) + prepar (hook) place_and_route - postpar hook - prebit hook + postpar (hook) + prebit (hook) bitstream - postbit hook + postbit (hook) close project Internal data structure From 727ba516eb6adf4bcfc6431b56249af1b0a296e9 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sat, 13 Jul 2024 20:35:06 -0300 Subject: [PATCH 175/254] Make STEPS accessible for external access --- pyfpga/project.py | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/pyfpga/project.py b/pyfpga/project.py index 212fafbe..4f4d9de0 100644 --- a/pyfpga/project.py +++ b/pyfpga/project.py @@ -18,6 +18,14 @@ from jinja2 import Environment, FileSystemLoader +STEPS = { + 'cfg': 'Project Creation', + 'syn': 'Synthesis', + 'par': 'Place and Route', + 'bit': 'Bitstream generation' +} + + class Project: """Base class to manage an FPGA project. @@ -210,23 +218,17 @@ def make(self, first='cfg', last='bit'): self.logger.debug('Executing make') if 'part' not in self.data: self.logger.info('Using the default PART') - steps = { - 'cfg': 'Project Creation', - 'syn': 'Synthesis', - 'par': 'Place and Route', - 'bit': 'Bitstream generation' - } - if last not in steps: + if last not in STEPS: raise ValueError('Invalid last step.') - if first not in steps: + if first not in STEPS: raise ValueError('Invalid first step.') - keys = list(steps.keys()) + keys = list(STEPS.keys()) index = [keys.index(first), keys.index(last)] if index[0] > index[1]: raise ValueError('Invalid steps combination.') - message = f'from {steps[first]} to {steps[last]}' + message = f'from {STEPS[first]} to {STEPS[last]}' if first == last: - message = steps[first] + message = STEPS[first] self.logger.info('Running %s', message) self.data['steps'] = keys[index[0]:index[1]+1] self._make_custom() From 3f9f8b419589280d7d805390fa0ac29ccd36d105 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sat, 13 Jul 2024 20:35:18 -0300 Subject: [PATCH 176/254] Make TOOLS accessible for external access --- pyfpga/factory.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyfpga/factory.py b/pyfpga/factory.py index 197b69d3..80782183 100644 --- a/pyfpga/factory.py +++ b/pyfpga/factory.py @@ -17,7 +17,7 @@ from pyfpga.vivado import Vivado -tools = { +TOOLS = { 'ise': Ise, 'libero': Libero, 'openflow': Openflow, @@ -31,9 +31,9 @@ class Factory: def __init__(self, tool='vivado', project=None, odir='results'): """Class constructor.""" - if tool not in tools: + if tool not in TOOLS: raise NotImplementedError(f'{tool} is unsupported') - self._instance = tools[tool](project, odir) + self._instance = TOOLS[tool](project, odir) def __getattr__(self, name): """Delegate attribute access to the tool instance.""" From 6661d219a577f93cc3ea39656d306ba93b16238b Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Mon, 15 Jul 2024 00:01:08 -0300 Subject: [PATCH 177/254] hdl2bit and prj2bit reimplemented --- pyfpga/helpers/hdl2bit.py | 123 +++++++++++++++++++++++--------------- pyfpga/helpers/prj2bit.py | 68 +++++++++------------ 2 files changed, 103 insertions(+), 88 deletions(-) diff --git a/pyfpga/helpers/hdl2bit.py b/pyfpga/helpers/hdl2bit.py index 8b6d9173..13d36507 100644 --- a/pyfpga/helpers/hdl2bit.py +++ b/pyfpga/helpers/hdl2bit.py @@ -1,38 +1,35 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020 PyFPGA Project +# Copyright (C) 2020-2024 PyFPGA Project # # SPDX-License-Identifier: GPL-3.0-or-later # """ -A CLI helper utility to go from FPGA design files to a bitstream. +A CLI helper utility to transform HDL design files into a bitstream. """ import argparse -import logging import sys -from fpga import __version__ as version -from fpga.project import Project, TOOLS -from fpga.tool import TASKS +from pathlib import Path +from pyfpga import __version__ as version +from pyfpga.factory import Factory, TOOLS +from pyfpga.project import STEPS -logging.basicConfig() -logging.getLogger('fpga.project').level = logging.INFO +tools = list(TOOLS.keys()) +steps = list(STEPS.keys()) -EPILOGUE = """ +EPILOGUE = f""" Supported values of arguments with choices: -* TOOL = {} -* TASK = {} +* TOOL = {'|'.join(tools)} +* STEP = {'|'.join(steps)} Notes: * PATH and FILE must be relative to the execution directory. * The default PART name and how to specify it depends on the selected TOOL. * More than one '--file', '--include' or '--param' arguments can be specified. -""".format( - " | ".join(TOOLS), - " | ".join(TASKS[1:len(TASKS)]) -) +""" def main(): @@ -49,28 +46,22 @@ def main(): parser.add_argument( '-v', '--version', action='version', - version='v{}'.format(version) - ) - - parser.add_argument( - 'top', - metavar='TOPFILE', - help='a top-level file' + version=f'v{version}' ) parser.add_argument( '-t', '--tool', metavar='TOOL', default='vivado', - choices=TOOLS, + choices=tools, help='backend tool to be used [vivado]' ) parser.add_argument( - '-o', '--outdir', + '-o', '--odir', metavar='PATH', - default='temp', - help='where to generate files [temp]' + default='results', + help='where to generate files [results]' ) parser.add_argument( @@ -81,67 +72,105 @@ def main(): parser.add_argument( '-f', '--file', - metavar='FILE[,PACKAGE]', + metavar='FILE[,LIBRARY]', action='append', - help='add a design file (specifying an optional VHDL package)' + help='add a design file (optionally specifying a VHDL library)' ) parser.add_argument( '-i', '--include', metavar='PATH', action='append', - help='specify where to search Verilog included files' + help='specify a Verilog Include directory' + ) + + parser.add_argument( + '--define', + metavar=('DEFINE', 'VALUE'), + action='append', + nargs=2, + help='define and set the value of a Verilog Define' ) parser.add_argument( '--param', - metavar=('GENERIC/PARAMETER', 'VALUE'), + metavar=('PARAMETER', 'VALUE'), action='append', nargs=2, - help='set the value of a generic/parameter of the top-level' + help='set the value of a Generic/Parameter of the top-level' ) parser.add_argument( - '--run', - metavar='TASK', - choices=TASKS[1:len(TASKS)], + '--project', + metavar='PROJECT', + help='optional PROJECT name' + ) + + parser.add_argument( + '--last', + metavar='STEP', + choices=steps, default='bit', - help='task to perform [{}]'.format('bit') + help=f'last step to perform [{steps[-1]}] ({"|".join(steps)})' + ) + + parser.add_argument( + 'toplevel', + metavar='TOPLEVEL', + help='the top-level name' ) args = parser.parse_args() + # ------------------------------------------------------------------------- # Solving with PyFPGA + # ------------------------------------------------------------------------- - prj = Project(args.tool, relative_to_script=False) - prj.set_outdir(args.outdir) + project = args.project or args.tool + + prj = Factory(args.tool, project, odir=args.odir) if args.part is not None: prj.set_part(args.part) if args.include is not None: for include in args.include: - prj.add_vlog_include(include) + prj.add_include(include) if args.file is not None: for file in args.file: - file = file.split(',') - if len(file) > 1: - prj.add_files(file[0], library=file[1]) + aux = file.split(',') + file = aux[0] + lib = aux[1] if len(aux) > 1 else None + ext = Path(file).suffix.lower() + if ext == '.v': + print(f'* Adding Verilog file: {file}') + prj.add_vlog(file) + elif ext == '.sv': + print(f'* Adding System Verilog file: {file}') + prj.add_slog(file) + elif ext in ['.vhd', '.vhdl']: + if lib: + print(f'* Adding VHDL file: {file} (library={lib})') + else: + print(f'* Adding VHDL file: {file}') + prj.add_vhdl(file, lib) else: - prj.add_files(file[0]) + print(f'* Adding Constraint file: {file}') + prj.add_cons(file) + + if args.define is not None: + for define in args.define: + prj.add_define(define[0], define[1]) if args.param is not None: for param in args.param: prj.add_param(param[0], param[1]) - prj.add_files(args.top) - prj.set_top(args.top) + prj.set_top(args.toplevel) try: - prj.generate(args.run) - except RuntimeError: - logging.error('{} not found'.format(args.tool)) + prj.make(last=args.last) except Exception as e: sys.exit('{} ({})'.format(type(e).__name__, e)) diff --git a/pyfpga/helpers/prj2bit.py b/pyfpga/helpers/prj2bit.py index 4a0a6886..1124e452 100644 --- a/pyfpga/helpers/prj2bit.py +++ b/pyfpga/helpers/prj2bit.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020 PyFPGA Project +# Copyright (C) 2020-2024 PyFPGA Project # # SPDX-License-Identifier: GPL-3.0-or-later # @@ -10,16 +10,14 @@ """ import argparse -import logging -import os import sys -from fpga import __version__ as version -from fpga.project import Project -from fpga.tool import TASKS +from pathlib import Path +from pyfpga import __version__ as version +from pyfpga.factory import Factory, TOOLS +from pyfpga.project import STEPS -logging.basicConfig() -logging.getLogger('fpga.project').level = logging.INFO +steps = list(STEPS.keys())[1:len(STEPS)] def main(): @@ -34,29 +32,21 @@ def main(): parser.add_argument( '-v', '--version', action='version', - version='v{}'.format(version) + version=f'v{version}' ) parser.add_argument( - 'project', - metavar='PRJFILE', - help='a vendor project file' - ) - - parser.add_argument( - '--run', - metavar='TASK', - choices=TASKS[1:len(TASKS)], + '--last', + metavar='STEP', + choices=steps, default='bit', - help='task to perform [{}] ({})'.format( - 'bit', " | ".join(TASKS[1:len(TASKS)]) - ) + help=f'last step to perform [{steps[-1]}] ({"|".join(steps)})' ) parser.add_argument( - '--clean', - action='store_true', - help='clean the generated project files' + 'prjfile', + metavar='PRJFILE', + help='a vendor Project File' ) args = parser.parse_args() @@ -70,34 +60,30 @@ def main(): '.xpr': 'vivado' } - if not os.path.exists(args.project): - sys.exit('Project file not found') + prjfile = Path(args.prjfile) + + if not prjfile.exists(): + sys.exit('file not found.') - outdir = os.path.dirname(args.project) - project, extension = os.path.splitext(args.project) - project = os.path.basename(project) + directory = prjfile.parent + base_name = prjfile.stem + extension = prjfile.suffix tool = '' if extension in tool_per_ext: tool = tool_per_ext[extension] - print('{} Project file found.'.format(tool)) + print(f'* {tool} project file found.') else: - sys.exit('Unknown Project file extension') + sys.exit('Unknown project file extension') + # ------------------------------------------------------------------------- # Solving with PyFPGA + # ------------------------------------------------------------------------- - prj = Project(tool, project=project, relative_to_script=False) - prj.set_outdir(outdir) - - prj.set_top(project) + prj = Factory(tool, base_name, directory) try: - if args.clean: - prj.clean() - else: - prj.generate(args.run, 'syn') - except RuntimeError: - logging.error('{} not found'.format(tool)) + prj.make('syn', args.last) except Exception as e: sys.exit('{} ({})'.format(type(e).__name__, e)) From 0df825dbab08398ab749c2f42f4a78e14473c48a Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Mon, 15 Jul 2024 00:01:47 -0300 Subject: [PATCH 178/254] Remove info message not compatible with prj2bit --- pyfpga/project.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/pyfpga/project.py b/pyfpga/project.py index 4f4d9de0..72e7edc0 100644 --- a/pyfpga/project.py +++ b/pyfpga/project.py @@ -216,8 +216,6 @@ def make(self, first='cfg', last='bit'): .. note:: valid steps are ``cfg``, ``syn``, ``par`` and ``bit``. """ self.logger.debug('Executing make') - if 'part' not in self.data: - self.logger.info('Using the default PART') if last not in STEPS: raise ValueError('Invalid last step.') if first not in STEPS: From 2f2cdf3a54ac6cba9294e2887276ab8ce97c1021 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Mon, 15 Jul 2024 00:41:08 -0300 Subject: [PATCH 179/254] Implemented new tests for helpers Closes #38 --- examples/helpers/Makefile | 39 ---- examples/helpers/ise.sh | 18 ++ examples/helpers/ise.xise | 353 ----------------------------------- examples/helpers/libero.sh | 18 ++ examples/helpers/openflow.sh | 11 ++ examples/helpers/quartus.qpf | 3 - examples/helpers/quartus.qsf | 24 --- examples/helpers/quartus.sh | 18 ++ examples/helpers/vivado.sh | 18 ++ examples/helpers/vivado.xpr | 204 -------------------- 10 files changed, 83 insertions(+), 623 deletions(-) delete mode 100644 examples/helpers/Makefile create mode 100644 examples/helpers/ise.sh delete mode 100644 examples/helpers/ise.xise create mode 100644 examples/helpers/libero.sh create mode 100644 examples/helpers/openflow.sh delete mode 100644 examples/helpers/quartus.qpf delete mode 100644 examples/helpers/quartus.qsf create mode 100644 examples/helpers/quartus.sh create mode 100644 examples/helpers/vivado.sh delete mode 100644 examples/helpers/vivado.xpr diff --git a/examples/helpers/Makefile b/examples/helpers/Makefile deleted file mode 100644 index dbec8388..00000000 --- a/examples/helpers/Makefile +++ /dev/null @@ -1,39 +0,0 @@ -#!/usr/bin/make - -OUTDIR = ../../build/helpers -HLPDIR = ../../fpga/helpers -HDLDIR = ../../hdl - -all: hdl2bit-vhdl hdl2bit-verilog prj2bit bitprog - -hdl2bit-vhdl: - python3 $(HLPDIR)/hdl2bit.py \ - --outdir $(OUTDIR) \ - --tool ise --run syn \ - -f $(HDLDIR)/*.vhdl,examples $(HDLDIR)/top.vhdl - -hdl2bit-verilog: - python3 $(HLPDIR)/hdl2bit.py \ - --outdir $(OUTDIR) \ - --tool ise --run syn \ - -i $(HDLDIR)/headers1 -i $(HDLDIR)/headers2 \ - -f $(HDLDIR)/blinking.v $(HDLDIR)/top.v - -prj2bit: - mkdir -p $(OUTDIR) - cp ise.xise quartus.* vivado.xpr $(OUTDIR) - python3 $(HLPDIR)/prj2bit.py --run syn $(OUTDIR)/ise.xise - python3 $(HLPDIR)/prj2bit.py --run syn $(OUTDIR)/quartus.qpf - python3 $(HLPDIR)/prj2bit.py --run syn $(OUTDIR)/vivado.xpr - -bitprog: - python3 $(HLPDIR)/bitprog.py \ - --outdir $(OUTDIR) \ - --tool ise \ - -d fpga -p 2 \ - ../../README.md - -clean: - python3 $(HLPDIR)/prj2bit.py --clean $(OUTDIR)/ise.xise - python3 $(HLPDIR)/prj2bit.py --clean $(OUTDIR)/quartus.qpf - python3 $(HLPDIR)/prj2bit.py --clean $(OUTDIR)/vivado.xpr diff --git a/examples/helpers/ise.sh b/examples/helpers/ise.sh new file mode 100644 index 00000000..11e73ba8 --- /dev/null +++ b/examples/helpers/ise.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +set -e + +HDIR=../../pyfpga/helpers + +python3 $HDIR/hdl2bit.py -t ise -o results/ise-vlog -p xc6slx16-3-csg32 \ + -i ../sources/vlog/include1 -i ../sources/vlog/include2 \ + -f ../sources/vlog/blink.v -f ../sources/vlog/top.v \ + -f ../sources/cons/nexys3/clk.xcf -f ../sources/cons/nexys3/clk.ucf -f ../sources/cons/nexys3/led.ucf \ + --define DEFINE1 1 --define DEFINE2 1 --param FREQ 125000000 --param SECS 1 Top + +python3 $HDIR/hdl2bit.py -t ise -o results/ise-vhdl -p xc6slx16-3-csg32 --project example \ + -f ../sources/vhdl/blink.vhdl,blink_lib -f ../sources/vhdl/blink_pkg.vhdl,blink_lib -f ../sources/vhdl/top.vhdl \ + -f ../sources/cons/nexys3/clk.xcf -f ../sources/cons/nexys3/clk.ucf -f ../sources/cons/nexys3/led.ucf \ + --param FREQ 125000000 --param SECS 1 --last cfg Top + +python3 $HDIR/prj2bit.py results/ise-vhdl/example.xise diff --git a/examples/helpers/ise.xise b/examples/helpers/ise.xise deleted file mode 100644 index 6233d9b1..00000000 --- a/examples/helpers/ise.xise +++ /dev/null @@ -1,353 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/helpers/libero.sh b/examples/helpers/libero.sh new file mode 100644 index 00000000..1a56da93 --- /dev/null +++ b/examples/helpers/libero.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +set -e + +HDIR=../../pyfpga/helpers + +python3 $HDIR/hdl2bit.py -t libero -o results/libero-vlog -p m2s010-1-tq144 \ + -i ../sources/vlog/include1 -i ../sources/vlog/include2 \ + -f ../sources/vlog/blink.v -f ../sources/vlog/top.v \ + -f ../sources/cons/maker/clk.sdc -f ../sources/cons/maker/clk.pdc -f ../sources/cons/maker/led.pdc \ + --define DEFINE1 1 --define DEFINE2 1 --param FREQ 125000000 --param SECS 1 Top + +python3 $HDIR/hdl2bit.py -t libero -o results/libero-vhdl -p m2s010-1-tq144 --project example \ + -f ../sources/vhdl/blink.vhdl,blink_lib -f ../sources/vhdl/blink_pkg.vhdl,blink_lib -f ../sources/vhdl/top.vhdl \ + -f ../sources/cons/maker/clk.sdc -f ../sources/cons/maker/clk.pdc -f ../sources/cons/maker/led.pdc \ + --param FREQ 125000000 --param SECS 1 --last cfg Top + +python3 $HDIR/prj2bit.py results/libero-vhdl/libero/example.prjx diff --git a/examples/helpers/openflow.sh b/examples/helpers/openflow.sh new file mode 100644 index 00000000..7b959a1a --- /dev/null +++ b/examples/helpers/openflow.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +set -e + +HDIR=../../pyfpga/helpers + +python3 $HDIR/hdl2bit.py -t openflow -o results/openflow-vlog -p hx1k-tq144 \ + -i ../sources/vlog/include1 -i ../sources/vlog/include2 \ + -f ../sources/vlog/blink.v -f ../sources/vlog/top.v \ + -f ../sources/cons/icestick/clk.pcf -f ../sources/cons/icestick/led.pcf \ + --define DEFINE1 1 --define DEFINE2 1 --param FREQ 100000000 --param SECS 1 Top diff --git a/examples/helpers/quartus.qpf b/examples/helpers/quartus.qpf deleted file mode 100644 index 4abe28eb..00000000 --- a/examples/helpers/quartus.qpf +++ /dev/null @@ -1,3 +0,0 @@ -QUARTUS_VERSION = "18.1" -DATE = "22:03:07 March 16, 2020" -PROJECT_REVISION = "quartus" diff --git a/examples/helpers/quartus.qsf b/examples/helpers/quartus.qsf deleted file mode 100644 index 3c046b57..00000000 --- a/examples/helpers/quartus.qsf +++ /dev/null @@ -1,24 +0,0 @@ -set_global_assignment -name FAMILY "Cyclone V" -set_global_assignment -name DEVICE 5CSEBA6U23I7 -set_global_assignment -name TOP_LEVEL_ENTITY top -set_global_assignment -name ORIGINAL_QUARTUS_VERSION 18.1.0 -set_global_assignment -name PROJECT_CREATION_TIME_DATE "22:03:07 MARCH 16, 2020" -set_global_assignment -name LAST_QUARTUS_VERSION "18.1.0 Lite Edition" -set_global_assignment -name PROJECT_OUTPUT_DIRECTORY output_files -set_global_assignment -name MIN_CORE_JUNCTION_TEMP "-40" -set_global_assignment -name MAX_CORE_JUNCTION_TEMP 100 -set_global_assignment -name ERROR_CHECK_FREQUENCY_DIVISOR 256 -set_global_assignment -name EDA_SIMULATION_TOOL "ModelSim-Altera (Verilog)" -set_global_assignment -name EDA_TIME_SCALE "1 ps" -section_id eda_simulation -set_global_assignment -name EDA_OUTPUT_DATA_FORMAT "VERILOG HDL" -section_id eda_simulation -set_global_assignment -name POWER_PRESET_COOLING_SOLUTION "23 MM HEAT SINK WITH 200 LFPM AIRFLOW" -set_global_assignment -name POWER_BOARD_THERMAL_MODEL "NONE (CONSERVATIVE)" -set_global_assignment -name TCL_SCRIPT_FILE ../../examples/quartus/de10nano.tcl -set_global_assignment -name SDC_FILE ../../examples/quartus/de10nano.sdc -set_global_assignment -name VHDL_FILE ../../hdl/top.vhdl -set_global_assignment -name VHDL_FILE ../../hdl/examples_pkg.vhdl -library examples -set_global_assignment -name VHDL_FILE ../../hdl/blinking.vhdl -library examples -set_global_assignment -name PARTITION_NETLIST_TYPE SOURCE -section_id Top -set_global_assignment -name PARTITION_FITTER_PRESERVATION_LEVEL PLACEMENT_AND_ROUTING -section_id Top -set_global_assignment -name PARTITION_COLOR 16764057 -section_id Top -set_instance_assignment -name PARTITION_HIERARCHY root_partition -to | -section_id Top diff --git a/examples/helpers/quartus.sh b/examples/helpers/quartus.sh new file mode 100644 index 00000000..b2ae4437 --- /dev/null +++ b/examples/helpers/quartus.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +set -e + +HDIR=../../pyfpga/helpers + +python3 $HDIR/hdl2bit.py -t quartus -o results/quartus-vlog -p 5CSEBA6U23I7 \ + -i ../sources/vlog/include1 -i ../sources/vlog/include2 \ + -f ../sources/vlog/blink.v -f ../sources/vlog/top.v \ + -f ../sources/cons/de10nano/clk.sdc -f ../sources/cons/de10nano/clk.tcl -f ../sources/cons/de10nano/led.tcl \ + --define DEFINE1 1 --define DEFINE2 1 --param FREQ 125000000 --param SECS 1 Top + +python3 $HDIR/hdl2bit.py -t quartus -o results/quartus-vhdl -p 5CSEBA6U23I7 --project example \ + -f ../sources/vhdl/blink.vhdl,blink_lib -f ../sources/vhdl/blink_pkg.vhdl,blink_lib -f ../sources/vhdl/top.vhdl \ + -f ../sources/cons/de10nano/clk.sdc -f ../sources/cons/de10nano/clk.tcl -f ../sources/cons/de10nano/led.tcl \ + --param FREQ 125000000 --param SECS 1 --last cfg Top + +python3 $HDIR/prj2bit.py results/quartus-vhdl/example.qpf diff --git a/examples/helpers/vivado.sh b/examples/helpers/vivado.sh new file mode 100644 index 00000000..868599a3 --- /dev/null +++ b/examples/helpers/vivado.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +set -e + +HDIR=../../pyfpga/helpers + +python3 $HDIR/hdl2bit.py -t vivado -o results/vivado-vlog -p xc7z010-1-clg400 \ + -i ../sources/vlog/include1 -i ../sources/vlog/include2 \ + -f ../sources/vlog/blink.v -f ../sources/vlog/top.v \ + -f ../sources/cons/ZYBO/timing.xdc -f ../sources/cons/ZYBO/clk.xdc -f ../sources/cons/ZYBO/led.xdc \ + --define DEFINE1 1 --define DEFINE2 1 --param FREQ 125000000 --param SECS 1 Top + +python3 $HDIR/hdl2bit.py -t vivado -o results/vivado-vhdl -p xc7z010-1-clg400 --project example \ + -f ../sources/vhdl/blink.vhdl,blink_lib -f ../sources/vhdl/blink_pkg.vhdl,blink_lib -f ../sources/vhdl/top.vhdl \ + -f ../sources/cons/ZYBO/timing.xdc -f ../sources/cons/ZYBO/clk.xdc -f ../sources/cons/ZYBO/led.xdc \ + --param FREQ 125000000 --param SECS 1 --last cfg Top + +python3 $HDIR/prj2bit.py results/vivado-vhdl/example.xpr diff --git a/examples/helpers/vivado.xpr b/examples/helpers/vivado.xpr deleted file mode 100644 index 521ddb20..00000000 --- a/examples/helpers/vivado.xpr +++ /dev/null @@ -1,204 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Vivado Synthesis Defaults - - - - - - - - - - - Default settings for Implementation. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - default_dashboard - - - From 79bb0b4d81c57760615f24332735275192423f89 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Mon, 15 Jul 2024 01:10:09 -0300 Subject: [PATCH 180/254] Add workaround for prj2bit --- pyfpga/templates/libero.jinja | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pyfpga/templates/libero.jinja b/pyfpga/templates/libero.jinja index 6962f97d..aaddbf9b 100644 --- a/pyfpga/templates/libero.jinja +++ b/pyfpga/templates/libero.jinja @@ -83,7 +83,9 @@ close_project {% if 'syn' in steps or 'par' in steps or 'bit' in steps %}# Design flow ----------------------------------------------------------------- -open_project {{ project }}/{{ project }}.prjx +if { [catch {open_project {{ project }}/{{ project }}.prjx} ] } { + open_project {{ project }}.prjx +} {% if 'syn' in steps %}# Synthesis From d9b02da7709e5819bf843be5c4bd84a5d0b2cced Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Mon, 15 Jul 2024 01:49:45 -0300 Subject: [PATCH 181/254] docs: added helpers --- docs/Makefile | 6 +++++- docs/helpers.rst | 12 ++++++++++++ docs/index.rst | 1 + docs/tools.rst | 13 ++++--------- 4 files changed, 22 insertions(+), 10 deletions(-) create mode 100644 docs/helpers.rst diff --git a/docs/Makefile b/docs/Makefile index d4bb2cbb..48576179 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -14,7 +14,11 @@ help: .PHONY: help Makefile +$(BUILDDIR)/hdl2bit $(BUILDDIR)/prj2bit: + @mkdir -p $(@D) + @python3 ../pyfpga/helpers/$(@F).py -h > $@ + # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). -%: Makefile +%: Makefile $(BUILDDIR)/hdl2bit $(BUILDDIR)/prj2bit @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/helpers.rst b/docs/helpers.rst new file mode 100644 index 00000000..e5623c92 --- /dev/null +++ b/docs/helpers.rst @@ -0,0 +1,12 @@ +Helpers +======= + +hdl2bit +------- + +.. literalinclude:: _build/hdl2bit + +prj2bit +------- + +.. literalinclude:: _build/prj2bit diff --git a/docs/index.rst b/docs/index.rst index 88c75aa4..ad539fe4 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -17,5 +17,6 @@ PyFPGA's documentation basic advanced api + helpers tools internals diff --git a/docs/tools.rst b/docs/tools.rst index 0bbbcc5a..a7c6dc55 100644 --- a/docs/tools.rst +++ b/docs/tools.rst @@ -1,25 +1,20 @@ Tools support ============= -.. ATTENTION:: - - (2024-05-31) To be updated. - +---------------+-----------+---------+-----+-----------------------------------------------+ | Tools | Vendor | Version | Tcl | Comment | +===============+===========+=========+=====+===============================================+ | ISE | Xilinx | 14.7 | 8.4 | Discontinued in 2013 | +---------------+-----------+---------+-----+-----------------------------------------------+ -| Libero-SoC | Microsemi | 12.2 | 8.5 | Important changes in version 12.0 (2019) | +| Libero-SoC | Microsemi | 2024.1 | 8.5 | Important changes in version 12.0 (2019) | +---------------+-----------+---------+-----+-----------------------------------------------+ -| Quartus Prime | Intel | 19.1 | 8.6 | Known as Quartus II until version 15.0 (2015) | +| Openflow | | | | | +---------------+-----------+---------+-----+-----------------------------------------------+ -| Vivado | Xilinx | 2019.1 | 8.5 | Introduced in 2012, it superseded ISE | +| Quartus Prime | Intel | 23.1 | 8.6 | Known as Quartus II until version 15.0 (2015) | +---------------+-----------+---------+-----+-----------------------------------------------+ -| Yosys | | 0.9-dev | 8.6 | The open-source synthesizer | +| Vivado | Xilinx | 2022.1 | 8.5 | Introduced in 2012, it superseded ISE | +---------------+-----------+---------+-----+-----------------------------------------------+ - * ISE supports devices starting from Spartan 3/Virtex 4 until some first members of the 7 series. Previous Spartan/Virtex devices were supported until version 10. Vivado supports devices starting from the 7 series. From d713c90793c9b71fdb1b5d9c7a212813adc11557 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Mon, 15 Jul 2024 01:56:50 -0300 Subject: [PATCH 182/254] ci: add pyfpga as dependency --- .github/workflows/docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 26c51962..0f17d958 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -12,7 +12,7 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 - name: Install dependencies - run: pip install sphinx sphinx-rtd-theme + run: pip install . && pip install sphinx sphinx-rtd-theme - name: Build documentation run: make docs - name: Deploy to GitHub Pages From 42fe56d3ea1a223202aa3de0c445921130543dbd Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Wed, 17 Jul 2024 20:00:23 -0300 Subject: [PATCH 183/254] bitprog reimplemented --- pyfpga/helpers/bitprog.py | 107 +++++++++++++++----------------------- 1 file changed, 42 insertions(+), 65 deletions(-) diff --git a/pyfpga/helpers/bitprog.py b/pyfpga/helpers/bitprog.py index 0282f263..6af85884 100644 --- a/pyfpga/helpers/bitprog.py +++ b/pyfpga/helpers/bitprog.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020 PyFPGA Project +# Copyright (C) 2020-2024 PyFPGA Project # # SPDX-License-Identifier: GPL-3.0-or-later # @@ -10,34 +10,25 @@ """ import argparse -import logging import sys -from fpga import __version__ as version -from fpga.project import Project, TOOLS -from fpga.tool import MEMWIDTHS +from pathlib import Path +from pyfpga import __version__ as version +from pyfpga.factory import Factory, TOOLS +from pyfpga.project import STEPS -logging.basicConfig() -logging.getLogger('fpga.project').level = logging.INFO +tools = list(TOOLS.keys()) +devs = ['fpga', 'spi', 'bpi'] +positions = range(1, 10) +widths = [2**i for i in range(6)] -DEVS = ['fpga', 'spi', 'bpi'] -POSITIONS = range(1, 10) -ACTIONS = ['program', 'detect', 'unlock'] - -EPILOGUE = """ +EPILOGUE = f""" Supported values of arguments with choices: -* TOOL = {} -* DEVTYPE = {} -* POSITIONS = {} -* MEMWIDTH = {} -* ACTION = {} -""".format( - " | ".join(TOOLS), - " | ".join(DEVS), - " | ".join(str(x) for x in POSITIONS), - " | ".join(str(x) for x in MEMWIDTHS), - " | ".join(ACTIONS) -) +* TOOL = {'|'.join(tools)} +* TYPE = {'|'.join(devs)} +* POSITION = {'|'.join(map(str, positions))} +* WIDTH = {'|'.join(map(str, widths))} +""" def main(): @@ -54,43 +45,36 @@ def main(): parser.add_argument( '-v', '--version', action='version', - version='v{}'.format(version) - ) - - parser.add_argument( - 'bit', - metavar='BITFILE', - nargs='?', - help='a bitstream file' + version=f'v{version}' ) parser.add_argument( '-t', '--tool', metavar='TOOL', default='vivado', - choices=TOOLS, + choices=tools, help='backend tool to be used [vivado]' ) parser.add_argument( - '-o', '--outdir', + '-o', '--odir', metavar='PATH', - default='temp', - help='where to generate files [temp]' + default='results', + help='where to generate files [results]' ) parser.add_argument( '-d', '--device', - metavar='DEVTYPE', - choices=DEVS, - default=DEVS[0], - help='the target device type [{}]'.format(DEVS[0]) + metavar='TYPE', + choices=devs, + default=devs[0], + help=f'the target device type [{devs[0]}]' ) parser.add_argument( '-p', '--position', metavar='POSITION', - choices=POSITIONS, + choices=positions, type=int, default=1, help='the device position into the JTAG chain [1]' @@ -98,47 +82,40 @@ def main(): parser.add_argument( '-m', '--memname', - metavar='MEMNAME', - default='', - help='memory name if spi or bpi selected' + metavar='NAME', + help='memory name for SPI or BPI devices [None]' ) parser.add_argument( '-w', '--width', - metavar='MEMWIDTH', - choices=MEMWIDTHS, + metavar='WIDTH', + choices=widths, type=int, default=1, - help='memory width if spi or bpi selected [1]' + help='memory width for SPI or BPI devices [1]' ) parser.add_argument( - '--run', - metavar='ACTION', - choices=ACTIONS, - default=ACTIONS[0], - help='action to perform [{}]'.format(ACTIONS[0]) + 'bit', + metavar='BITFILE', + help='a bitstream file' ) args = parser.parse_args() + # ------------------------------------------------------------------------- # Solving with PyFPGA + # ------------------------------------------------------------------------- - prj = Project(args.tool, relative_to_script=False) - prj.set_outdir(args.outdir) - - if args.run == 'program': - devtype = args.device - prj.set_bitstream(args.bit) - elif args.run == 'detect': - devtype = 'detect' - else: # args.run == 'unlock' - devtype = 'unlock' + prj = Factory(args.tool, odir=args.odir) try: - prj.transfer(devtype, args.position, args.memname, args.width) - except RuntimeError: - logging.error('{} not found'.format(args.tool)) + if args.device == 'fpga': + prj.prog(args.bit, args.position) + if args.device == 'spi': + prj.prog_spi(args.bit, args.position, args.width, args.memname) + if args.device == 'bpi': + prj.prog_bpi(args.bit, args.position, args.width, args.memname) except Exception as e: sys.exit('{} ({})'.format(type(e).__name__, e)) From 995eae3b119c3a2af4c87195c571ee78547198f3 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Wed, 17 Jul 2024 20:02:37 -0300 Subject: [PATCH 184/254] docs: add bitprog help --- docs/Makefile | 13 +++---------- docs/helpers.rst | 5 +++++ 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/docs/Makefile b/docs/Makefile index 48576179..a9325d75 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -1,24 +1,17 @@ -# Minimal makefile for Sphinx documentation -# - -# You can set these variables from the command line, and also -# from the environment for the first two. SPHINXOPTS ?= SPHINXBUILD ?= sphinx-build SOURCEDIR = . BUILDDIR = _build +HELPERS = $(BUILDDIR)/hdl2bit $(BUILDDIR)/prj2bit $(BUILDDIR)/bitprog -# Put it first so that "make" without argument is like "make help". help: @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) .PHONY: help Makefile -$(BUILDDIR)/hdl2bit $(BUILDDIR)/prj2bit: +$(HELPERS): @mkdir -p $(@D) @python3 ../pyfpga/helpers/$(@F).py -h > $@ -# Catch-all target: route all unknown targets to Sphinx using the new -# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). -%: Makefile $(BUILDDIR)/hdl2bit $(BUILDDIR)/prj2bit +%: Makefile $(HELPERS) @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/helpers.rst b/docs/helpers.rst index e5623c92..542deb27 100644 --- a/docs/helpers.rst +++ b/docs/helpers.rst @@ -10,3 +10,8 @@ prj2bit ------- .. literalinclude:: _build/prj2bit + +bitprog +------- + +.. literalinclude:: _build/bitprog From 6220dbdd07cb1181bcccb577dffe88b3a1c47bd7 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Mon, 5 Aug 2024 20:51:40 -0300 Subject: [PATCH 185/254] Modified to use TOOLS for choices --- tests/projects/support.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/projects/support.py b/tests/projects/support.py index 2e26e3b5..68bc88b1 100644 --- a/tests/projects/support.py +++ b/tests/projects/support.py @@ -3,12 +3,13 @@ import argparse import sys -from pyfpga.factory import Factory +from pyfpga.factory import Factory, TOOLS + parser = argparse.ArgumentParser() parser.add_argument( '--tool', default='openflow', - choices=['ise', 'libero', 'quartus', 'openflow', 'vivado'] + choices=list(TOOLS.keys()) ) args = parser.parse_args() From cc89caf82268d7769e6c3a3717cef7f899ded4f3 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Mon, 5 Aug 2024 21:59:23 -0300 Subject: [PATCH 186/254] Modify tests to be able of run pytest from project root --- Makefile | 2 +- tests/test_data.py | 72 ++++++++++++++++++++++++++++----------------- tests/test_tools.py | 18 +++++++----- 3 files changed, 56 insertions(+), 36 deletions(-) diff --git a/Makefile b/Makefile index f9eda4cb..fb528da7 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ lint: git diff --check --cached test: - cd tests; pytest + pytest clean: py3clean . diff --git a/tests/test_data.py b/tests/test_data.py index 3ba4e4a9..a8e2af19 100644 --- a/tests/test_data.py +++ b/tests/test_data.py @@ -2,41 +2,59 @@ from pyfpga.vivado import Vivado +tdir = Path(__file__).parent.resolve() + pattern = { 'project': 'EXAMPLE', 'part': 'PARTNAME', 'includes': [ - Path('fakedata/dir1').resolve().as_posix(), - Path('fakedata/dir2').resolve().as_posix(), - Path('fakedata/dir3').resolve().as_posix() + Path(tdir / 'fakedata/dir1').resolve().as_posix(), + Path(tdir / 'fakedata/dir2').resolve().as_posix(), + Path(tdir / 'fakedata/dir3').resolve().as_posix() ], 'files': { - Path('fakedata/vhdl0.vhdl').resolve().as_posix(): { + Path(tdir / 'fakedata/vhdl0.vhdl').resolve().as_posix(): { 'hdl': 'vhdl', 'lib': 'LIB' }, - Path('fakedata/dir1/vhdl1.vhdl').resolve().as_posix(): { + Path(tdir / 'fakedata/dir1/vhdl1.vhdl').resolve().as_posix(): { 'hdl': 'vhdl', 'lib': 'LIB' }, - Path('fakedata/dir2/vhdl2.vhdl').resolve().as_posix(): { + Path(tdir / 'fakedata/dir2/vhdl2.vhdl').resolve().as_posix(): { 'hdl': 'vhdl', 'lib': 'LIB' }, - Path('fakedata/dir3/vhdl3.vhdl').resolve().as_posix(): { + Path(tdir / 'fakedata/dir3/vhdl3.vhdl').resolve().as_posix(): { 'hdl': 'vhdl', 'lib': 'LIB' }, - Path('fakedata/vlog0.v').resolve().as_posix(): {'hdl': 'vlog'}, - Path('fakedata/dir1/vlog1.v').resolve().as_posix(): {'hdl': 'vlog'}, - Path('fakedata/dir2/vlog2.v').resolve().as_posix(): {'hdl': 'vlog'}, - Path('fakedata/dir3/vlog3.v').resolve().as_posix(): {'hdl': 'vlog'}, - Path('fakedata/slog0.sv').resolve().as_posix(): {'hdl': 'slog'}, - Path('fakedata/dir1/slog1.sv').resolve().as_posix(): {'hdl': 'slog'}, - Path('fakedata/dir2/slog2.sv').resolve().as_posix(): {'hdl': 'slog'}, - Path('fakedata/dir3/slog3.sv').resolve().as_posix(): {'hdl': 'slog'} + Path(tdir / 'fakedata/vlog0.v').resolve().as_posix(): { + 'hdl': 'vlog' + }, + Path(tdir / 'fakedata/dir1/vlog1.v').resolve().as_posix(): { + 'hdl': 'vlog' + }, + Path(tdir / 'fakedata/dir2/vlog2.v').resolve().as_posix(): { + 'hdl': 'vlog' + }, + Path(tdir / 'fakedata/dir3/vlog3.v').resolve().as_posix(): { + 'hdl': 'vlog' + }, + Path(tdir / 'fakedata/slog0.sv').resolve().as_posix(): { + 'hdl': 'slog' + }, + Path(tdir / 'fakedata/dir1/slog1.sv').resolve().as_posix(): { + 'hdl': 'slog' + }, + Path(tdir / 'fakedata/dir2/slog2.sv').resolve().as_posix(): { + 'hdl': 'slog' + }, + Path(tdir / 'fakedata/dir3/slog3.sv').resolve().as_posix(): { + 'hdl': 'slog' + } }, 'top': 'TOPNAME', 'constraints': { - Path('fakedata/cons/all.xdc').resolve().as_posix(): 'all', - Path('fakedata/cons/syn.xdc').resolve().as_posix(): 'syn', - Path('fakedata/cons/par.xdc').resolve().as_posix(): 'par' + Path(tdir / 'fakedata/cons/all.xdc').resolve().as_posix(): 'all', + Path(tdir / 'fakedata/cons/syn.xdc').resolve().as_posix(): 'syn', + Path(tdir / 'fakedata/cons/par.xdc').resolve().as_posix(): 'par' }, 'params': { 'PAR1': 'VAL1', @@ -65,15 +83,15 @@ def test_data(): prj = Vivado('EXAMPLE') prj.set_part('PARTNAME') prj.set_top('TOPNAME') - prj.add_include('fakedata/dir1') - prj.add_include('fakedata/dir2') - prj.add_include('fakedata/dir3') - prj.add_slog('fakedata/**/*.sv') - prj.add_vhdl('fakedata/**/*.vhdl', 'LIB') - prj.add_vlog('fakedata/**/*.v') - prj.add_cons('fakedata/cons/all.xdc') - prj.add_cons('fakedata/cons/syn.xdc', 'syn') - prj.add_cons('fakedata/cons/par.xdc', 'par') + prj.add_include(str(tdir / 'fakedata/dir1')) + prj.add_include(str(tdir / 'fakedata/dir2')) + prj.add_include(str(tdir / 'fakedata/dir3')) + prj.add_slog(str(tdir / 'fakedata/**/*.sv')) + prj.add_vhdl(str(tdir / 'fakedata/**/*.vhdl'), 'LIB') + prj.add_vlog(str(tdir / 'fakedata/**/*.v')) + prj.add_cons(str(tdir / 'fakedata/cons/all.xdc')) + prj.add_cons(str(tdir / 'fakedata/cons/syn.xdc'), 'syn') + prj.add_cons(str(tdir / 'fakedata/cons/par.xdc'), 'par') prj.add_param('PAR1', 'VAL1') prj.add_param('PAR2', 'VAL2') prj.add_param('PAR3', 'VAL3') diff --git a/tests/test_tools.py b/tests/test_tools.py index 7c23c4a1..45f972ed 100644 --- a/tests/test_tools.py +++ b/tests/test_tools.py @@ -1,6 +1,8 @@ from pathlib import Path from pyfpga.factory import Factory +tdir = Path(__file__).parent.resolve() + def test_ise(): tool = 'ise' @@ -45,15 +47,15 @@ def generate(tool, part): prj = Factory(tool, odir=f'results/{tool}') prj.set_part(part) prj.set_top('TOPNAME') - prj.add_include('fakedata/dir1') - prj.add_include('fakedata/dir2') + prj.add_include(str(tdir / 'fakedata/dir1')) + prj.add_include(str(tdir / 'fakedata/dir2')) if tool != 'ise': - prj.add_slog('fakedata/**/*.sv') - prj.add_vhdl('fakedata/**/*.vhdl', 'LIB') - prj.add_vlog('fakedata/**/*.v') - prj.add_cons('fakedata/cons/all.xdc') - prj.add_cons('fakedata/cons/syn.xdc', 'syn') - prj.add_cons('fakedata/cons/par.xdc', 'par') + prj.add_slog(str(tdir / 'fakedata/**/*.sv')) + prj.add_vhdl(str(tdir / 'fakedata/**/*.vhdl'), 'LIB') + prj.add_vlog(str(tdir / 'fakedata/**/*.v')) + prj.add_cons(str(tdir / 'fakedata/cons/all.xdc')) + prj.add_cons(str(tdir / 'fakedata/cons/syn.xdc'), 'syn') + prj.add_cons(str(tdir / 'fakedata/cons/par.xdc'), 'par') prj.add_param('PAR1', 'VAL1') prj.add_param('PAR2', 'VAL2') prj.add_define('DEF1', 'VAL1') From 6fecb1d17c5d0ee247911625fa86f23b64f65141 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Thu, 8 Aug 2024 22:01:21 -0300 Subject: [PATCH 187/254] docs: added new sections --- docs/extending.rst | 2 ++ docs/index.rst | 3 ++- docs/tools.rst | 4 ++-- 3 files changed, 6 insertions(+), 3 deletions(-) create mode 100644 docs/extending.rst diff --git a/docs/extending.rst b/docs/extending.rst new file mode 100644 index 00000000..20d48463 --- /dev/null +++ b/docs/extending.rst @@ -0,0 +1,2 @@ +Extending +========= diff --git a/docs/index.rst b/docs/index.rst index ad539fe4..e9241b3e 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -16,7 +16,8 @@ PyFPGA's documentation intro basic advanced - api helpers + api tools internals + extending diff --git a/docs/tools.rst b/docs/tools.rst index a7c6dc55..ffd3d97b 100644 --- a/docs/tools.rst +++ b/docs/tools.rst @@ -1,5 +1,5 @@ -Tools support -============= +Tools +===== +---------------+-----------+---------+-----+-----------------------------------------------+ | Tools | Vendor | Version | Tcl | Comment | From e36413d6c41b9483cbcce375251320c288525699 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Thu, 8 Aug 2024 22:39:46 -0300 Subject: [PATCH 188/254] docs: complete introduction section --- docs/basic.rst | 18 +----------------- docs/intro.rst | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/docs/basic.rst b/docs/basic.rst index 824aea8d..b178c0fc 100644 --- a/docs/basic.rst +++ b/docs/basic.rst @@ -3,7 +3,7 @@ Basic usage .. ATTENTION:: - (2024-05-31) To be updated. + (2024-08-08) To be updated. Project Creation ---------------- @@ -18,22 +18,6 @@ name is used when *no name* is provided). prj = Project('vivado', 'projectName') -.. NOTE:: - - The supported tool are: ``ghdl``, ``ise``, ``libero``, ``openflow``, - ``quartus``, ``vivado``, ``yosys``, ``yosys-ise`` and ``yosys-vivado``. - -.. ATTENTION:: - - PyFPGA assumes that the backend Tool is ready to run. - This implies, depending on the operating system, things such as: - - * Tool installed. - * A valid License configured. - * Tool available in the system PATH. - * GNU/Linux: extra packages installed, environment variables assigned - and permissions granted on devices (to transfer the bitstream). - By default, the directory where the project is generated is called ``build`` and is located in the same place that the script, but another name and location can be specified. diff --git a/docs/intro.rst b/docs/intro.rst index c516b331..abc7e945 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -1,2 +1,26 @@ Introduction ============ + +PyFPGA is a Python package that provides an abstraction layer for working with FPGA development tools in a vendor-agnostic, programmatic way. It includes: + +* A **class** for each supported tool, enabling **project creation**, **synthesis**, **place and route**, **bitstream generation**, and **programming**. +* A set of **command-line** helpers for simple projects or quick evaluations. + +With PyFPGA, you can create your own FPGA development workflow tailored to your needs. Some of its key benefits include: + +* A unified API across different tools and devices. +* Compatibility with *Version Control Systems* and *Continuous Integration*. +* Ensured reproducibility and repeatability. +* Lower resource consumption compared to GUI-based workflows. + +It currently supports vendor tools such as ``Diamond``, ``Ise``, ``Quartus``, ``Libero``, and ``Vivado``, as well as ``Openflow``, a solution based on *Free/Libre and Open Source Software* (**FLOSS**). + +.. ATTENTION:: + + PyFPGA assumes that the backend tool is ready to run. + This implies, depending on the operating system, the following: + + * The tool is installed. + * A valid license, if needed, is configured. + * The tool is available in the system PATH. + * On GNU/Linux: required packages are installed, environment variables are set, and permissions are granted for devices (to transfer the bitstream). From 908533866584ce4c62fd81afda5b017a11b2df5a Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sun, 11 Aug 2024 17:21:09 -0300 Subject: [PATCH 189/254] Add Vivado hooks examples --- examples/hooks/vivado.py | 41 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 examples/hooks/vivado.py diff --git a/examples/hooks/vivado.py b/examples/hooks/vivado.py new file mode 100644 index 00000000..8621ae11 --- /dev/null +++ b/examples/hooks/vivado.py @@ -0,0 +1,41 @@ +"""Vivado hooks examples.""" + +from pathlib import Path +from pyfpga.vivado import Vivado + + +prj = Vivado() + +prj.set_part('xc7z010-1-clg400') + +prj.add_param('FREQ', '125000000') +prj.add_param('SECS', '1') +prj.add_define('DEFINE1', '1') +prj.add_define('DEFINE2', '1') + +prj.add_include('../sources/slog/include1') +prj.add_include('../sources/slog/include2') +prj.add_slog('../sources/slog/*.sv') + +prj.add_cons('../sources/cons/ZYBO/timing.xdc') +prj.add_cons('../sources/cons/ZYBO/clk.xdc') +prj.add_cons('../sources/cons/ZYBO/led.xdc') + +prj.set_top('Top') + +prj.add_hook('precfg', ''' +set obj [get_runs synth_1] +set_property strategy "Flow_AreaOptimized_high" $obj +set_property "steps.synth_design.args.directive" "AreaOptimized_high" $obj +set_property "steps.synth_design.args.control_set_opt_threshold" "1" $obj +set obj [get_runs impl_1] +set_property strategy "Area_Explore" $obj +set_property "steps.opt_design.args.directive" "ExploreArea" $obj +''') + +prj.add_hook('postcfg', f''' +set_property USED_IN_SYNTHESIS FALSE [get_files {Path('../sources/cons/ZYBO/clk.xdc').resolve()}] +set_property USED_IN_SYNTHESIS FALSE [get_files {Path('../sources/cons/ZYBO/led.xdc').resolve()}] +''') + +prj.make() From e1c6ebf875c74282b95a76c9ed6bd2f96dd4589c Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sun, 11 Aug 2024 17:41:52 -0300 Subject: [PATCH 190/254] Remove the 'when' parameter from 'add_cons' (Vivado-specific) --- docs/internals.rst | 12 ++++++------ examples/hooks/vivado.py | 5 +++-- examples/projects/ise.py | 12 ++++++------ examples/projects/libero.py | 6 +++--- examples/projects/openflow.py | 16 ++++++++-------- examples/projects/quartus.py | 6 +++--- examples/projects/vivado.py | 12 ++++++------ pyfpga/project.py | 9 +++------ pyfpga/templates/vivado.jinja | 5 ----- tests/test_data.py | 10 +++++----- tests/test_tools.py | 4 ++-- 11 files changed, 45 insertions(+), 52 deletions(-) diff --git a/docs/internals.rst b/docs/internals.rst index fd388ae4..4da687d2 100644 --- a/docs/internals.rst +++ b/docs/internals.rst @@ -39,15 +39,15 @@ Internal data structure 'part': 'PARTNAME', 'includes': ['DIR1', 'DIR2', 'DIR3'], 'files': { - 'FILE1': {'hdl': 'vhdl', 'lib': 'LIB1'} - 'FILE2': {'hdl': 'vlog'}, - 'FILE3': {'hdl': 'slog'} + 'FILE1': {'hdl': 'vhdl', 'lib': 'LIB1', 'opt': 'OPTS'}, + 'FILE2': {'hdl': 'vlog', 'opt': 'OPTS'}, + 'FILE3': {'hdl': 'slog', 'opt': 'OPTS'} }, 'top': 'TOPNAME', 'constraints': { - 'FILE1': 'all', - 'FILE2': 'syn', - 'FILE3': 'par' + 'FILE1': {'opt': 'OPTS'}, + 'FILE2': {'opt': 'OPTS'}, + 'FILE3': {'opt': 'OPTS'} }, 'params': { 'PAR1': 'VAL1', diff --git a/examples/hooks/vivado.py b/examples/hooks/vivado.py index 8621ae11..9d239247 100644 --- a/examples/hooks/vivado.py +++ b/examples/hooks/vivado.py @@ -33,9 +33,10 @@ set_property "steps.opt_design.args.directive" "ExploreArea" $obj ''') +place = ['../sources/cons/ZYBO/clk.xdc', '../sources/cons/ZYBO/led.xdc'] prj.add_hook('postcfg', f''' -set_property USED_IN_SYNTHESIS FALSE [get_files {Path('../sources/cons/ZYBO/clk.xdc').resolve()}] -set_property USED_IN_SYNTHESIS FALSE [get_files {Path('../sources/cons/ZYBO/led.xdc').resolve()}] +set_property USED_IN_SYNTHESIS FALSE [get_files {Path(place[0]).resolve()}] +set_property USED_IN_SYNTHESIS FALSE [get_files {Path(place[1]).resolve()}] ''') prj.make() diff --git a/examples/projects/ise.py b/examples/projects/ise.py index a1b9dba0..a81082a2 100644 --- a/examples/projects/ise.py +++ b/examples/projects/ise.py @@ -22,15 +22,15 @@ if args.board == 's6micro': prj.set_part('xc6slx9-2-csg324') prj.add_param('FREQ', '125000000') - prj.add_cons('../sources/cons/s6micro/clk.xcf', 'syn') - prj.add_cons('../sources/cons/s6micro/clk.ucf', 'par') - prj.add_cons('../sources/cons/s6micro/led.ucf', 'par') + prj.add_cons('../sources/cons/s6micro/clk.xcf') + prj.add_cons('../sources/cons/s6micro/clk.ucf') + prj.add_cons('../sources/cons/s6micro/led.ucf') if args.board == 'nexys3': prj.set_part('xc6slx16-3-csg32') prj.add_param('FREQ', '100000000') - prj.add_cons('../sources/cons/nexys3/clk.xcf', 'syn') - prj.add_cons('../sources/cons/nexys3/clk.ucf', 'par') - prj.add_cons('../sources/cons/nexys3/led.ucf', 'par') + prj.add_cons('../sources/cons/nexys3/clk.xcf') + prj.add_cons('../sources/cons/nexys3/clk.ucf') + prj.add_cons('../sources/cons/nexys3/led.ucf') prj.add_param('SECS', '1') if args.source == 'vhdl': diff --git a/examples/projects/libero.py b/examples/projects/libero.py index 4f4360d7..e9e2697c 100644 --- a/examples/projects/libero.py +++ b/examples/projects/libero.py @@ -21,9 +21,9 @@ if args.board == 'maker': prj.set_part('m2s010-1-tq144') prj.add_param('FREQ', '125000000') - prj.add_cons('../sources/cons/maker/clk.sdc', 'syn') - prj.add_cons('../sources/cons/maker/clk.pdc', 'par') - prj.add_cons('../sources/cons/maker/led.pdc', 'par') + prj.add_cons('../sources/cons/maker/clk.sdc') + prj.add_cons('../sources/cons/maker/clk.pdc') + prj.add_cons('../sources/cons/maker/led.pdc') prj.add_param('SECS', '1') if args.source == 'vhdl': diff --git a/examples/projects/openflow.py b/examples/projects/openflow.py index b40f6c96..0b1242b3 100644 --- a/examples/projects/openflow.py +++ b/examples/projects/openflow.py @@ -23,23 +23,23 @@ if args.board == 'icestick': prj.set_part('hx1k-tq144') prj.add_param('FREQ', '100000000') - prj.add_cons('../sources/cons/icestick/clk.pcf', 'par') - prj.add_cons('../sources/cons/icestick/led.pcf', 'par') + prj.add_cons('../sources/cons/icestick/clk.pcf') + prj.add_cons('../sources/cons/icestick/led.pcf') if args.board == 'edu-ciaa': prj.set_part('hx1k-tq144') prj.add_param('FREQ', '100000000') - prj.add_cons('../sources/cons/edu-ciaa/clk.pcf', 'par') - prj.add_cons('../sources/cons/edu-ciaa/led.pcf', 'par') + prj.add_cons('../sources/cons/edu-ciaa/clk.pcf') + prj.add_cons('../sources/cons/edu-ciaa/led.pcf') if args.board == 'orangecrab': prj.set_part('25k-CSFBGA285') prj.add_param('FREQ', '100000000') - prj.add_cons('../sources/cons/orangecrab/clk.lpf', 'par') - prj.add_cons('../sources/cons/orangecrab/led.lpf', 'par') + prj.add_cons('../sources/cons/orangecrab/clk.lpf') + prj.add_cons('../sources/cons/orangecrab/led.lpf') if args.board == 'ecp5evn': prj.set_part('um5g-85k-CABGA381') prj.add_param('FREQ', '100000000') - prj.add_cons('../sources/cons/ecp5evn/clk.lpf', 'par') - prj.add_cons('../sources/cons/ecp5evn/led.lpf', 'par') + prj.add_cons('../sources/cons/ecp5evn/clk.lpf') + prj.add_cons('../sources/cons/ecp5evn/led.lpf') prj.add_param('SECS', '1') if args.source == 'vhdl': diff --git a/examples/projects/quartus.py b/examples/projects/quartus.py index 07cfedc4..cccbb372 100644 --- a/examples/projects/quartus.py +++ b/examples/projects/quartus.py @@ -22,9 +22,9 @@ if args.board == 'de10nano': prj.set_part('5CSEBA6U23I7') prj.add_param('FREQ', '125000000') - prj.add_cons('../sources/cons/de10nano/clk.sdc', 'syn') - prj.add_cons('../sources/cons/de10nano/clk.tcl', 'par') - prj.add_cons('../sources/cons/de10nano/led.tcl', 'par') + prj.add_cons('../sources/cons/de10nano/clk.sdc') + prj.add_cons('../sources/cons/de10nano/clk.tcl') + prj.add_cons('../sources/cons/de10nano/led.tcl') prj.add_param('SECS', '1') if args.source == 'vhdl': diff --git a/examples/projects/vivado.py b/examples/projects/vivado.py index 367f2861..35ccecc7 100644 --- a/examples/projects/vivado.py +++ b/examples/projects/vivado.py @@ -22,15 +22,15 @@ if args.board == 'zybo': prj.set_part('xc7z010-1-clg400') prj.add_param('FREQ', '125000000') - prj.add_cons('../sources/cons/ZYBO/timing.xdc', 'syn') - prj.add_cons('../sources/cons/ZYBO/clk.xdc', 'par') - prj.add_cons('../sources/cons/ZYBO/led.xdc', 'par') + prj.add_cons('../sources/cons/ZYBO/timing.xdc') + prj.add_cons('../sources/cons/ZYBO/clk.xdc') + prj.add_cons('../sources/cons/ZYBO/led.xdc') if args.board == 'arty': prj.set_part('xc7a35ticsg324-1L') prj.add_param('FREQ', '100000000') - prj.add_cons('../sources/cons/arty_a7_35t/timing.xdc', 'syn') - prj.add_cons('../sources/cons/arty_a7_35t/clk.xdc', 'par') - prj.add_cons('../sources/cons/arty_a7_35t/led.xdc', 'par') + prj.add_cons('../sources/cons/arty_a7_35t/timing.xdc') + prj.add_cons('../sources/cons/arty_a7_35t/clk.xdc') + prj.add_cons('../sources/cons/arty_a7_35t/led.xdc') prj.add_param('SECS', '1') if args.source == 'vhdl': diff --git a/pyfpga/project.py b/pyfpga/project.py index 72e7edc0..e6ba24fe 100644 --- a/pyfpga/project.py +++ b/pyfpga/project.py @@ -123,22 +123,19 @@ def add_vlog(self, pathname): self.logger.debug('Executing add_vlog') self._add_file(pathname, 'vlog') - def add_cons(self, path, when='all'): + def add_cons(self, path): """Add a constraint file. :param pathname: path of a file :type pathname: str - :param when: always ('all'), synthesis ('syn') or P&R ('par') - :type only: str, optional :raises FileNotFoundError: if path is not found """ self.logger.debug('Executing add_cons') path = Path(path).resolve() if not path.is_file(): raise FileNotFoundError(path) - if when not in ['all', 'syn', 'par']: - raise ValueError('Invalid only.') - self.data.setdefault('constraints', {})[path.as_posix()] = when + attr = {} + self.data.setdefault('constraints', {})[path.as_posix()] = attr def add_param(self, name, value): """Add a Parameter/Generic Value. diff --git a/pyfpga/templates/vivado.jinja b/pyfpga/templates/vivado.jinja index babbf3b6..e214cfb4 100644 --- a/pyfpga/templates/vivado.jinja +++ b/pyfpga/templates/vivado.jinja @@ -25,11 +25,6 @@ add_file {{ name }} {% if constraints %}# Constraints inclusion {% for name, attr in constraints.items() %} add_file -fileset constrs_1 {{ name }} -{% if attr == "syn" %} -set_property USED_IN_IMPLEMENTATION FALSE [get_files {{ name }}] -{% elif attr == "par" %} -set_property USED_IN_SYNTHESIS FALSE [get_files {{ name }}] -{% endif %} {% if loop.first %}set_property TARGET_CONSTRS_FILE {{ name }} [current_fileset -constrset]{% endif %} {% endfor %} {% endif %} diff --git a/tests/test_data.py b/tests/test_data.py index a8e2af19..49b0645b 100644 --- a/tests/test_data.py +++ b/tests/test_data.py @@ -52,9 +52,9 @@ }, 'top': 'TOPNAME', 'constraints': { - Path(tdir / 'fakedata/cons/all.xdc').resolve().as_posix(): 'all', - Path(tdir / 'fakedata/cons/syn.xdc').resolve().as_posix(): 'syn', - Path(tdir / 'fakedata/cons/par.xdc').resolve().as_posix(): 'par' + Path(tdir / 'fakedata/cons/all.xdc').resolve().as_posix(): {}, + Path(tdir / 'fakedata/cons/syn.xdc').resolve().as_posix(): {}, + Path(tdir / 'fakedata/cons/par.xdc').resolve().as_posix(): {} }, 'params': { 'PAR1': 'VAL1', @@ -90,8 +90,8 @@ def test_data(): prj.add_vhdl(str(tdir / 'fakedata/**/*.vhdl'), 'LIB') prj.add_vlog(str(tdir / 'fakedata/**/*.v')) prj.add_cons(str(tdir / 'fakedata/cons/all.xdc')) - prj.add_cons(str(tdir / 'fakedata/cons/syn.xdc'), 'syn') - prj.add_cons(str(tdir / 'fakedata/cons/par.xdc'), 'par') + prj.add_cons(str(tdir / 'fakedata/cons/syn.xdc')) + prj.add_cons(str(tdir / 'fakedata/cons/par.xdc')) prj.add_param('PAR1', 'VAL1') prj.add_param('PAR2', 'VAL2') prj.add_param('PAR3', 'VAL3') diff --git a/tests/test_tools.py b/tests/test_tools.py index 45f972ed..a2950019 100644 --- a/tests/test_tools.py +++ b/tests/test_tools.py @@ -54,8 +54,8 @@ def generate(tool, part): prj.add_vhdl(str(tdir / 'fakedata/**/*.vhdl'), 'LIB') prj.add_vlog(str(tdir / 'fakedata/**/*.v')) prj.add_cons(str(tdir / 'fakedata/cons/all.xdc')) - prj.add_cons(str(tdir / 'fakedata/cons/syn.xdc'), 'syn') - prj.add_cons(str(tdir / 'fakedata/cons/par.xdc'), 'par') + prj.add_cons(str(tdir / 'fakedata/cons/syn.xdc')) + prj.add_cons(str(tdir / 'fakedata/cons/par.xdc')) prj.add_param('PAR1', 'VAL1') prj.add_param('PAR2', 'VAL2') prj.add_define('DEF1', 'VAL1') From e0943bdb50d426a4e2db1b4ded2ebcb93c8836e3 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sun, 11 Aug 2024 18:37:19 -0300 Subject: [PATCH 191/254] Add a Vivado Elaboration example (using hooks) Closes #39 --- examples/hooks/vivado-elab.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 examples/hooks/vivado-elab.py diff --git a/examples/hooks/vivado-elab.py b/examples/hooks/vivado-elab.py new file mode 100644 index 00000000..f21c550f --- /dev/null +++ b/examples/hooks/vivado-elab.py @@ -0,0 +1,23 @@ +"""Vivado elaboration example.""" + +from pyfpga.vivado import Vivado + + +prj = Vivado() + +prj.set_part('xc7z010-1-clg400') + +prj.add_param('FREQ', '125000000') +prj.add_param('SECS', '1') +prj.add_define('DEFINE1', '1') +prj.add_define('DEFINE2', '1') + +prj.add_include('../sources/slog/include1') +prj.add_include('../sources/slog/include2') +prj.add_slog('../sources/slog/*.sv') + +prj.set_top('Top') + +prj.add_hook('presyn', 'synth_design -rtl -rtl_skip_mlo; exit 0') + +prj.make() From 83d1e59e413600cbd13f37e74e5c18eeec49d811 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sun, 11 Aug 2024 22:09:21 -0300 Subject: [PATCH 192/254] Adds set_debug() --- pyfpga/project.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pyfpga/project.py b/pyfpga/project.py index e6ba24fe..86d9f267 100644 --- a/pyfpga/project.py +++ b/pyfpga/project.py @@ -200,6 +200,10 @@ def add_hook(self, stage, hook): raise ValueError('Invalid stage.') self.data.setdefault('hooks', {}).setdefault(stage, []).append(hook) + def set_debug(self): + """Enables debug messages.""" + self.logger.setLevel(logging.DEBUG) + def make(self, first='cfg', last='bit'): """Run the underlying tool. From 0103506a38d2d50ff4b95c89d3c96aec597670a5 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sun, 11 Aug 2024 22:10:01 -0300 Subject: [PATCH 193/254] docs: rewritten basic usage --- docs/basic.rst | 163 ++++++++++++++++++++++--------------------------- docs/index.rst | 5 -- 2 files changed, 74 insertions(+), 94 deletions(-) diff --git a/docs/basic.rst b/docs/basic.rst index b178c0fc..5efcfccd 100644 --- a/docs/basic.rst +++ b/docs/basic.rst @@ -1,148 +1,133 @@ Basic usage =========== -.. ATTENTION:: +Project Configuration +--------------------- - (2024-08-08) To be updated. +The first steps involve importing the necessary module to support the desired tool and instantiating the corresponding *class*: -Project Creation ----------------- +.. code-block:: python + + from pyfpga.vivado import Vivado + + prj = Vivado('PRJNAME', odir='OUTDIR') -The first steps are import the module and instantiate the ``Project`` *class*, -specifying the *TOOL* to use and, optionally, a *PROJECT NAME* (the *tool* -name is used when *no name* is provided). +In the example, we are using Vivado, specifying the optional parameter *project name* (*tool name* if omitted) and *output directory* (*results* by default). + +Next step is to specify the target FPGA device: .. code-block:: python - from fpga.project import Project + prj.set_part('xc7k160t-3-fbg484') + +.. note:: - prj = Project('vivado', 'projectName') + Default parts are provided for each supported tool. -By default, the directory where the project is generated is called ``build`` -and is located in the same place that the script, but another name and location -can be specified. +HDL source files are added using one of the following methods: .. code-block:: python - prj.set_outdir('../temp') + prj.add_vhdl('PATH_TO_FILES_GLOB_COMPATIBLE', 'OPTIONAL_LIBNAME') + prj.add_vlog('PATH_TO_FILES_GLOB_COMPATIBLE') + prj.add_slog('PATH_TO_FILES_GLOB_COMPATIBLE') -Next, the FPGA part would be specified: +In these methods, you provide a path to the files. The path can include wildcards (like `*.vhdl`), allowing you to match multiple files at once. -.. code-block:: python +For `add_vhdl`, you can also optionally specify a library name where the files will be included. - prj.set_part('xc7k160t-3-fbg484') +.. note:: + + Internally, the methods that specify files use `glob`_ to support wildcards and `Path`_ to obtain absolute paths. -.. NOTE:: + .. _glob: https://docs.python.org/3/library/glob.html + .. _Path: https://docs.python.org/3/library/pathlib.html - You can use the default FPGA part for a quick test or make a lazy comparison - between tools, but generally, you will want to specify a particular one. - Examples about how to specify a part according the tool, are (default values - when ``set_part`` is not employed): +Generics/parameters can be specified with: + +.. code-block:: python - * **Ise:** ``xc7k160t-3-fbg484`` (*device-speed-package*) - * **Libero:** ``mpf100t-1-fcg484`` (*device-speed-package*) - * **Openflow:** ``hx8k-ct256`` (*device-package*) - * **Quartus:** ``10cl120zf780i8g`` (*part*) - * **Vivado:** ``xc7k160t-3-fbg484`` (*part*) + prj.add_param('PARAMNAME', 'PARAMVALUE') -The files addition method allows specifying one or more HDL or constraint files -(also block designs in case of Vivado). -It uses ``glob`` internally, which makes available the use of wildcards. -The path to their location must be relative to the Python script, and there -are optional parameters to indicate the file type (``vhdl``, ``verilog``, -``constraint`` or ``design``), which is automatically detected based on the -file extension, and if it is a member of a VHDL package. +For Verilog and SystemVerilog, the following methods are also available: .. code-block:: python - prj.add_files('hdl/blinking.vhdl', library='examples') - prj.add_files('hdl/examples_pkg.vhdl', library='examples') - prj.add_files('hdl/top.vhdl') + prj.add_include('PATH_TO_A_DIRECTORY') + prj.add_define('DEFNAME', 'DEFVALUE') -.. NOTE:: +Constraint source files are included using the following: - * In some cases, the files order could be a problem, so take into account to - change the order if needed. - * If a file seems unsupported, you can always use the ``prefile`` or - ``project`` :ref:`hooks`. - * In case of Verilog, ``add_vlog_include`` can be used to specify where to - search for included files. +.. code-block:: python + + prj.add_cons('PATH_TO_FILES_GLOB_COMPATIBLE') -Finally, the top-level must be specified: +Finally, the top-level can be specified as follows: .. code-block:: python prj.set_top('Top') -.. NOTE:: +.. note:: - A relative path to a valid VHDL/Verilog file is also accepted by ``set_top``, - to automatically extract the top-level name. + The order of the methods described in this section is not significant. + They will be arranged in the required order by the underlying template. -Project generation ------------------- +Bitstream generation +-------------------- -Next step if to generate the project. In the most basic form, you can run the -following to get a bitstream: +After configuring the project, you can run the following to generate a bitstream: .. code-block:: python - prj.generate() + prj.make() -Additionally, you can specify which task to perform: +By default, this method performs *project creation*, *synthesis*, *place and route*, and *bitstream generation*. +However, you can optionally specify both the initial and final stages, as follows: .. code-block:: python - prj.generate('syn') - -.. NOTE:: - - The valid values are: + prj.make(first='syn', last='par') - * ``prj``: to generate only a project file (only supported for privative tools) - * ``syn``: to performs synthesis. - * ``imp``: to performs synthesis and implementation (place and route, - optimizations and static timming analysis when available). - * ``bit``: (default) to perform synthesis, implementation and bitstream generation. +.. note:: -Bitstream transfer ------------------- + Valid values are: -This method is in charge of run the needed tool to transfer a bitstream to a -device (commonly an FPGA, but memories are also supported in some cases). -It has up to four main optional parameters: + * ``cfg``: generates the project file + * ``syn``: performs synthesis + * ``par``: performs place and route + * ``bit``: performs bitstream generation -.. code-block:: python +.. note:: - prj.transfer(devtype, position, part, width) + After executing this method, you will find the file `.tcl` (or `sh` in some cases) in the output directory. + For debugging purposes, if things do not work as expected, you can review this file. -Where *devtype* is ``fpga`` by default but can also be ``spi``, ``bpi``, etc, if -supported. An integer number can be used to specify the *position* (1) in the -Jtag chain. When a memory is used as *devtype*, the *part* name and the -*width* in bits must be also specified. +Bitstream programming +--------------------- -.. NOTE:: +The final step is programming the FPGA: - * In Xilinx, `spi` and `bpi` memories are out of the Jtag chain and are - programmed through the FPGA. You must specify the FPGA *position*. - * In a Linux systems, you need to have permission over the device - (udev rule, be a part of a group, etc). +.. code-block:: python -Logging capabilities --------------------- + prj.prog('BITSTREAM', 'POSITION') -PyFPGA uses the `logging `_ -module, with a *NULL* handler and the *INFO* level by default. -Messages can be enabled with: +Both `BITSTREAM` and `POSITION` are optional. +If `BITSTREAM` is not specified, PyFPGA will attempt to discover it based on project information. +The `POSITION` parameter is not always required (depends on the tool being used). -.. code-block:: python +.. note:: - import logging + After executing this method, you will find the file `prog.tcl` (or `sh` in some cases) in the output directory. + For debugging purposes, if things do not work as expected, you can review this file. - logging.basicConfig() +Debugging +--------- -You can enable *DEBUG* messages adding: +Under the hood, `logging`_ is employed. To enable debug messages, you can use: .. code-block:: python - logging.getLogger('fpga.project').level = logging.DEBUG + prj.set_debug() + +.. _logging: https://docs.python.org/3/library/logging.html diff --git a/docs/index.rst b/docs/index.rst index e9241b3e..3e8dfcb0 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -6,11 +6,6 @@ PyFPGA's documentation :align: center :target: https://github.com/PyFPGA/pyfpga -.. ATTENTION:: - - (2024-05-31) PyFPGA is in the process of being strongly rewritten/simplified. - Most changes are internal, but the API will also change. - .. toctree:: intro From e552a69fa8aba9a736dc7ae2b7717cd9f1b8c772 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sun, 11 Aug 2024 23:29:57 -0300 Subject: [PATCH 194/254] docs: rewritten (WIP) advanced usage --- docs/advanced.rst | 184 +++++++++++----------------------------------- 1 file changed, 42 insertions(+), 142 deletions(-) diff --git a/docs/advanced.rst b/docs/advanced.rst index 245f4c08..44762476 100644 --- a/docs/advanced.rst +++ b/docs/advanced.rst @@ -1,161 +1,61 @@ Advanced usage ============== -.. ATTENTION:: - - (2024-05-31) To be updated. - -Multi project managment ------------------------ - -.. code-block:: python +PyFPGA offers advanced features for more customized and flexible control over FPGA project management. +This section covers two key advanced features: - PROJECTS = { - '': Project( - '', - '', - { - 'outdir': '', - 'part': '' - 'paths': [ - '', - ... - '' - ], - 'vhdl': [ - ['', ''], - '', - ... - '' - ], - 'verilog': [ - '', - ... - '' - ], - 'constraint': [ - '', - ... - '' - ], - 'params': { - '': '', - ... - '': '' - }, - 'top': '' - } - ) - '': Project( - ... - ) - } +1. **Hooks**: These are points in the code where you can insert custom code to extend or modify the behavior of the tool. +Hooks provide a way to integrate additional functionality or perform specific actions at predefined stages of the project lifecycle. -.. _hooks: +2. **Options**: This feature allows you to specify additional options to fine-tune the tool's behavior. +Options provide greater control over the tool's operation and enable you to customize the processing according to your specific requirements. Hooks ----- -The following table depicts the parts of the *Project Creation* and the -*Design Flow* internally performed by PyFPGA. - -+--------------------------+----------------------+ -| Project Creation | Design Flow | -+==========================+======================+ -| Part specification | **preflow** hook | -+--------------------------+----------------------+ -| **prefile** hook | Synthesis | -+--------------------------+----------------------+ -| Files addition | **postsyn** hook | -+--------------------------+----------------------+ -| Top specification | Place and Route | -+--------------------------+----------------------+ -| Parameters specification | **postpar** hook | -+--------------------------+----------------------+ -| **project** hook | Bitstream generation | -+--------------------------+----------------------+ -| | **postbit** hook | -+--------------------------+----------------------+ - -If the provided API if not enough or suitable for your project, you can -specify additional *hooks* in different parts of the flow, using: - -.. code-block:: python - - prj.add_hook(hook, phase) - -.. NOTE:: - - * Valid vaues for *phase* are ``prefile``, ``project`` (default), ``preflow``, - ``postsyn``, ``postpar`` and ``postbit``. - * The *hook* string must be a valid command (supported by the used tool). - * If more than one *hook* is needed in the same *phase*, you can call this - method several times (the commands will be executed in order). - -Parameters ----------- - -The generics/parameters of the project can be optionally changed with: - -.. code-block:: python - - prj.add_param('param1', value1) - ... - prj.add_param('paramN', valueN) - -Generate options ----------------- - -The method ``generate`` (previously seen at the end of -[Basic usage](#basic-usage) section) has optional parameters: +Hooks allow you to insert custom code at specific stages of the project lifecycle. The available hooks are: + ++---------+---------------------------------------------------------------------------------------------+ +| Stage | Description | ++=========+=============================================================================================+ +| precfg | Code inserted after project creation and before files inclusion (e.g., specify HDL version) | ++---------+---------------------------------------------------------------------------------------------+ +| postcfg | Code inserted after files inclusion (e.g., additional project configurations) | ++---------+---------------------------------------------------------------------------------------------+ +| presyn | Code inserted before synthesis (e.g., synthesis-specific options) | ++---------+---------------------------------------------------------------------------------------------+ +| postsyn | Code inserted after synthesis (e.g., report generation) | ++---------+---------------------------------------------------------------------------------------------+ +| prepar | Code inserted before place and route (e.g., place-and-route-specific options) | ++---------+---------------------------------------------------------------------------------------------+ +| postpar | Code inserted after place and route (e.g., report generation) | ++---------+---------------------------------------------------------------------------------------------+ +| prebit | Code inserted before bitstream generation (e.g., bitstream-specific options) | ++---------+---------------------------------------------------------------------------------------------+ +| postbit | Code inserted after bitstream generation (e.g., report generation) | ++---------+---------------------------------------------------------------------------------------------+ + +You can specify hooks for a specific stage either line-by-line: .. code-block:: python - prj.generate(to_task, from_task, capture) + prj.add_hook('presyn', 'COMMAND1') + prj.add_hook('presyn', 'COMMAND2') + prj.add_hook('presyn', 'COMMAND3') -With *to_task* and *from_taks* (with default values ``bit`` and ``prj``), -you are selecting the first and last task to execute when `generate` is -invoqued. The order and available tasks are ``prj``, ``syn``, ``par`` and ``bit``. -It can be useful in at least two cases: - -* Maybe you created a file project with the GUI of the Tool and only want to - run the Design Flow, so you can use: ``generate(to_task='bit', from_task='syn')`` - -* Despite that a method to insert particular commands is provided, you would - want to perform some processing from Python between tasks, using something - like: +Or in a multi-line format: .. code-block:: python - prj.generate(to_task='syn', from_task='prj') - #Some other Python commands here - prj.generate(to_task='bit', from_task='syn') + prj.add_hook('presyn', """ + COMMAND1 + COMMAND2 + COMMAND3 + """) -In case of *capture*, it is useful to catch execution messages to be -post-processed or saved to a file: - -.. code-block:: python +Options +------- - result = prj.generate(capture=True) - print(result) - -In case of *capture*, it is useful to catch execution messages to be -post-processed or saved to a file. - -Exceptions ----------- - -Finally, you must run the bitstream generation or its transfer. Both of them -are time-consuming tasks, performed by a backend tool, which could fail. -Exceptions are raised in such cases, that should be ideally caught to avoid -abnormal program termination. - -.. code-block:: python - - try: - prj.generate() - prj.transfer() - except Exception as e: - print('{} ({})'.format(type(e).__name__, e)) +.. ATTENTION:: -And wait for the backend Tool to accomplish its task. + WIP feature. From fe6937afec60b92dc27c224ac0f8f1450b43831f Mon Sep 17 00:00:00 2001 From: Markus Koch Date: Thu, 1 Aug 2024 07:53:32 +0200 Subject: [PATCH 195/254] 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') From 45995b8a93ed384b2173570a343d0e22a5049981 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Mon, 12 Aug 2024 19:50:32 -0300 Subject: [PATCH 196/254] Fix a linter issue --- examples/hooks/diamond.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/hooks/diamond.py b/examples/hooks/diamond.py index eac463fa..0e0b5d55 100644 --- a/examples/hooks/diamond.py +++ b/examples/hooks/diamond.py @@ -47,6 +47,6 @@ prj.set_top('Top') for hook_name, hook in hooks.items(): - prj.add_hook('postpar', hook) + prj.add_hook('postpar', hook) prj.make() From 95afdae1f2db803c09c872fb991d6b45d3d06e7f Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Mon, 12 Aug 2024 20:04:04 -0300 Subject: [PATCH 197/254] docs: add content to 'Extending' --- docs/extending.rst | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/docs/extending.rst b/docs/extending.rst index 20d48463..03c995cf 100644 --- a/docs/extending.rst +++ b/docs/extending.rst @@ -1,2 +1,40 @@ Extending ========= + +1. Add support for the new tool: + +.. code-block:: python + + pyfpga/templates/.jinja + pyfpga/templates/-prog.jinja + pyfpga/.py + +2. Include the new tool on Factory: + +.. code-block:: python + + pyfpga/factory.py + +3. Add tests and a tool mock-up: + +.. code-block:: python + + tests/test_tools.py + tests/mocks/ + +4. Updated the project's documentation: + +.. code-block:: python + + README.md + docs + +5. [OPTIONAL] Add examples: + +.. code-block:: python + + examples/sources/cons//timing. + examples/sources/cons//clk. + examples/sources/cons//led. + examples/projects/.py + examples/hooks/.py From dd8e4de3b8ea10f9d3e3ac3aa4cd112be81ba105 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Mon, 12 Aug 2024 20:21:03 -0300 Subject: [PATCH 198/254] A few constraint files were renamed to maintain names coherency --- examples/helpers/ise.sh | 4 ++-- examples/helpers/libero.sh | 4 ++-- examples/helpers/quartus.sh | 4 ++-- examples/hooks/diamond.py | 5 ++--- examples/projects/diamond.py | 5 ++--- examples/projects/ise.py | 4 ++-- examples/projects/libero.py | 2 +- examples/projects/quartus.py | 2 +- examples/sources/cons/brevia2/{io.lpf => led.lpf} | 0 examples/sources/cons/de10nano/{clk.sdc => timing.sdc} | 0 examples/sources/cons/maker/{clk.sdc => timing.sdc} | 0 examples/sources/cons/nexys3/{clk.xcf => timing.xcf} | 0 examples/sources/cons/s6micro/{clk.xcf => timing.xcf} | 0 13 files changed, 14 insertions(+), 16 deletions(-) rename examples/sources/cons/brevia2/{io.lpf => led.lpf} (100%) rename examples/sources/cons/de10nano/{clk.sdc => timing.sdc} (100%) rename examples/sources/cons/maker/{clk.sdc => timing.sdc} (100%) rename examples/sources/cons/nexys3/{clk.xcf => timing.xcf} (100%) rename examples/sources/cons/s6micro/{clk.xcf => timing.xcf} (100%) diff --git a/examples/helpers/ise.sh b/examples/helpers/ise.sh index 11e73ba8..a366c6e9 100644 --- a/examples/helpers/ise.sh +++ b/examples/helpers/ise.sh @@ -7,12 +7,12 @@ HDIR=../../pyfpga/helpers python3 $HDIR/hdl2bit.py -t ise -o results/ise-vlog -p xc6slx16-3-csg32 \ -i ../sources/vlog/include1 -i ../sources/vlog/include2 \ -f ../sources/vlog/blink.v -f ../sources/vlog/top.v \ - -f ../sources/cons/nexys3/clk.xcf -f ../sources/cons/nexys3/clk.ucf -f ../sources/cons/nexys3/led.ucf \ + -f ../sources/cons/nexys3/timing.xcf -f ../sources/cons/nexys3/clk.ucf -f ../sources/cons/nexys3/led.ucf \ --define DEFINE1 1 --define DEFINE2 1 --param FREQ 125000000 --param SECS 1 Top python3 $HDIR/hdl2bit.py -t ise -o results/ise-vhdl -p xc6slx16-3-csg32 --project example \ -f ../sources/vhdl/blink.vhdl,blink_lib -f ../sources/vhdl/blink_pkg.vhdl,blink_lib -f ../sources/vhdl/top.vhdl \ - -f ../sources/cons/nexys3/clk.xcf -f ../sources/cons/nexys3/clk.ucf -f ../sources/cons/nexys3/led.ucf \ + -f ../sources/cons/nexys3/timing.xcf -f ../sources/cons/nexys3/clk.ucf -f ../sources/cons/nexys3/led.ucf \ --param FREQ 125000000 --param SECS 1 --last cfg Top python3 $HDIR/prj2bit.py results/ise-vhdl/example.xise diff --git a/examples/helpers/libero.sh b/examples/helpers/libero.sh index 1a56da93..8d270c48 100644 --- a/examples/helpers/libero.sh +++ b/examples/helpers/libero.sh @@ -7,12 +7,12 @@ HDIR=../../pyfpga/helpers python3 $HDIR/hdl2bit.py -t libero -o results/libero-vlog -p m2s010-1-tq144 \ -i ../sources/vlog/include1 -i ../sources/vlog/include2 \ -f ../sources/vlog/blink.v -f ../sources/vlog/top.v \ - -f ../sources/cons/maker/clk.sdc -f ../sources/cons/maker/clk.pdc -f ../sources/cons/maker/led.pdc \ + -f ../sources/cons/maker/timing.sdc -f ../sources/cons/maker/clk.pdc -f ../sources/cons/maker/led.pdc \ --define DEFINE1 1 --define DEFINE2 1 --param FREQ 125000000 --param SECS 1 Top python3 $HDIR/hdl2bit.py -t libero -o results/libero-vhdl -p m2s010-1-tq144 --project example \ -f ../sources/vhdl/blink.vhdl,blink_lib -f ../sources/vhdl/blink_pkg.vhdl,blink_lib -f ../sources/vhdl/top.vhdl \ - -f ../sources/cons/maker/clk.sdc -f ../sources/cons/maker/clk.pdc -f ../sources/cons/maker/led.pdc \ + -f ../sources/cons/maker/timing.sdc -f ../sources/cons/maker/clk.pdc -f ../sources/cons/maker/led.pdc \ --param FREQ 125000000 --param SECS 1 --last cfg Top python3 $HDIR/prj2bit.py results/libero-vhdl/libero/example.prjx diff --git a/examples/helpers/quartus.sh b/examples/helpers/quartus.sh index b2ae4437..b532ed83 100644 --- a/examples/helpers/quartus.sh +++ b/examples/helpers/quartus.sh @@ -7,12 +7,12 @@ HDIR=../../pyfpga/helpers python3 $HDIR/hdl2bit.py -t quartus -o results/quartus-vlog -p 5CSEBA6U23I7 \ -i ../sources/vlog/include1 -i ../sources/vlog/include2 \ -f ../sources/vlog/blink.v -f ../sources/vlog/top.v \ - -f ../sources/cons/de10nano/clk.sdc -f ../sources/cons/de10nano/clk.tcl -f ../sources/cons/de10nano/led.tcl \ + -f ../sources/cons/de10nano/timing.sdc -f ../sources/cons/de10nano/clk.tcl -f ../sources/cons/de10nano/led.tcl \ --define DEFINE1 1 --define DEFINE2 1 --param FREQ 125000000 --param SECS 1 Top python3 $HDIR/hdl2bit.py -t quartus -o results/quartus-vhdl -p 5CSEBA6U23I7 --project example \ -f ../sources/vhdl/blink.vhdl,blink_lib -f ../sources/vhdl/blink_pkg.vhdl,blink_lib -f ../sources/vhdl/top.vhdl \ - -f ../sources/cons/de10nano/clk.sdc -f ../sources/cons/de10nano/clk.tcl -f ../sources/cons/de10nano/led.tcl \ + -f ../sources/cons/de10nano/timing.sdc -f ../sources/cons/de10nano/clk.tcl -f ../sources/cons/de10nano/led.tcl \ --param FREQ 125000000 --param SECS 1 --last cfg Top python3 $HDIR/prj2bit.py results/quartus-vhdl/example.qpf diff --git a/examples/hooks/diamond.py b/examples/hooks/diamond.py index 0e0b5d55..389b9a60 100644 --- a/examples/hooks/diamond.py +++ b/examples/hooks/diamond.py @@ -33,9 +33,8 @@ 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_cons('../sources/cons/brevia2/clk.lpf') +prj.add_cons('../sources/cons/brevia2/led.lpf') prj.add_include('../sources/vlog/include1') prj.add_include('../sources/vlog/include2') diff --git a/examples/projects/diamond.py b/examples/projects/diamond.py index 9e602d5c..4f5a85de 100644 --- a/examples/projects/diamond.py +++ b/examples/projects/diamond.py @@ -22,9 +22,8 @@ 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_cons('../sources/cons/brevia2/clk.lpf') + prj.add_cons('../sources/cons/brevia2/led.lpf') prj.add_param('SECS', '1') if args.source == 'vhdl': diff --git a/examples/projects/ise.py b/examples/projects/ise.py index a81082a2..38d886c1 100644 --- a/examples/projects/ise.py +++ b/examples/projects/ise.py @@ -22,13 +22,13 @@ if args.board == 's6micro': prj.set_part('xc6slx9-2-csg324') prj.add_param('FREQ', '125000000') - prj.add_cons('../sources/cons/s6micro/clk.xcf') + prj.add_cons('../sources/cons/s6micro/timing.xcf') prj.add_cons('../sources/cons/s6micro/clk.ucf') prj.add_cons('../sources/cons/s6micro/led.ucf') if args.board == 'nexys3': prj.set_part('xc6slx16-3-csg32') prj.add_param('FREQ', '100000000') - prj.add_cons('../sources/cons/nexys3/clk.xcf') + prj.add_cons('../sources/cons/nexys3/timing.xcf') prj.add_cons('../sources/cons/nexys3/clk.ucf') prj.add_cons('../sources/cons/nexys3/led.ucf') prj.add_param('SECS', '1') diff --git a/examples/projects/libero.py b/examples/projects/libero.py index e9e2697c..4edd978e 100644 --- a/examples/projects/libero.py +++ b/examples/projects/libero.py @@ -21,7 +21,7 @@ if args.board == 'maker': prj.set_part('m2s010-1-tq144') prj.add_param('FREQ', '125000000') - prj.add_cons('../sources/cons/maker/clk.sdc') + prj.add_cons('../sources/cons/maker/timing.sdc') prj.add_cons('../sources/cons/maker/clk.pdc') prj.add_cons('../sources/cons/maker/led.pdc') prj.add_param('SECS', '1') diff --git a/examples/projects/quartus.py b/examples/projects/quartus.py index cccbb372..02ed76d3 100644 --- a/examples/projects/quartus.py +++ b/examples/projects/quartus.py @@ -22,7 +22,7 @@ if args.board == 'de10nano': prj.set_part('5CSEBA6U23I7') prj.add_param('FREQ', '125000000') - prj.add_cons('../sources/cons/de10nano/clk.sdc') + prj.add_cons('../sources/cons/de10nano/timing.sdc') prj.add_cons('../sources/cons/de10nano/clk.tcl') prj.add_cons('../sources/cons/de10nano/led.tcl') prj.add_param('SECS', '1') diff --git a/examples/sources/cons/brevia2/io.lpf b/examples/sources/cons/brevia2/led.lpf similarity index 100% rename from examples/sources/cons/brevia2/io.lpf rename to examples/sources/cons/brevia2/led.lpf diff --git a/examples/sources/cons/de10nano/clk.sdc b/examples/sources/cons/de10nano/timing.sdc similarity index 100% rename from examples/sources/cons/de10nano/clk.sdc rename to examples/sources/cons/de10nano/timing.sdc diff --git a/examples/sources/cons/maker/clk.sdc b/examples/sources/cons/maker/timing.sdc similarity index 100% rename from examples/sources/cons/maker/clk.sdc rename to examples/sources/cons/maker/timing.sdc diff --git a/examples/sources/cons/nexys3/clk.xcf b/examples/sources/cons/nexys3/timing.xcf similarity index 100% rename from examples/sources/cons/nexys3/clk.xcf rename to examples/sources/cons/nexys3/timing.xcf diff --git a/examples/sources/cons/s6micro/clk.xcf b/examples/sources/cons/s6micro/timing.xcf similarity index 100% rename from examples/sources/cons/s6micro/clk.xcf rename to examples/sources/cons/s6micro/timing.xcf From d169a7b20e44dc43da80e09141cf695bcb82a17d Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Mon, 12 Aug 2024 20:26:51 -0300 Subject: [PATCH 199/254] Update copyright --- pyfpga/factory.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyfpga/factory.py b/pyfpga/factory.py index 753ce84d..6da4029a 100644 --- a/pyfpga/factory.py +++ b/pyfpga/factory.py @@ -1,5 +1,5 @@ # -# Copyright (C) 2024 Rodrigo A. Melo +# Copyright (C) 2024 PyFPGA Project # # SPDX-License-Identifier: GPL-3.0-or-later # From 25613a6b710d529e48379899655cd84cb5ebc216 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Mon, 12 Aug 2024 22:15:29 -0300 Subject: [PATCH 200/254] docs: 'Tools' rewritten --- docs/tools.rst | 236 +++++++++++++++++++++++++++++-------------------- 1 file changed, 141 insertions(+), 95 deletions(-) diff --git a/docs/tools.rst b/docs/tools.rst index ffd3d97b..8839ac2d 100644 --- a/docs/tools.rst +++ b/docs/tools.rst @@ -1,98 +1,144 @@ Tools ===== -+---------------+-----------+---------+-----+-----------------------------------------------+ -| Tools | Vendor | Version | Tcl | Comment | -+===============+===========+=========+=====+===============================================+ -| ISE | Xilinx | 14.7 | 8.4 | Discontinued in 2013 | -+---------------+-----------+---------+-----+-----------------------------------------------+ -| Libero-SoC | Microsemi | 2024.1 | 8.5 | Important changes in version 12.0 (2019) | -+---------------+-----------+---------+-----+-----------------------------------------------+ -| Openflow | | | | | -+---------------+-----------+---------+-----+-----------------------------------------------+ -| Quartus Prime | Intel | 23.1 | 8.6 | Known as Quartus II until version 15.0 (2015) | -+---------------+-----------+---------+-----+-----------------------------------------------+ -| Vivado | Xilinx | 2022.1 | 8.5 | Introduced in 2012, it superseded ISE | -+---------------+-----------+---------+-----+-----------------------------------------------+ - -* ISE supports devices starting from Spartan 3/Virtex 4 until some first members of the 7 series. - Previous Spartan/Virtex devices were supported until version 10. Vivado supports devices starting - from the 7 series. - -* Libero-SoC had a fork for PolarFire devices which was merged in version 12.0 (2019). - Libero SoC v12.0 and later supports PolarFire, RTG4, SmartFusion2 and IGLOO2 FPGA families. - Libero SoC v11.9 and earlier are the alternative to work with SmartFusion, IGLOO, ProASIC3 and - Fusion families. - Libero IDE v9.2 (2016) was the last version of the previous tool to work with antifuse and older - flash devices. - -* Since the change from Quartus II to Prime, three editions are available: Pro (for Agilex, - Stratix 10, Arria 10 and Cyclone GX devices), Standard (for Cyclone 10 LP and earlier devices) - and Lite (a high-volume low-end subset of the Standard edition). - -Detailed support ----------------- - -+------------------------------+---------+----------+------------+-----------+----------+ -| | ISE | Libero | Openflow | Quartus | Vivado | -+==============================+=========+==========+============+===========+==========+ -|**add_files** | | | | | | -+------------------------------+---------+----------+------------+-----------+----------+ -|``vhdl`` | ``Yes`` | ``Yes`` | ``Yes`` | ``Yes`` | ``Yes`` | -+------------------------------+---------+----------+------------+-----------+----------+ -|``verilog`` | ``Yes`` | ``Yes`` | ``Yes`` | ``Yes`` | ``Yes`` | -+------------------------------+---------+----------+------------+-----------+----------+ -|``system_verilog`` | ``TBD`` | ``TBD`` | ``TBD`` | ``TBD`` | ``TBD`` | -+------------------------------+---------+----------+------------+-----------+----------+ -|``constraint`` | ``Yes`` | ``Yes`` | ``Yes`` | ``Yes`` | ``Yes`` | -+------------------------------+---------+----------+------------+-----------+----------+ -|``block_design`` | ``NY`` | ``NY`` | ``NY`` | ``NY`` | ``Yes`` | -+------------------------------+---------+----------+------------+-----------+----------+ -|**add_param** | | | | | | -+------------------------------+---------+----------+------------+-----------+----------+ -|``boolean`` (*VHDL/Verilog*) | ``TBD`` | ``TBD`` |``TBD`` | ``TBD`` | ``TBD`` | -+------------------------------+---------+----------+------------+-----------+----------+ -|``integer`` (*VHDL/Verilog*) | ``TBD`` | ``TBD`` |``TBD`` | ``TBD`` | ``TBD`` | -+------------------------------+---------+----------+------------+-----------+----------+ -|``string`` (*VHDL/Verilog*) | ``TBD`` | ``TBD`` |``TBD`` | ``TBD`` | ``TBD`` | -+------------------------------+---------+----------+------------+-----------+----------+ -|``real`` (*VHDL/Verilog*) | ``TBD`` | ``TBD`` |``TBD`` | ``TBD`` | ``TBD`` | -+------------------------------+---------+----------+------------+-----------+----------+ -|``std_logic`` (*VHDL*) | ``TBD`` | ``TBD`` |``TBD`` | ``TBD`` | ``TBD`` | -+------------------------------+---------+----------+------------+-----------+----------+ -|``std_logic_vector`` (*VHDL*) | ``TBD`` | ``TBD`` |``TBD`` | ``TBD`` | ``TBD`` | -+------------------------------+---------+----------+------------+-----------+----------+ -|**add_vlog_include** | ``Yes`` | ``Yes`` | ``Yes`` | ``Yes`` | ``Yes`` | -+------------------------------+---------+----------+------------+-----------+----------+ -|**add_vlog_define** | ``TBI`` | ``TBI`` | ``TBI`` | ``TBI`` | ``TBI`` | -+------------------------------+---------+----------+------------+-----------+----------+ -|**set_vhdl_arch** | ``TBI`` | ``TBI`` | ``TBI`` | ``TBI`` | ``TBI`` | -+------------------------------+---------+----------+------------+-----------+----------+ -|**generate** | | | | | | -+------------------------------+---------+----------+------------+-----------+----------+ -|``prj`` | ``Yes`` | ``Yes`` | ``No`` | ``Yes`` | ``Yes`` | -+------------------------------+---------+----------+------------+-----------+----------+ -|``syn`` | ``Yes`` | ``Yes`` | ``Yes`` | ``Yes`` | ``Yes`` | -+------------------------------+---------+----------+------------+-----------+----------+ -|``par`` | ``Yes`` | ``Yes`` | ``Yes`` | ``Yes`` | ``Yes`` | -+------------------------------+---------+----------+------------+-----------+----------+ -|``bit`` | ``Yes`` | ``Yes`` | ``Yes`` | ``Yes`` | ``Yes`` | -+------------------------------+---------+----------+------------+-----------+----------+ -|**transfer** | | | | | | -+------------------------------+---------+----------+------------+-----------+----------+ -|``fpga`` | ``Yes`` | ``NY`` | ``Yes`` | ``Yes`` | ``Yes`` | -+------------------------------+---------+----------+------------+-----------+----------+ -|``spi`` | ``Yes`` | ``NY`` | ``NY`` | ``NY`` | ``NY`` | -+------------------------------+---------+----------+------------+-----------+----------+ -|``bpi`` | ``Yes`` | ``NY`` | ``NY`` | ``NY`` | ``NY`` | -+------------------------------+---------+----------+------------+-----------+----------+ -|``detect`` | ``Yes`` | ``NY`` | ``NY`` | ``Yes`` | ``Yes`` | -+------------------------------+---------+----------+------------+-----------+----------+ -|``unlock`` | ``Yes`` | ``No`` | ``No`` | ``No`` | ``No`` | -+------------------------------+---------+----------+------------+-----------+----------+ - -* ``Yes``: already supported -* ``No``: no plans (or unneeded) -* ``NY``: Not yet, but maybe someday -* ``TBD``: To Be Defined -* ``TBI``: To Be Implemented +.. list-table:: Default PyFPGA's parts per tool + :header-rows: 1 + + * - Tool + - Vendor + - Default device + - Name format + * - Diamond + - Lattice + - LFXP2-5E-5TN144C + - device-speed-package + * - ISE + - Xilinx + - XC7K160T-3-FBG484 + - device-speed-package + * - Libero + - Microchip/Microsemi + - MPF100T-1-FCG484 + - device-speed-package + * - Openflow + - FLOSS + - HX8K-CT256 + - device-package + * - Quartus + - Intel/Altera + - 10M50SCE144I7G + - part + * - Vivado + - AMD/Xilinx + - XC7K160T-3-FBG484 + - device-speed-package + +Diamond +------- + +`Diamond downloads `_ + +Diamond is the previous generation EDA tool from Lattice. + +Example: + +.. code:: + + from pyfpga.diamond import Diamond + + prj = Diamond() + +ISE +--- + +`ISE downloads `_ + +ISE (*Integrated Software Environment*) is the previous Xilinx's EDA, superseded by Vivado. +The last version is ISE 14.7, launched in October 2013. +It supports devices starting from Spartan 3/Virtex 4 until some of the first members of the 7 series (all the 7 series and above are supported by Vivado). +Previous Spartan/Virtex devices were supported until version 10. + +.. attention:: + + ISE supports Verilog 2001 and VHDL 1993, but not SystemVerilog. + +Example: + +.. code:: + + from pyfpga.ise import Ise + + prj = Ise() + +Libero +------ + +`Libero downloads `_ + +Libero-SoC (Microsemi, acquired by Microchip in 2018) is the evolution of Libero-IDE (Actel, acquired by Microsemi in 2010). +PyFPGA supports Libero-SoC starting from 12.0, which supports most modern families. +For other devices, Libero-SoC 11.9 or Libero-IDE v9.2 are needed, but these versions are not supported by PyFPGA. + +Example: + +.. code:: + + from pyfpga.libero import Libero + + prj = Libero() + +Openflow +-------- + +`Docker downloads `_ + +Openflow is the combination of different Free/Libre and Open Source (FLOSS) tools: + +* Yosys for synthesis, with ghdl-yosys-plugin for VHDL support. +* nextpnr in its ice40 and ecp5 versions. +* Projects icestorm and Trellis. + +It relies on Docker and fine-grain containers. + +.. attention:: + + It is currently the only flow not solved using Tcl (it uses docker in a bash script instead). + +Example: + +.. code:: + + from pyfpga.openflow import Openflow + + prj = Openflow() + +Quartus +------- + +`Quartus downloads `_ + +Quartus Prime (Intel) is the continuation of Quartus II (Altera) and is divided into the Pro, Standard, and Lite editions, each supporting different families. + +Example: + +.. code:: + + from pyfpga.quartus import Quartus + + prj = Quartus() + +Vivado +------ + +`Vivado downloads `_ + +Vivado is the current EDA tool from Xilinx, which has superseded ISE and supports the 7 series and above. +It is included with Vitis, the SDK for embedded applications. + +Example: + +.. code:: + + from pyfpga.vivado import Vivado + + prj = Vivado() From 29b3d35593715d854cfd9984a429cbde91de423d Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Mon, 12 Aug 2024 22:37:05 -0300 Subject: [PATCH 201/254] docs: 'Internals' updated --- docs/internals.rst | 124 ++++++++++++++++++++++++--------------------- 1 file changed, 65 insertions(+), 59 deletions(-) diff --git a/docs/internals.rst b/docs/internals.rst index 4da687d2..504b69ec 100644 --- a/docs/internals.rst +++ b/docs/internals.rst @@ -1,72 +1,78 @@ Internals ========= -Underlying tool steps ---------------------- +Underlying steps +---------------- .. code-block:: - create project - config project - part - precfg (hook) - params - defines - includes - files - top - postcfg (hook) - close project + project creation [options] + project configuration + part + precfg hook + params + defines + includes + files [options] + top + postcfg hook + project close - open project - presyn (hook) - synthesis - postsyn (hook) - prepar (hook) - place_and_route - postpar (hook) - prebit (hook) - bitstream - postbit (hook) - close project + project open + presyn hook + synthesis [options] + postsyn hook + prepar hook + place_and_route [options] + postpar hook + prebit hook + bitstream [options] + postbit hook + project close Internal data structure ----------------------- .. code-block:: - data = { - 'part': 'PARTNAME', - 'includes': ['DIR1', 'DIR2', 'DIR3'], - 'files': { - 'FILE1': {'hdl': 'vhdl', 'lib': 'LIB1', 'opt': 'OPTS'}, - 'FILE2': {'hdl': 'vlog', 'opt': 'OPTS'}, - 'FILE3': {'hdl': 'slog', 'opt': 'OPTS'} - }, - 'top': 'TOPNAME', - 'constraints': { - 'FILE1': {'opt': 'OPTS'}, - 'FILE2': {'opt': 'OPTS'}, - 'FILE3': {'opt': 'OPTS'} - }, - 'params': { - 'PAR1': 'VAL1', - 'PAR2': 'VAL2', - 'PAR3': 'VAL3' - }, - 'defines': { - 'DEF1': 'VAL1', - 'DEF2': 'VAL2', - 'DEF3': 'VAL3' - }, - 'hooks': { - 'precfg': ['CMD1', 'CMD2'], - 'postcfg': ['CMD1', 'CMD2'], - 'presyn': ['CMD1', 'CMD2'], - 'postsyn': ['CMD1', 'CMD2'], - 'prepar': ['CMD1', 'CMD2'], - 'postpar': ['CMD1', 'CMD2'], - 'prebit': ['CMD1', 'CMD2'], - 'postbit': ['CMD1', 'CMD2'] - } - } + data = { + 'part': 'PARTNAME', + 'includes': ['DIR1', 'DIR2', 'DIR3'], + 'files': { + 'FILE1': {'hdl': 'vhdl', 'lib': 'LIB1', 'opt': 'OPTS'}, + 'FILE2': {'hdl': 'vlog', 'opt': 'OPTS'}, + 'FILE3': {'hdl': 'slog', 'opt': 'OPTS'} + }, + 'top': 'TOPNAME', + 'constraints': { + 'FILE1': {'opt': 'OPTS'}, + 'FILE2': {'opt': 'OPTS'}, + 'FILE3': {'opt': 'OPTS'} + }, + 'params': { + 'PAR1': 'VAL1', + 'PAR2': 'VAL2', + 'PAR3': 'VAL3' + }, + 'defines': { + 'DEF1': 'VAL1', + 'DEF2': 'VAL2', + 'DEF3': 'VAL3' + }, + 'hooks': { + 'precfg': ['CMD1', 'CMD2'], + 'postcfg': ['CMD1', 'CMD2'], + 'presyn': ['CMD1', 'CMD2'], + 'postsyn': ['CMD1', 'CMD2'], + 'prepar': ['CMD1', 'CMD2'], + 'postpar': ['CMD1', 'CMD2'], + 'prebit': ['CMD1', 'CMD2'], + 'postbit': ['CMD1', 'CMD2'] + }, + 'options': { + 'prj': 'OPTS', + 'syn': 'OPTS', + 'pre': 'OPTS', + 'pre': 'OPTS' + } + } From 9fb634e7cc92420fb32b1da893c035a4bfade2c7 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Mon, 12 Aug 2024 22:46:42 -0300 Subject: [PATCH 202/254] docs: 'advanced' was updated/simplified --- docs/advanced.rst | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/docs/advanced.rst b/docs/advanced.rst index 44762476..84046c48 100644 --- a/docs/advanced.rst +++ b/docs/advanced.rst @@ -1,14 +1,7 @@ Advanced usage ============== -PyFPGA offers advanced features for more customized and flexible control over FPGA project management. -This section covers two key advanced features: - -1. **Hooks**: These are points in the code where you can insert custom code to extend or modify the behavior of the tool. -Hooks provide a way to integrate additional functionality or perform specific actions at predefined stages of the project lifecycle. - -2. **Options**: This feature allows you to specify additional options to fine-tune the tool's behavior. -Options provide greater control over the tool's operation and enable you to customize the processing according to your specific requirements. +The flow implemented by PyFPGA should be sufficient for most cases, but further customizations are possible and discussed in this section. Hooks ----- @@ -56,6 +49,8 @@ Or in a multi-line format: Options ------- +Options allow you to specify additional settings to fine-tune certain commands. The available options are: + .. ATTENTION:: - WIP feature. + This feature is WIP. From cdfec22663b543c0476dec96f6514fdb1c1d0f28 Mon Sep 17 00:00:00 2001 From: Markus Koch Date: Thu, 22 Aug 2024 11:04:32 +0200 Subject: [PATCH 203/254] diamond: Use the project name as prefix for the combined constraints Diamond defaults to .lpf. This fixes #49. --- pyfpga/templates/diamond.jinja | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyfpga/templates/diamond.jinja b/pyfpga/templates/diamond.jinja index 3a191524..175be04d 100644 --- a/pyfpga/templates/diamond.jinja +++ b/pyfpga/templates/diamond.jinja @@ -25,7 +25,7 @@ prj_src add {% if 'lib' in attr %}-work {{ attr.lib }}{% else %}{% endif %} {{ n # 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"] +set fileId [open {{ project }}.lpf "w"] {% for name, attr in constraints.items() %} set fp [open "{{ name }}" r] set file_data [read $fp] From b2cd5cb82b26dee68fd03d1fe0b5210617a04cc2 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Thu, 22 Aug 2024 22:44:00 -0300 Subject: [PATCH 204/254] openflow: use the project name as the name for the combined constraints --- pyfpga/templates/openflow.jinja | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/pyfpga/templates/openflow.jinja b/pyfpga/templates/openflow.jinja index 2cafa31d..861a0917 100644 --- a/pyfpga/templates/openflow.jinja +++ b/pyfpga/templates/openflow.jinja @@ -37,7 +37,7 @@ read_verilog -defer -sv {{ name }} chparam{% for key, value in params.items() %} -set {{ key }} {{ value }}{% endfor %} {% endif %} -{% if top%} +{% if top %} synth -top {{ top }} {% endif %} @@ -71,10 +71,10 @@ synth_{{ family }} -top {{ top }} -json {{ project }}.json CONSTRAINTS="{{ constraints | join(' ') }}" {% if family == 'ice40' %} -if [ -n "$CONSTRAINTS" ]; then - cat $CONSTRAINTS > constraints.pcf - CONSTRAINT="--pcf constraints.pcf" -fi +{% if constraints %} +cat $CONSTRAINTS > {{ project }}.pcf +CONSTRAINT="--pcf {{ project }}.pcf" +{% endif %} $DOCKER hdlc/nextpnr:ice40 /bin/bash -c " {% if hooks %}{{ hooks.prepar | join('\n') }}{% endif %} nextpnr-ice40 --{{ device }} --package {{ package }} $CONSTRAINT --json {{ project }}.json --asc {{ project }}.asc @@ -86,10 +86,10 @@ icetime -d {{ device }} -mtr {{ project }}.rpt {{ project }}.asc {% endif %} {% if family == 'ecp5' %} -if [ -n "$CONSTRAINTS" ]; then - cat $CONSTRAINTS > constraints.lpf - CONSTRAINT="--lpf constraints.lpf" -fi +{% if constraints %} +cat $CONSTRAINTS > {{ project }}.lpf +CONSTRAINT="--lpf {{ project }}.lpf" +{% endif %} $DOCKER hdlc/nextpnr:ecp5 /bin/bash -c " {% if hooks %}{{ hooks.prepar | join('\n') }}{% endif %} nextpnr-ecp5 --{{ device }} --package {{ package }} $CONSTRAINT --json {{ project }}.json --textcfg {{ project }}.config From e87cde86f3481268fed37bd5dc546c41b4c0f78f Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Thu, 22 Aug 2024 22:49:39 -0300 Subject: [PATCH 205/254] openflow: add missing comments --- pyfpga/templates/openflow.jinja | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pyfpga/templates/openflow.jinja b/pyfpga/templates/openflow.jinja index 861a0917..088a77dc 100644 --- a/pyfpga/templates/openflow.jinja +++ b/pyfpga/templates/openflow.jinja @@ -15,15 +15,15 @@ $DOCKER hdlc/ghdl:yosys /bin/bash -c " {% if hooks %}{{ hooks.presyn | join('\n') }}{% endif %} yosys -Q -m ghdl -p ' -{% if includes %} +{% if includes %}# Verilog Includes verilog_defaults -add{% for path in includes %} -I{{ path }}{% endfor %} {% endif %} -{% if defines %} +{% if defines %}# Verilog Defines verilog_defines{% for key, value in defines.items() %} -D{{ key }}={{ value }}{% endfor %} {% endif %} -{% if files %} +{% if files %}# Files inclusion {% for name, attr in files.items() %} {% if attr.hdl == "vlog" %} read_verilog -defer {{ name }} @@ -33,11 +33,11 @@ read_verilog -defer -sv {{ name }} {% endfor %} {% endif %} -{% if params %} +{% if params %}# Verilog Parameters / VHDL Generics chparam{% for key, value in params.items() %} -set {{ key }} {{ value }}{% endfor %} {% endif %} -{% if top %} +{% if top %}# Top-level specification synth -top {{ top }} {% endif %} From 93d1bb08ad455f09f9982341fa5d9e90d8457aa1 Mon Sep 17 00:00:00 2001 From: Benjamin ZIEGLER Date: Fri, 16 Aug 2024 14:13:11 +0200 Subject: [PATCH 206/254] Print actual data when adding configurations to a project --- pyfpga/project.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/pyfpga/project.py b/pyfpga/project.py index 86d9f267..0b7298b8 100644 --- a/pyfpga/project.py +++ b/pyfpga/project.py @@ -60,7 +60,7 @@ def set_part(self, name): :param name: FPGA part name :type name: str """ - self.logger.debug('Executing set_part') + self.logger.debug(f'Executing set_part: {name}') self.data['part'] = name def add_include(self, path): @@ -72,7 +72,7 @@ def add_include(self, path): :type name: str :raises NotADirectoryError: if path is not a directory """ - self.logger.debug('Executing add_include') + self.logger.debug(f'Executing add_include: {path}') path = Path(path).resolve() if not path.is_dir(): raise NotADirectoryError(path) @@ -98,7 +98,7 @@ def add_slog(self, pathname): :type pathname: str :raises FileNotFoundError: when pathname is not found """ - self.logger.debug('Executing add_slog') + self.logger.debug(f'Executing add_slog: {pathname}') self._add_file(pathname, 'slog') def add_vhdl(self, pathname, lib=None): @@ -110,7 +110,8 @@ def add_vhdl(self, pathname, lib=None): :type lib: str, optional :raises FileNotFoundError: when pathname is not found """ - self.logger.debug('Executing add_vhdl') + lib_str = 'default library' if lib is None else lib + self.logger.debug(f'Executing add_vhdl: {lib_str} : {pathname}') self._add_file(pathname, 'vhdl', lib) def add_vlog(self, pathname): @@ -120,7 +121,7 @@ def add_vlog(self, pathname): :type pathname: str :raises FileNotFoundError: when pathname is not found """ - self.logger.debug('Executing add_vlog') + self.logger.debug(f'Executing add_vlog: {pathname}') self._add_file(pathname, 'vlog') def add_cons(self, path): @@ -130,7 +131,7 @@ def add_cons(self, path): :type pathname: str :raises FileNotFoundError: if path is not found """ - self.logger.debug('Executing add_cons') + self.logger.debug(f'Executing add_cons: {path}') path = Path(path).resolve() if not path.is_file(): raise FileNotFoundError(path) @@ -145,7 +146,7 @@ def add_param(self, name, value): :param value: parameter/generic value :type name: str """ - self.logger.debug('Executing add_param') + self.logger.debug(f'Executing add_param: {name} : {value}') self.data.setdefault('params', {})[name] = value def add_define(self, name, value): @@ -156,7 +157,7 @@ def add_define(self, name, value): :param value: define value :type name: str """ - self.logger.debug('Executing add_define') + self.logger.debug(f'Executing add_define: {name} : {value}') self.data.setdefault('defines', {})[name] = value def add_fileset(self, pathname): @@ -166,7 +167,7 @@ def add_fileset(self, pathname): :type pathname: str :raises FileNotFoundError: when pathname is not found """ - self.logger.debug('Executing add_fileset') + self.logger.debug(f'Executing add_fileset: {pathname}') if not os.path.exists(pathname): raise FileNotFoundError(pathname) raise NotImplementedError() @@ -177,7 +178,7 @@ def set_top(self, name): :param name: top-level name :type name: str """ - self.logger.debug('Executing set_top') + self.logger.debug(f'Executing set_top: {name}') self.data['top'] = name def add_hook(self, stage, hook): @@ -191,7 +192,7 @@ def add_hook(self, stage, hook): :type hook: str :raises ValueError: when stage is invalid """ - self.logger.debug('Executing add_hook') + self.logger.debug(f'Executing add_hook: {stage} : {hook}') stages = [ 'precfg', 'postcfg', 'presyn', 'postsyn', 'prepar', 'postpar', 'prebit', 'postbit' From 1bdbb64fefbe2ad186039aea279dffc58d79b695 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Wed, 28 Aug 2024 22:13:28 -0300 Subject: [PATCH 207/254] Fix linting issue --- pyfpga/project.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/pyfpga/project.py b/pyfpga/project.py index 0b7298b8..b6061b0f 100644 --- a/pyfpga/project.py +++ b/pyfpga/project.py @@ -60,7 +60,7 @@ def set_part(self, name): :param name: FPGA part name :type name: str """ - self.logger.debug(f'Executing set_part: {name}') + self.logger.debug('Executing set_part: %s', name) self.data['part'] = name def add_include(self, path): @@ -72,7 +72,7 @@ def add_include(self, path): :type name: str :raises NotADirectoryError: if path is not a directory """ - self.logger.debug(f'Executing add_include: {path}') + self.logger.debug('Executing add_include: %s', path) path = Path(path).resolve() if not path.is_dir(): raise NotADirectoryError(path) @@ -98,7 +98,7 @@ def add_slog(self, pathname): :type pathname: str :raises FileNotFoundError: when pathname is not found """ - self.logger.debug(f'Executing add_slog: {pathname}') + self.logger.debug('Executing add_slog: %s', pathname) self._add_file(pathname, 'slog') def add_vhdl(self, pathname, lib=None): @@ -111,7 +111,7 @@ def add_vhdl(self, pathname, lib=None): :raises FileNotFoundError: when pathname is not found """ lib_str = 'default library' if lib is None else lib - self.logger.debug(f'Executing add_vhdl: {lib_str} : {pathname}') + self.logger.debug('Executing add_vhdl: %s : %s', lib_str, pathname) self._add_file(pathname, 'vhdl', lib) def add_vlog(self, pathname): @@ -121,7 +121,7 @@ def add_vlog(self, pathname): :type pathname: str :raises FileNotFoundError: when pathname is not found """ - self.logger.debug(f'Executing add_vlog: {pathname}') + self.logger.debug('Executing add_vlog: %s', pathname) self._add_file(pathname, 'vlog') def add_cons(self, path): @@ -131,7 +131,7 @@ def add_cons(self, path): :type pathname: str :raises FileNotFoundError: if path is not found """ - self.logger.debug(f'Executing add_cons: {path}') + self.logger.debug('Executing add_cons: %s', path) path = Path(path).resolve() if not path.is_file(): raise FileNotFoundError(path) @@ -146,7 +146,7 @@ def add_param(self, name, value): :param value: parameter/generic value :type name: str """ - self.logger.debug(f'Executing add_param: {name} : {value}') + self.logger.debug('Executing add_param: %s : %s', name, value) self.data.setdefault('params', {})[name] = value def add_define(self, name, value): @@ -157,7 +157,7 @@ def add_define(self, name, value): :param value: define value :type name: str """ - self.logger.debug(f'Executing add_define: {name} : {value}') + self.logger.debug('Executing add_define: %s : %s', name, value) self.data.setdefault('defines', {})[name] = value def add_fileset(self, pathname): @@ -167,7 +167,7 @@ def add_fileset(self, pathname): :type pathname: str :raises FileNotFoundError: when pathname is not found """ - self.logger.debug(f'Executing add_fileset: {pathname}') + self.logger.debug('Executing add_fileset: %s', pathname) if not os.path.exists(pathname): raise FileNotFoundError(pathname) raise NotImplementedError() @@ -178,7 +178,7 @@ def set_top(self, name): :param name: top-level name :type name: str """ - self.logger.debug(f'Executing set_top: {name}') + self.logger.debug('Executing set_top: %s', name) self.data['top'] = name def add_hook(self, stage, hook): @@ -192,7 +192,7 @@ def add_hook(self, stage, hook): :type hook: str :raises ValueError: when stage is invalid """ - self.logger.debug(f'Executing add_hook: {stage} : {hook}') + self.logger.debug('Executing add_hook: %s : %s', stage, hook) stages = [ 'precfg', 'postcfg', 'presyn', 'postsyn', 'prepar', 'postpar', 'prebit', 'postbit' From d584bce2894632bf6c6739be408b4467eccd1cf3 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Fri, 30 Aug 2024 19:04:20 -0300 Subject: [PATCH 208/254] docs: add generation timestamp --- docs/index.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/index.rst b/docs/index.rst index 3e8dfcb0..685e7ac0 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -16,3 +16,9 @@ PyFPGA's documentation tools internals extending + +.. |timestamp| date:: %Y-%m-%d %H:%M (%Z) + +.. note:: + + Documentation generated on |timestamp|. From d866f8bcf0a9be8e254abc2fa2f097fecb43d805 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Fri, 30 Aug 2024 20:11:04 -0300 Subject: [PATCH 209/254] openflow: re-added Synthesis for Xilinx devices --- pyfpga/templates/openflow.jinja | 33 +++++++++++---------------------- 1 file changed, 11 insertions(+), 22 deletions(-) diff --git a/pyfpga/templates/openflow.jinja b/pyfpga/templates/openflow.jinja index 088a77dc..a77a25fd 100644 --- a/pyfpga/templates/openflow.jinja +++ b/pyfpga/templates/openflow.jinja @@ -37,35 +37,24 @@ read_verilog -defer -sv {{ name }} chparam{% for key, value in params.items() %} -set {{ key }} {{ value }}{% endfor %} {% endif %} -{% if top %}# Top-level specification +# Top-level specification and Syntesis +{% if family in ['ice40', 'ecp5'] %} +synth_{{ family }} -top {{ top }} -json {{ project }}.json +{% elif family in ['xc6s', 'xc6v', 'xc5v', 'xc4v', 'xc3sda', 'xc3sa', 'xc3se', 'xc3s', 'xc2vp', 'xc2v', 'xcve', 'xcv'] %} +synth_xilinx -top {{ top }} -family {{ family }} +write_edif -pvector bra {{ project }}.edif -ise +{% elif family %} +synth_xilinx -top {{ top }} -family {{ family }} +write_edif -pvector bra {{ project }}.edif +{% else %} synth -top {{ top }} +write_verilog {{ project }}.v {% endif %} - -synth_{{ family }} -top {{ top }} -json {{ project }}.json ' {% if hooks %}{{ hooks.postsyn | join('\n') }}{% endif %} " {% endif %} -{# -#SYNTH= -#WRITE= -#if [[ $BACKEND == "vivado" ]]; then -# SYNTH="synth_xilinx -top $TOP -family $FAMILY" -# WRITE="write_edif -pvector bra $PROJECT.edif" -#elif [[ $BACKEND == "ise" ]]; then -# SYNTH="synth_xilinx -top $TOP -family $FAMILY -ise" -# WRITE="write_edif -pvector bra $PROJECT.edif" -#elif [[ $BACKEND == "nextpnr" ]]; then -# SYNTH="synth_$FAMILY -top $TOP -json $PROJECT.json" -#elif [[ $BACKEND == "verilog-nosynth" ]]; then -# WRITE="write_verilog $PROJECT.v" -#else -# SYNTH="synth -top $TOP" -# WRITE="write_verilog $PROJECT.v" -#fi -#} - {% if 'par' in steps %}# Place and Route CONSTRAINTS="{{ constraints | join(' ') }}" From 5ae1a819a4e66d5f1948f196ee40cc7e44d4bc4e Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Fri, 30 Aug 2024 20:21:33 -0300 Subject: [PATCH 210/254] Added Diamond into regress.sh --- examples/projects/regress.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/projects/regress.sh b/examples/projects/regress.sh index 6af2f12b..22c8f9e8 100644 --- a/examples/projects/regress.sh +++ b/examples/projects/regress.sh @@ -4,6 +4,7 @@ set -e declare -A TOOLS +TOOLS["diamond"]="brevia2" TOOLS["ise"]="s6micro nexys3" TOOLS["libero"]="maker" TOOLS["openflow"]="icestick edu-ciaa orangecrab ecp5evn" @@ -19,7 +20,7 @@ for TOOL in "${!TOOLS[@]}"; do if [[ "$TOOL" == "ise" && "$SOURCE" == "slog" ]]; then continue fi - if [[ "$TOOL" == "openflow" && "$SOURCE" != "vlog" ]]; then + if [[ "$TOOL" == "openflow" && "$SOURCE" == "vhdl" ]]; then continue fi echo "> $TOOL - $BOARD - $SOURCE" From 024a89a29a01ebc8a7d44553e7bce68e99ad0efe Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Fri, 30 Aug 2024 20:23:05 -0300 Subject: [PATCH 211/254] examples: modify output directory of projects --- examples/projects/diamond.py | 2 +- examples/projects/ise.py | 3 ++- examples/projects/libero.py | 2 +- examples/projects/openflow.py | 3 ++- examples/projects/quartus.py | 3 ++- examples/projects/vivado.py | 3 ++- 6 files changed, 10 insertions(+), 6 deletions(-) diff --git a/examples/projects/diamond.py b/examples/projects/diamond.py index 4f5a85de..0128d7c3 100644 --- a/examples/projects/diamond.py +++ b/examples/projects/diamond.py @@ -17,7 +17,7 @@ ) args = parser.parse_args() -prj = Diamond(odir='../build/diamond') +prj = Diamond(odir=f'results/diamond/{args.source}/{args.board}') if args.board == 'brevia2': prj.set_part('LFXP2-5E-5TN144C') diff --git a/examples/projects/ise.py b/examples/projects/ise.py index 38d886c1..279a5db0 100644 --- a/examples/projects/ise.py +++ b/examples/projects/ise.py @@ -17,7 +17,7 @@ ) args = parser.parse_args() -prj = Ise(odir='../build/ise') +prj = Ise(odir=f'results/ise/{args.source}/{args.board}') if args.board == 's6micro': prj.set_part('xc6slx9-2-csg324') @@ -35,6 +35,7 @@ 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') diff --git a/examples/projects/libero.py b/examples/projects/libero.py index 4edd978e..dd2c5861 100644 --- a/examples/projects/libero.py +++ b/examples/projects/libero.py @@ -16,7 +16,7 @@ ) args = parser.parse_args() -prj = Libero(odir='../build/libero') +prj = Libero(odir=f'results/libero/{args.source}/{args.board}') if args.board == 'maker': prj.set_part('m2s010-1-tq144') diff --git a/examples/projects/openflow.py b/examples/projects/openflow.py index 0b1242b3..56a6b2ab 100644 --- a/examples/projects/openflow.py +++ b/examples/projects/openflow.py @@ -18,7 +18,7 @@ ) args = parser.parse_args() -prj = Openflow(odir='../build/openflow') +prj = Openflow(odir=f'results/openflow/{args.source}/{args.board}') if args.board == 'icestick': prj.set_part('hx1k-tq144') @@ -44,6 +44,7 @@ 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') diff --git a/examples/projects/quartus.py b/examples/projects/quartus.py index 02ed76d3..eb7e889e 100644 --- a/examples/projects/quartus.py +++ b/examples/projects/quartus.py @@ -17,7 +17,7 @@ ) args = parser.parse_args() -prj = Quartus(odir='../build/quartus') +prj = Quartus(odir=f'results/quartus/{args.source}/{args.board}') if args.board == 'de10nano': prj.set_part('5CSEBA6U23I7') @@ -29,6 +29,7 @@ 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') diff --git a/examples/projects/vivado.py b/examples/projects/vivado.py index 35ccecc7..af497b91 100644 --- a/examples/projects/vivado.py +++ b/examples/projects/vivado.py @@ -17,7 +17,7 @@ ) args = parser.parse_args() -prj = Vivado(odir='../build/vivado') +prj = Vivado(odir=f'results/vivado/{args.source}/{args.board}') if args.board == 'zybo': prj.set_part('xc7z010-1-clg400') @@ -35,6 +35,7 @@ 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') From e63851de95b1dc13e4a7c5a7786e0506f979e25d Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Fri, 30 Aug 2024 20:27:22 -0300 Subject: [PATCH 212/254] examples: remove odir specification in diamond (hooks) --- examples/hooks/diamond.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/hooks/diamond.py b/examples/hooks/diamond.py index 389b9a60..ce4f3558 100644 --- a/examples/hooks/diamond.py +++ b/examples/hooks/diamond.py @@ -1,8 +1,8 @@ -"""Diamond example hooks.""" +"""Diamond hooks examples.""" from pyfpga.diamond import Diamond -prj = Diamond(odir='../build/diamond') +prj = Diamond() hooks = { "reports": """ From 0d95e0178f22262f5cfb3a818060a83711a70085 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Fri, 30 Aug 2024 21:16:56 -0300 Subject: [PATCH 213/254] Add to remove the results directories --- Makefile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index fb528da7..88a8c3da 100644 --- a/Makefile +++ b/Makefile @@ -16,7 +16,9 @@ test: clean: py3clean . cd docs; make clean - rm -fr build .pytest_cache + rm -fr .pytest_cache + rm -fr `find . -name results` + rm -fr `find . -name __pycache__` submodule-init: git submodule update --init --recursive From 99f235e3711c8b1b96defb2f6b3cb89978b563ab Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Fri, 30 Aug 2024 21:24:59 -0300 Subject: [PATCH 214/254] docs: minor changes in the generation --- .github/workflows/docs.yml | 2 +- docs/Makefile | 2 +- docs/conf.py | 8 ++++---- docs/helpers.rst | 6 +++--- docs/{_static => images}/logo.png | Bin docs/{_static => images}/schema.png | Bin docs/index.rst | 2 +- 7 files changed, 10 insertions(+), 10 deletions(-) rename docs/{_static => images}/logo.png (100%) rename docs/{_static => images}/schema.png (100%) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 0f17d958..85963a48 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -19,4 +19,4 @@ jobs: uses: peaceiris/actions-gh-pages@v4 with: github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: docs/_build/html + publish_dir: docs/build/html diff --git a/docs/Makefile b/docs/Makefile index a9325d75..13352008 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -1,7 +1,7 @@ SPHINXOPTS ?= SPHINXBUILD ?= sphinx-build SOURCEDIR = . -BUILDDIR = _build +BUILDDIR = build HELPERS = $(BUILDDIR)/hdl2bit $(BUILDDIR)/prj2bit $(BUILDDIR)/bitprog help: diff --git a/docs/conf.py b/docs/conf.py index 3e57d268..530ce742 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -8,8 +8,8 @@ # -- Project information ----------------------------------------------------- project = 'PyFPGA' -copyright = '2024, Rodrigo Alejandro Melo' -author = 'Rodrigo Alejandro Melo' +copyright = '2016-2024, PyFPGA Project' +author = 'PyFPGA contributors' # -- General configuration --------------------------------------------------- @@ -31,9 +31,9 @@ 'repositoy': ('https://github.com/PyFPGA/pyfpga/tree/main/%s', None) } -exclude_patterns = ['_build', 'wip'] +exclude_patterns = ['build'] # -- Options for HTML output ------------------------------------------------- html_theme = 'sphinx_rtd_theme' -html_static_path = ['_static'] +html_static_path = ['images'] diff --git a/docs/helpers.rst b/docs/helpers.rst index 542deb27..619f2c35 100644 --- a/docs/helpers.rst +++ b/docs/helpers.rst @@ -4,14 +4,14 @@ Helpers hdl2bit ------- -.. literalinclude:: _build/hdl2bit +.. literalinclude:: build/hdl2bit prj2bit ------- -.. literalinclude:: _build/prj2bit +.. literalinclude:: build/prj2bit bitprog ------- -.. literalinclude:: _build/bitprog +.. literalinclude:: build/bitprog diff --git a/docs/_static/logo.png b/docs/images/logo.png similarity index 100% rename from docs/_static/logo.png rename to docs/images/logo.png diff --git a/docs/_static/schema.png b/docs/images/schema.png similarity index 100% rename from docs/_static/schema.png rename to docs/images/schema.png diff --git a/docs/index.rst b/docs/index.rst index 685e7ac0..3bfdf197 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,7 +1,7 @@ PyFPGA's documentation ====================== -.. image:: _static/logo.png +.. image:: images/logo.png :width: 200 px :align: center :target: https://github.com/PyFPGA/pyfpga From 7fc1ab9017328b405841ccc704273f47484a5461 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Fri, 30 Aug 2024 21:57:42 -0300 Subject: [PATCH 215/254] Removed an unneeded comment --- pyfpga/templates/libero.jinja | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyfpga/templates/libero.jinja b/pyfpga/templates/libero.jinja index aaddbf9b..c64d8a5d 100644 --- a/pyfpga/templates/libero.jinja +++ b/pyfpga/templates/libero.jinja @@ -14,7 +14,7 @@ set_device -family {{ family }} -die {{ device }} -package {{ package }} -speed {% if hooks %}{{ hooks.precfg | join('\n') }}{% endif %} -{% if includes %}# Verilog Includes (Libero) +{% if includes %}# Verilog Includes set_global_include_path_order -paths "{{ includes | join(' ') }}" {% endif %} From ed36eaddd0daad22cbb401b64b689089604932b9 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sat, 31 Aug 2024 08:41:17 -0300 Subject: [PATCH 216/254] tests: moved from projects/support.py to simply support.py --- tests/{projects => }/support.py | 40 ++++++++++++++++----------------- 1 file changed, 20 insertions(+), 20 deletions(-) rename tests/{projects => }/support.py (70%) diff --git a/tests/projects/support.py b/tests/support.py similarity index 70% rename from tests/projects/support.py rename to tests/support.py index 68bc88b1..128a17c5 100644 --- a/tests/projects/support.py +++ b/tests/support.py @@ -17,16 +17,16 @@ print('INFO: checking basic Verilog Support') prj = Factory(args.tool) -prj.add_vlog('../../examples/sources/vlog/blink.v') +prj.add_vlog('../examples/sources/vlog/blink.v') prj.set_top('Blink') prj.make(last='syn') print('INFO: checking advanced Verilog Support') prj = Factory(args.tool) -prj.add_vlog('../../examples/sources/vlog/*.v') +prj.add_vlog('../examples/sources/vlog/*.v') prj.set_top('Top') -prj.add_include('../../examples/sources/vlog/include1') -prj.add_include('../../examples/sources/vlog/include2') +prj.add_include('../examples/sources/vlog/include1') +prj.add_include('../examples/sources/vlog/include2') prj.add_define('DEFINE1', '1') prj.add_define('DEFINE2', '1') prj.add_param('FREQ', '1') @@ -36,7 +36,7 @@ try: print('INFO: checking Verilog Includes Support') prj = Factory(args.tool) - prj.add_vlog('../../examples/sources/vlog/*.v') + prj.add_vlog('../examples/sources/vlog/*.v') prj.set_top('Top') prj.add_define('DEFINE1', '1') prj.add_define('DEFINE2', '1') @@ -52,10 +52,10 @@ try: print('INFO: checking Verilog Defines Support') prj = Factory(args.tool) - prj.add_vlog('../../examples/sources/vlog/*.v') + prj.add_vlog('../examples/sources/vlog/*.v') prj.set_top('Top') - prj.add_include('../../examples/sources/vlog/include1') - prj.add_include('../../examples/sources/vlog/include2') + prj.add_include('../examples/sources/vlog/include1') + prj.add_include('../examples/sources/vlog/include2') prj.add_param('FREQ', '1') prj.add_param('SECS', '1') prj.make(last='syn') @@ -68,10 +68,10 @@ try: print('INFO: checking Verilog Parameters Support') prj = Factory(args.tool) - prj.add_vlog('../../examples/sources/vlog/*.v') + prj.add_vlog('../examples/sources/vlog/*.v') prj.set_top('Top') - prj.add_include('../../examples/sources/vlog/include1') - prj.add_include('../../examples/sources/vlog/include2') + prj.add_include('../examples/sources/vlog/include1') + prj.add_include('../examples/sources/vlog/include2') prj.add_define('DEFINE1', '1') prj.add_define('DEFINE2', '1') prj.make(last='syn') @@ -84,16 +84,16 @@ if args.tool not in ['ise']: print('INFO: checking basic System Verilog Support') prj = Factory(args.tool) - prj.add_slog('../../examples/sources/slog/blink.sv') + prj.add_slog('../examples/sources/slog/blink.sv') prj.set_top('Blink') prj.make(last='syn') print('INFO: checking advanced System Verilog Support') prj = Factory(args.tool) - prj.add_slog('../../examples/sources/slog/*.sv') + prj.add_slog('../examples/sources/slog/*.sv') prj.set_top('Top') - prj.add_include('../../examples/sources/slog/include1') - prj.add_include('../../examples/sources/slog/include2') + prj.add_include('../examples/sources/slog/include1') + prj.add_include('../examples/sources/slog/include2') prj.add_define('DEFINE1', '1') prj.add_define('DEFINE2', '1') prj.add_param('FREQ', '1') @@ -103,14 +103,14 @@ if args.tool not in ['openflow']: print('* INFO: checking basic VHDL Support') prj = Factory(args.tool) - prj.add_vhdl('../../examples/sources/vhdl/blink.vhdl') + prj.add_vhdl('../examples/sources/vhdl/blink.vhdl') prj.set_top('Blink') prj.make(last='syn') print('* INFO: checking advanced VHDL Support') prj = Factory(args.tool) - prj.add_vhdl('../../examples/sources/vhdl/*.vhdl', 'blink_lib') - prj.add_vhdl('../../examples/sources/vhdl/top.vhdl') + prj.add_vhdl('../examples/sources/vhdl/*.vhdl', 'blink_lib') + prj.add_vhdl('../examples/sources/vhdl/top.vhdl') prj.set_top('Top') prj.add_param('FREQ', '1') prj.add_param('SECS', '1') @@ -119,8 +119,8 @@ try: print('INFO: checking VHDL Generics') prj = Factory(args.tool) - prj.add_vhdl('../../examples/sources/vhdl/*.vhdl', 'blink_lib') - prj.add_vhdl('../../examples/sources/vhdl/top.vhdl') + prj.add_vhdl('../examples/sources/vhdl/*.vhdl', 'blink_lib') + prj.add_vhdl('../examples/sources/vhdl/top.vhdl') prj.set_top('Top') prj.make(last='syn') sys.exit('ERROR: something does not work as expected') From 9188bd8f795af65458f6a380f0d5f012d5212ee9 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sat, 31 Aug 2024 09:04:30 -0300 Subject: [PATCH 217/254] Add to specify the tool to run in regress.sh --- examples/projects/regress.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/examples/projects/regress.sh b/examples/projects/regress.sh index 22c8f9e8..be6219a0 100644 --- a/examples/projects/regress.sh +++ b/examples/projects/regress.sh @@ -13,7 +13,12 @@ TOOLS["vivado"]="zybo arty" SOURCES=("vlog" "vhdl" "slog") +SPECIFIED_TOOL=$1 + for TOOL in "${!TOOLS[@]}"; do + if [[ -n "$SPECIFIED_TOOL" && "$TOOL" != "$SPECIFIED_TOOL" ]]; then + continue + fi BOARDS=${TOOLS[$TOOL]} for BOARD in $BOARDS; do for SOURCE in "${SOURCES[@]}"; do From f203de16cc71fae6a6307f3eb159517589607d81 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sat, 31 Aug 2024 09:05:23 -0300 Subject: [PATCH 218/254] Removed unused targets and added all --- Makefile | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index 88a8c3da..892fdcb4 100644 --- a/Makefile +++ b/Makefile @@ -2,6 +2,8 @@ .PHONY: docs +all: docs lint test + docs: cd docs; make html @@ -19,9 +21,3 @@ clean: rm -fr .pytest_cache rm -fr `find . -name results` rm -fr `find . -name __pycache__` - -submodule-init: - git submodule update --init --recursive - -submodule-update: - cd examples/resources; git checkout main; git pull From a4ffd5df78f53fdb96276e695c194e8f25ceda1d Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sat, 31 Aug 2024 09:11:07 -0300 Subject: [PATCH 219/254] docs: improve the extending section --- docs/extending.rst | 76 ++++++++++++++++++++++++++++++++++------------ 1 file changed, 56 insertions(+), 20 deletions(-) diff --git a/docs/extending.rst b/docs/extending.rst index 03c995cf..18956259 100644 --- a/docs/extending.rst +++ b/docs/extending.rst @@ -1,40 +1,76 @@ Extending ========= -1. Add support for the new tool: +.. note:: -.. code-block:: python + All classes inherit from project.py. + +This is a guide on how to add support for a new TOOL. + +Add support for the new tool +---------------------------- + +.. code-block:: bash pyfpga/templates/.jinja pyfpga/templates/-prog.jinja pyfpga/.py + pyfpga/factory.py # UPDATE + pyfpga/helpers/prj2bit.py # UPDATE -2. Include the new tool on Factory: +Add tests and a tool mock-up +---------------------------- -.. code-block:: python +.. code-block:: bash - pyfpga/factory.py + tests/test_tools.py # UPDATE + tests/support.py # UPDATE if exceptions are needed + tests/mocks/ -3. Add tests and a tool mock-up: +Add examples +------------ -.. code-block:: python +.. code-block:: bash - tests/test_tools.py - tests/mocks/ + examples/sources/cons//timing. + examples/sources/cons//clk. + examples/sources/cons//led. + examples/projects/.py + examples/projects/regress.sh # UPDATE + examples/helpers/.sh + examples/hooks/.py # OPTIONAL -4. Updated the project's documentation: +Verify the code +--------------- -.. code-block:: python +Run it at the root of the repo. - README.md - docs +.. code-block:: bash -5. [OPTIONAL] Add examples: + make docs + make lint + make test -.. code-block:: python +.. tip:: - examples/sources/cons//timing. - examples/sources/cons//clk. - examples/sources/cons//led. - examples/projects/.py - examples/hooks/.py + You can simply run ``make`` to perform all the operations. + Running ``make clean`` will remove all the generated files. + +Verify the functionality +------------------------ + +.. code-block:: bash + + cd examples/projects/ + bash regress.sh + cd ../../tests/ + python3 support.py --tool + +Updated the documentation +------------------------- + +.. code-block:: bash + + README.md + docs/intro.rst + docs/tools.rst From 8f7e89a4a554714ce1089765c94bb9469f3f1f12 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sat, 31 Aug 2024 09:29:59 -0300 Subject: [PATCH 220/254] examples: renamed yosys as misc --- examples/{yosys/ise.py => misc/yosys-ise.py} | 0 examples/{yosys/vivado.py => misc/yosys-vivado.py} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename examples/{yosys/ise.py => misc/yosys-ise.py} (100%) rename examples/{yosys/vivado.py => misc/yosys-vivado.py} (100%) diff --git a/examples/yosys/ise.py b/examples/misc/yosys-ise.py similarity index 100% rename from examples/yosys/ise.py rename to examples/misc/yosys-ise.py diff --git a/examples/yosys/vivado.py b/examples/misc/yosys-vivado.py similarity index 100% rename from examples/yosys/vivado.py rename to examples/misc/yosys-vivado.py From 578ee491c9cb75949ec563b449521bdcb7804fa8 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sat, 31 Aug 2024 09:31:10 -0300 Subject: [PATCH 221/254] examples: update/simplify README.md --- examples/README.md | 60 +++++++--------------------------------------- 1 file changed, 8 insertions(+), 52 deletions(-) diff --git a/examples/README.md b/examples/README.md index e648ca00..2ec68b5e 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,55 +1,11 @@ -# PyFPGA examples +# PyFPGA Examples -## Tool-specific examples +In this section, you will find: -Led blinking examples where a Bitstream is generated and transfer to a -supported board. It shows the inclusion of Constraints files. +* `projects`: basic but complete examples for each supported tool. +* `helpers`: examples of the PyFPGA helpers. +* `hooks`: how to use this feature. +* `misc`: miscellaneous examples. -* [ghdl](ghdl): VHDL synthesis with GDHL (`--synth`) -* [ise](ise): Spartan-6 FPGA LX9 MicroBoard (Avnet) -* [libero](libero): Digi-Key SmartFusion2 Maker Board (Digi-Key) -* [openflow](openflow): - * IceStick (`icestorm.py`) - * EDU-CIAA-FPGA (`icestorm.py --board edu-ciaa-fpga`) - * OrangeCrab-r0.2 (`prjtrellis.py`) - * ECP5 Evaluation Board (`prjtrellis.py --board ecp5evn`) -* [quartus](quartus): DE10Nano (Terasic) -* [vivado](vivado): Zybo (Digilent) -* [yosys](yosys): - * Verilog synthesis with Yosys (using `ghdl-yosys-plugin` for VHDL) - * Spartan-6 FPGA LX9 MicroBoard (`ise.py`) - * Zybo (`vivado.py`) - -## Multi-project examples - -Examples where more than a project is solved in the same script. - -* [multi/projects.py](multi/projects.py): it uses a dict with three project -names where different tools, part names, files and top-level names can be -specified. In this manner, you can manage alternatives or sub-products of your -design in a single place. -* [multi/verilog.py](multi/verilog.py): here the same set of Verilog files are -synthesised with all the available tools, which is useful to make comparations -and check portability. -* [multi/vhdl.py](multi/vhdl.py): the same concept that the previous one, but -using VHDL instead of Verilog files. The main difference is how to deal with -VHDL libraries. -* [multi/parameters.py](multi/parameters.py): VHDL and Verilog files are -synthesized changing the value of its generics/parameters. -* [multi/memory.py](multi/memory.py): it tests the Memory Content Files -inclusion capability of the supported tools. - -## Hooks examples - -* [hooks/strategies.py](hooks/strategies.py): the same HDL is synthesized by -different tools, changing the optimization strategy (`area`, `power` and -`speed`). - -## Helpers - -Examples to exercise developed helper tools such as `hdl2bit`, `prj2bit` and -`bitprog`. - -## Miscellaneous examples - -* [misc/capture.py](misc/capture.py): it shows how to capture the execution messages. +For an example where all the tools are employed based on the same code, you can check +[support.py](../tests/support.py) (located under the [tests](../tests) directory). From fbf80e0b1834f861fc836eb4e132184071de1c2f Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sat, 31 Aug 2024 10:08:55 -0300 Subject: [PATCH 222/254] diamondc: removed unused import --- tests/mocks/diamondc | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/mocks/diamondc b/tests/mocks/diamondc index 1beac9c1..50303cb3 100755 --- a/tests/mocks/diamondc +++ b/tests/mocks/diamondc @@ -8,7 +8,6 @@ import argparse import subprocess -import sys parser = argparse.ArgumentParser() From 884fda8490a2b9e2d9e69b5ab7e983868f2688d4 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sat, 31 Aug 2024 10:09:33 -0300 Subject: [PATCH 223/254] Simplified how to clean docs --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 892fdcb4..2f2402c4 100644 --- a/Makefile +++ b/Makefile @@ -17,7 +17,7 @@ test: clean: py3clean . - cd docs; make clean + rm -fr docs/build rm -fr .pytest_cache rm -fr `find . -name results` rm -fr `find . -name __pycache__` From 7851b97872258d7728f6be271b6f83f21ca420a5 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sat, 31 Aug 2024 10:25:45 -0300 Subject: [PATCH 224/254] ci: modified when to trigger the docs actions --- .github/workflows/docs.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 85963a48..1db219c9 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -2,6 +2,8 @@ name: 'docs' on: push: + paths: + - 'docs/**' branches: # - main From c7e5369def5d4fb26dfcee20ca5f7b33dea40177 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sat, 31 Aug 2024 13:21:24 -0300 Subject: [PATCH 225/254] openflow: re-added VHDL support (WIP) --- pyfpga/templates/openflow.jinja | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/pyfpga/templates/openflow.jinja b/pyfpga/templates/openflow.jinja index a77a25fd..b5b6d96c 100644 --- a/pyfpga/templates/openflow.jinja +++ b/pyfpga/templates/openflow.jinja @@ -13,6 +13,17 @@ DOCKER="docker run --user $(id -u):$(id -g) --rm -v $HOME:$HOME -w $PWD" {% if 'syn' in steps %}# Synthesis $DOCKER hdlc/ghdl:yosys /bin/bash -c " {% if hooks %}{{ hooks.presyn | join('\n') }}{% endif %} + +{% set gflags = '--std=08 -fsynopsys -fexplicit -frelaxed' %} +{% if files %}# Files inclusion +{% for name, attr in files.items() %} +{% if attr.hdl == "vhdl" %} +ghdl -a {{ gflags }}{% if 'lib' in attr %} --work={{ attr.lib }}{% endif %} {{ name }} +{% endif %} +{% endfor %} +{% endif %} +ghdl -a --std=08 -fsynopsys -fexplicit -frelaxed /home/rodrigo/repos-ram/PyFPGA/pyfpga/examples/sources/vhdl/top.vhdl + yosys -Q -m ghdl -p ' {% if includes %}# Verilog Includes @@ -29,13 +40,21 @@ verilog_defines{% for key, value in defines.items() %} -D{{ key }}={{ value }}{% read_verilog -defer {{ name }} {% elif attr.hdl == "slog" %} read_verilog -defer -sv {{ name }} +{% elif attr.hdl == "vhdl" %} +{% if loop.first %} +# VHDL Generics +{% set generics = "-gFREQ=125000000 -gSECS=1" %} +ghdl {{ gflags }} {{ generics }} {{ top }} +{% endif %} {% endif %} {% endfor %} {% endif %} -{% if params %}# Verilog Parameters / VHDL Generics +{# +{% if params %}# Verilog Parameters chparam{% for key, value in params.items() %} -set {{ key }} {{ value }}{% endfor %} {% endif %} +#} # Top-level specification and Syntesis {% if family in ['ice40', 'ecp5'] %} From 01991d7a0fe600b778b20545015dad3e99bee740 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sat, 31 Aug 2024 13:36:01 -0300 Subject: [PATCH 226/254] openflow: add generics support --- pyfpga/templates/openflow.jinja | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/pyfpga/templates/openflow.jinja b/pyfpga/templates/openflow.jinja index b5b6d96c..a0c18dd6 100644 --- a/pyfpga/templates/openflow.jinja +++ b/pyfpga/templates/openflow.jinja @@ -42,19 +42,18 @@ read_verilog -defer {{ name }} read_verilog -defer -sv {{ name }} {% elif attr.hdl == "vhdl" %} {% if loop.first %} -# VHDL Generics -{% set generics = "-gFREQ=125000000 -gSECS=1" %} -ghdl {{ gflags }} {{ generics }} {{ top }} +{% if params %}# VHDL Generics +ghdl {{ gflags }}{% for key, value in params.items() %} -g{{ key }}={{ value }}{% endfor %} {{ top }} +{% else %} +ghdl {{ gflags }} {{ top }} {% endif %} {% endif %} -{% endfor %} {% endif %} - -{# -{% if params %}# Verilog Parameters +{% if loop.last and attr.hdl in ["vlog", "slog"] and params %}# Verilog Parameters chparam{% for key, value in params.items() %} -set {{ key }} {{ value }}{% endfor %} {% endif %} -#} +{% endfor %} +{% endif %} # Top-level specification and Syntesis {% if family in ['ice40', 'ecp5'] %} From 2864ced35c7468daa0c803185db6b2d9e4770f0c Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Mon, 2 Sep 2024 19:38:14 -0300 Subject: [PATCH 227/254] openflow: properly re-added VHDL support Closes #51 --- examples/projects/regress.sh | 3 --- pyfpga/project.py | 6 ++++-- pyfpga/templates/openflow.jinja | 5 ++--- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/examples/projects/regress.sh b/examples/projects/regress.sh index be6219a0..45b7d848 100644 --- a/examples/projects/regress.sh +++ b/examples/projects/regress.sh @@ -25,9 +25,6 @@ for TOOL in "${!TOOLS[@]}"; do if [[ "$TOOL" == "ise" && "$SOURCE" == "slog" ]]; then continue fi - if [[ "$TOOL" == "openflow" && "$SOURCE" == "vhdl" ]]; then - continue - fi echo "> $TOOL - $BOARD - $SOURCE" python3 $TOOL.py --board $BOARD --source $SOURCE done diff --git a/pyfpga/project.py b/pyfpga/project.py index b6061b0f..10bb57ea 100644 --- a/pyfpga/project.py +++ b/pyfpga/project.py @@ -83,13 +83,15 @@ def _add_file(self, pathname, hdl=None, lib=None): if len(files) == 0: raise FileNotFoundError(pathname) for file in files: - path = Path(file).resolve() + path = Path(file).resolve().as_posix() attr = {} if hdl: attr['hdl'] = hdl if lib: attr['lib'] = lib - self.data.setdefault('files', {})[path.as_posix()] = attr + if path in self.data.get('files', {}): + del self.data['files'][path] + self.data.setdefault('files', {})[path] = attr def add_slog(self, pathname): """Add System Verilog file/s. diff --git a/pyfpga/templates/openflow.jinja b/pyfpga/templates/openflow.jinja index a0c18dd6..79977719 100644 --- a/pyfpga/templates/openflow.jinja +++ b/pyfpga/templates/openflow.jinja @@ -15,14 +15,13 @@ $DOCKER hdlc/ghdl:yosys /bin/bash -c " {% if hooks %}{{ hooks.presyn | join('\n') }}{% endif %} {% set gflags = '--std=08 -fsynopsys -fexplicit -frelaxed' %} -{% if files %}# Files inclusion +{% if files %}# VHDL Files inclusion {% for name, attr in files.items() %} {% if attr.hdl == "vhdl" %} ghdl -a {{ gflags }}{% if 'lib' in attr %} --work={{ attr.lib }}{% endif %} {{ name }} {% endif %} {% endfor %} {% endif %} -ghdl -a --std=08 -fsynopsys -fexplicit -frelaxed /home/rodrigo/repos-ram/PyFPGA/pyfpga/examples/sources/vhdl/top.vhdl yosys -Q -m ghdl -p ' @@ -34,7 +33,7 @@ verilog_defaults -add{% for path in includes %} -I{{ path }}{% endfor %} verilog_defines{% for key, value in defines.items() %} -D{{ key }}={{ value }}{% endfor %} {% endif %} -{% if files %}# Files inclusion +{% if files %}# VLOG Files inclusion {% for name, attr in files.items() %} {% if attr.hdl == "vlog" %} read_verilog -defer {{ name }} From aa29487869b751bed48cf167cdd2658e279a4da9 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Mon, 2 Sep 2024 20:34:54 -0300 Subject: [PATCH 228/254] docs: add a hint about file processing order --- docs/basic.rst | 35 ++++++++++++++++++++++++----------- docs/extending.rst | 2 +- 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/docs/basic.rst b/docs/basic.rst index 5efcfccd..0b40c35f 100644 --- a/docs/basic.rst +++ b/docs/basic.rst @@ -4,7 +4,7 @@ Basic usage Project Configuration --------------------- -The first steps involve importing the necessary module to support the desired tool and instantiating the corresponding *class*: +The first steps involve importing the necessary module to support the desired tool and instantiating the corresponding ``class``: .. code-block:: python @@ -12,7 +12,9 @@ The first steps involve importing the necessary module to support the desired to prj = Vivado('PRJNAME', odir='OUTDIR') -In the example, we are using Vivado, specifying the optional parameter *project name* (*tool name* if omitted) and *output directory* (*results* by default). +In the example, we are using Vivado, specifying the optional parameters +``project name`` (``tool name`` if omitted) and ``output directory`` +(``results`` by default). Next step is to specify the target FPGA device: @@ -32,9 +34,8 @@ HDL source files are added using one of the following methods: prj.add_vlog('PATH_TO_FILES_GLOB_COMPATIBLE') prj.add_slog('PATH_TO_FILES_GLOB_COMPATIBLE') -In these methods, you provide a path to the files. The path can include wildcards (like `*.vhdl`), allowing you to match multiple files at once. - -For `add_vhdl`, you can also optionally specify a library name where the files will be included. +In these methods, you provide a path to the files. The path can include wildcards (like ``*.vhdl``), allowing you to match multiple files at once. +In case of ``add_vhdl``, you can also optionally specify a library name where the files will be included. .. note:: @@ -43,6 +44,15 @@ For `add_vhdl`, you can also optionally specify a library name where the files w .. _glob: https://docs.python.org/3/library/glob.html .. _Path: https://docs.python.org/3/library/pathlib.html +.. hint:: + + Files are processed in the order they are added. If a file is specified more than once, + it is removed from its previous position and placed at the end of the list. + This allows you to ensure that a file is processed after others when necessary + (e.g., placing a top-level at the end) or to customize options + (e.g., removing a VHDL library specification in case of a top-level) + when multiple files are added using a wildcard. + Generics/parameters can be specified with: .. code-block:: python @@ -82,7 +92,8 @@ After configuring the project, you can run the following to generate a bitstream prj.make() -By default, this method performs *project creation*, *synthesis*, *place and route*, and *bitstream generation*. +By default, this method performs **project creation**, **synthesis**, **place and route**, +and **bitstream generation**. However, you can optionally specify both the initial and final stages, as follows: .. code-block:: python @@ -100,7 +111,8 @@ However, you can optionally specify both the initial and final stages, as follow .. note:: - After executing this method, you will find the file `.tcl` (or `sh` in some cases) in the output directory. + After executing this method, you will find the file ``.tcl`` + (``.sh`` in some cases) in the output directory. For debugging purposes, if things do not work as expected, you can review this file. Bitstream programming @@ -112,13 +124,14 @@ The final step is programming the FPGA: prj.prog('BITSTREAM', 'POSITION') -Both `BITSTREAM` and `POSITION` are optional. -If `BITSTREAM` is not specified, PyFPGA will attempt to discover it based on project information. -The `POSITION` parameter is not always required (depends on the tool being used). +Both ``BITSTREAM`` and ``POSITION`` are optional. +If ``BITSTREAM`` is not specified, PyFPGA will attempt to discover it based on project information. +The ``POSITION`` parameter is not always required (depends on the tool being used). .. note:: - After executing this method, you will find the file `prog.tcl` (or `sh` in some cases) in the output directory. + After executing this method, you will find the file ``prog.tcl`` + (``-prog.sh`` in some cases) in the output directory. For debugging purposes, if things do not work as expected, you can review this file. Debugging diff --git a/docs/extending.rst b/docs/extending.rst index 18956259..746ffc5d 100644 --- a/docs/extending.rst +++ b/docs/extending.rst @@ -3,7 +3,7 @@ Extending .. note:: - All classes inherit from project.py. + All classes inherit from ``Project`` (``project.py``). This is a guide on how to add support for a new TOOL. From 9a2df317a720e6899aad003a5b6bb38e16c73ed3 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Mon, 2 Sep 2024 21:29:43 -0300 Subject: [PATCH 229/254] docs: added a diagram about Openflow --- docs/images/Makefile | 15 +++ docs/images/openflow.dot | 28 +++++ docs/images/openflow.svg | 224 +++++++++++++++++++++++++++++++++++++++ docs/tools.rst | 4 + 4 files changed, 271 insertions(+) create mode 100644 docs/images/Makefile create mode 100644 docs/images/openflow.dot create mode 100644 docs/images/openflow.svg diff --git a/docs/images/Makefile b/docs/images/Makefile new file mode 100644 index 00000000..2f5b759b --- /dev/null +++ b/docs/images/Makefile @@ -0,0 +1,15 @@ +#!/usr/bin/make + +FILES = $(wildcard *.dot) +FILES := $(basename $(FILES)) +FILES := $(addsuffix .svg,$(FILES)) + +ODIR = . + +vpath %.svg $(ODIR) + +%.svg: %.dot + @mkdir -p $(ODIR) + dot -Tsvg $< -o $(ODIR)/$@ + +all: $(FILES) diff --git a/docs/images/openflow.dot b/docs/images/openflow.dot new file mode 100644 index 00000000..1d1dec16 --- /dev/null +++ b/docs/images/openflow.dot @@ -0,0 +1,28 @@ +digraph openflow { + graph [ranksep=0.25]; + node [shape = doublecircle]; + node [shape = rectangle]; + GHDL "ghdl-yosys-plugin" Yosys "nextpnr-ice40" "nextpnr-ecp5" icetime icepack iceprog eccpack; + node [shape = note ]; + VHDL Verilog; + node [shape = box3d ]; + ice40; + node [shape = oval]; + "bit-ice40" [label=".bit"]; + "bit-ecp5" [label=".bit"]; + VHDL -> {GHDL "ghdl-yosys-plugin"}; + GHDL -> "ghdl-yosys-plugin"; + "ghdl-yosys-plugin" -> Yosys; + Verilog -> Yosys; + Yosys -> ".json"; + ".json" -> {"nextpnr-ice40" "nextpnr-ecp5"}; + "nextpnr-ice40" -> ".asc"; + "nextpnr-ecp5" -> ".config"; + ".asc" -> {icetime icepack}; + icepack -> "bit-ice40"; + "bit-ice40" -> iceprog; + iceprog -> ice40; + ".config" -> eccpack; + eccpack -> "bit-ecp5"; + {rank = same; GHDL ; "ghdl-yosys-plugin"; Yosys;} +} diff --git a/docs/images/openflow.svg b/docs/images/openflow.svg new file mode 100644 index 00000000..3ee12ac3 --- /dev/null +++ b/docs/images/openflow.svg @@ -0,0 +1,224 @@ + + + + + + +openflow + + + +GHDL + +GHDL + + + +ghdl-yosys-plugin + +ghdl-yosys-plugin + + + +GHDL->ghdl-yosys-plugin + + + + + +Yosys + +Yosys + + + +ghdl-yosys-plugin->Yosys + + + + + +.json + +.json + + + +Yosys->.json + + + + + +nextpnr-ice40 + +nextpnr-ice40 + + + +.asc + +.asc + + + +nextpnr-ice40->.asc + + + + + +nextpnr-ecp5 + +nextpnr-ecp5 + + + +.config + +.config + + + +nextpnr-ecp5->.config + + + + + +icetime + +icetime + + + +icepack + +icepack + + + +bit-ice40 + +.bit + + + +icepack->bit-ice40 + + + + + +iceprog + +iceprog + + + +ice40 + + + + +ice40 + + + +iceprog->ice40 + + + + + +eccpack + +eccpack + + + +bit-ecp5 + +.bit + + + +eccpack->bit-ecp5 + + + + + +VHDL + + + +VHDL + + + +VHDL->GHDL + + + + + +VHDL->ghdl-yosys-plugin + + + + + +Verilog + + + +Verilog + + + +Verilog->Yosys + + + + + +bit-ice40->iceprog + + + + + +.json->nextpnr-ice40 + + + + + +.json->nextpnr-ecp5 + + + + + +.asc->icetime + + + + + +.asc->icepack + + + + + +.config->eccpack + + + + + diff --git a/docs/tools.rst b/docs/tools.rst index 8839ac2d..1f2ee57b 100644 --- a/docs/tools.rst +++ b/docs/tools.rst @@ -94,6 +94,10 @@ Openflow Openflow is the combination of different Free/Libre and Open Source (FLOSS) tools: +.. image:: images/openflow.svg + :width: 70% + :align: center + * Yosys for synthesis, with ghdl-yosys-plugin for VHDL support. * nextpnr in its ice40 and ecp5 versions. * Projects icestorm and Trellis. From 29ed35a312a7f3586b01c70ac8855d0e5a78f2f7 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Mon, 2 Sep 2024 21:52:16 -0300 Subject: [PATCH 230/254] ci: modified to trigger docs when the branch is main or dev --- .github/workflows/docs.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 1db219c9..8d26b1af 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -5,7 +5,8 @@ on: paths: - 'docs/**' branches: -# - main + - main + - dev jobs: docs: From 9aa363f3b3b23d89d7947f5c228bed9b287e8173 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Mon, 2 Sep 2024 23:51:08 -0300 Subject: [PATCH 231/254] ci: add windows tests --- .github/workflows/test.yml | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 40699986..0b33ba57 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -4,7 +4,7 @@ on: push: jobs: - test: + test-lin: strategy: matrix: os: ['ubuntu'] @@ -25,3 +25,20 @@ jobs: run: pip install . && pip install pytest - name: Run tests run: source tests/mocks/source-me.sh && make test + test-win: + runs-on: windows-latest + name: windows | 3.12 + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + submodules: true + fetch-depth: 0 + - name: Set up Python 3.12 + uses: actions/setup-python@v5 + with: + python-version: 3.12 + - name: Install dependencies + run: pip install . && pip install pytest + - name: Run tests + run: make test From 6dc528a4e724b398fd734cabf7c420fd66b3280e Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Mon, 2 Sep 2024 23:59:09 -0300 Subject: [PATCH 232/254] ci: add to run regress.sh --- .github/workflows/test.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0b33ba57..d45d0543 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -24,7 +24,9 @@ jobs: - name: Install dependencies run: pip install . && pip install pytest - name: Run tests - run: source tests/mocks/source-me.sh && make test + run: | + source tests/mocks/source-me.sh && make test + cd examples/projects && bash regress.sh test-win: runs-on: windows-latest name: windows | 3.12 From 48af8b24979e93d26381a4db9c3c26791b4f6831 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Wed, 4 Sep 2024 20:09:23 -0300 Subject: [PATCH 233/254] ci: modified when to trigger the docs actions --- .github/workflows/docs.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 8d26b1af..bf55bcfb 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -4,6 +4,7 @@ on: push: paths: - 'docs/**' + - 'pyfpga/project.py' branches: - main - dev From fcd51f6e5c1ea741d10a956292a5de3ac90f48b2 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Wed, 4 Sep 2024 20:25:17 -0300 Subject: [PATCH 234/254] ci: modified to run ubuntu and windows tests using the same job --- .github/workflows/test.yml | 39 ++++++++++++++------------------------ tests/test_tools.py | 4 ++-- 2 files changed, 16 insertions(+), 27 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d45d0543..12e54f2c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -4,19 +4,25 @@ on: push: jobs: - test-lin: + test: strategy: matrix: - os: ['ubuntu'] - pyver: ['3.8', '3.9', '3.10', '3.11', '3.12'] + os: [ubuntu, windows] + pyver: [3.8, 3.9, 3.10, 3.11, 3.12] + exclude: + - os: windows + pyver: 3.8 + - os: windows + pyver: 3.9 + - os: windows + pyver: 3.10 + - os: windows + pyver: 3.11 runs-on: ${{ matrix.os }}-latest name: ${{ matrix.os }} | ${{ matrix.pyver }} steps: - name: Checkout repository uses: actions/checkout@v4 - with: - submodules: true - fetch-depth: 0 - name: Set up Python ${{ matrix.pyver }} uses: actions/setup-python@v5 with: @@ -25,22 +31,5 @@ jobs: run: pip install . && pip install pytest - name: Run tests run: | - source tests/mocks/source-me.sh && make test - cd examples/projects && bash regress.sh - test-win: - runs-on: windows-latest - name: windows | 3.12 - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - submodules: true - fetch-depth: 0 - - name: Set up Python 3.12 - uses: actions/setup-python@v5 - with: - python-version: 3.12 - - name: Install dependencies - run: pip install . && pip install pytest - - name: Run tests - run: make test + make test + cd examples/projects && bash regress.sh --notool diff --git a/tests/test_tools.py b/tests/test_tools.py index ad28632a..98f00055 100644 --- a/tests/test_tools.py +++ b/tests/test_tools.py @@ -85,9 +85,9 @@ def generate(tool, part): prj.add_hook('postbit', 'HOOK16') try: prj.make() - except Exception: + except RuntimeError: pass try: prj.prog() - except Exception: + except RuntimeError: pass From 1a0ff918b0f63b58c52193aeed798f78aa4c7c00 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sat, 7 Sep 2024 14:22:48 -0300 Subject: [PATCH 235/254] Add the --notool option, to avoid errors when tools are not available --- examples/projects/diamond.py | 16 +++++++++++----- examples/projects/ise.py | 16 +++++++++++----- examples/projects/libero.py | 16 +++++++++++----- examples/projects/openflow.py | 16 +++++++++++----- examples/projects/quartus.py | 16 +++++++++++----- examples/projects/regress.sh | 25 +++++++++++++++++++++++-- examples/projects/vivado.py | 16 +++++++++++----- 7 files changed, 89 insertions(+), 32 deletions(-) diff --git a/examples/projects/diamond.py b/examples/projects/diamond.py index 0128d7c3..464be7ff 100644 --- a/examples/projects/diamond.py +++ b/examples/projects/diamond.py @@ -15,6 +15,9 @@ parser.add_argument( '--action', choices=['make', 'prog', 'all'], default='make' ) +parser.add_argument( + '--notool', action='store_true' +) args = parser.parse_args() prj = Diamond(odir=f'results/diamond/{args.source}/{args.board}') @@ -43,8 +46,11 @@ prj.set_top('Top') -if args.action in ['make', 'all']: - prj.make() - -if args.action in ['prog', 'all']: - prj.prog() +try: + if args.action in ['make', 'all']: + prj.make() + if args.action in ['prog', 'all']: + prj.prog() +except RuntimeError: + if not args.notool: + raise diff --git a/examples/projects/ise.py b/examples/projects/ise.py index 279a5db0..1955d34d 100644 --- a/examples/projects/ise.py +++ b/examples/projects/ise.py @@ -15,6 +15,9 @@ parser.add_argument( '--action', choices=['make', 'prog', 'all'], default='make' ) +parser.add_argument( + '--notool', action='store_true' +) args = parser.parse_args() prj = Ise(odir=f'results/ise/{args.source}/{args.board}') @@ -45,8 +48,11 @@ prj.set_top('Top') -if args.action in ['make', 'all']: - prj.make() - -if args.action in ['prog', 'all']: - prj.prog() +try: + if args.action in ['make', 'all']: + prj.make() + if args.action in ['prog', 'all']: + prj.prog() +except RuntimeError: + if not args.notool: + raise diff --git a/examples/projects/libero.py b/examples/projects/libero.py index dd2c5861..8bb94b7a 100644 --- a/examples/projects/libero.py +++ b/examples/projects/libero.py @@ -14,6 +14,9 @@ parser.add_argument( '--action', choices=['make', 'prog', 'all'], default='make' ) +parser.add_argument( + '--notool', action='store_true' +) args = parser.parse_args() prj = Libero(odir=f'results/libero/{args.source}/{args.board}') @@ -43,8 +46,11 @@ prj.set_top('Top') -if args.action in ['make', 'all']: - prj.make() - -if args.action in ['prog', 'all']: - prj.prog() +try: + if args.action in ['make', 'all']: + prj.make() + if args.action in ['prog', 'all']: + prj.prog() +except RuntimeError: + if not args.notool: + raise diff --git a/examples/projects/openflow.py b/examples/projects/openflow.py index 56a6b2ab..98e2e999 100644 --- a/examples/projects/openflow.py +++ b/examples/projects/openflow.py @@ -16,6 +16,9 @@ parser.add_argument( '--action', choices=['make', 'prog', 'all'], default='make' ) +parser.add_argument( + '--notool', action='store_true' +) args = parser.parse_args() prj = Openflow(odir=f'results/openflow/{args.source}/{args.board}') @@ -59,8 +62,11 @@ prj.set_top('Top') -if args.action in ['make', 'all']: - prj.make() - -if args.action in ['prog', 'all']: - prj.prog() +try: + if args.action in ['make', 'all']: + prj.make() + if args.action in ['prog', 'all']: + prj.prog() +except RuntimeError: + if not args.notool: + raise diff --git a/examples/projects/quartus.py b/examples/projects/quartus.py index eb7e889e..258fa8e6 100644 --- a/examples/projects/quartus.py +++ b/examples/projects/quartus.py @@ -15,6 +15,9 @@ parser.add_argument( '--action', choices=['make', 'prog', 'all'], default='make' ) +parser.add_argument( + '--notool', action='store_true' +) args = parser.parse_args() prj = Quartus(odir=f'results/quartus/{args.source}/{args.board}') @@ -44,8 +47,11 @@ prj.set_top('Top') -if args.action in ['make', 'all']: - prj.make() - -if args.action in ['prog', 'all']: - prj.prog() +try: + if args.action in ['make', 'all']: + prj.make() + if args.action in ['prog', 'all']: + prj.prog() +except RuntimeError: + if not args.notool: + raise diff --git a/examples/projects/regress.sh b/examples/projects/regress.sh index 45b7d848..a260b52e 100644 --- a/examples/projects/regress.sh +++ b/examples/projects/regress.sh @@ -13,7 +13,24 @@ TOOLS["vivado"]="zybo arty" SOURCES=("vlog" "vhdl" "slog") -SPECIFIED_TOOL=$1 +SPECIFIED_TOOL="" +NOTOOL=false +while [[ "$#" -gt 0 ]]; do + case $1 in + --tool) + SPECIFIED_TOOL="$2" + shift 2 + ;; + --notool) + NOTOOL=true + shift + ;; + *) + echo "Invalid option: $1" + exit 1 + ;; + esac +done for TOOL in "${!TOOLS[@]}"; do if [[ -n "$SPECIFIED_TOOL" && "$TOOL" != "$SPECIFIED_TOOL" ]]; then @@ -26,7 +43,11 @@ for TOOL in "${!TOOLS[@]}"; do continue fi echo "> $TOOL - $BOARD - $SOURCE" - python3 $TOOL.py --board $BOARD --source $SOURCE + if [[ "$NOTOOL" == true ]]; then + python3 $TOOL.py --board $BOARD --source $SOURCE --notool + else + python3 $TOOL.py --board $BOARD --source $SOURCE + fi done done done diff --git a/examples/projects/vivado.py b/examples/projects/vivado.py index af497b91..33fba3f3 100644 --- a/examples/projects/vivado.py +++ b/examples/projects/vivado.py @@ -15,6 +15,9 @@ parser.add_argument( '--action', choices=['make', 'prog', 'all'], default='make' ) +parser.add_argument( + '--notool', action='store_true' +) args = parser.parse_args() prj = Vivado(odir=f'results/vivado/{args.source}/{args.board}') @@ -50,8 +53,11 @@ prj.set_top('Top') -if args.action in ['make', 'all']: - prj.make() - -if args.action in ['prog', 'all']: - prj.prog() +try: + if args.action in ['make', 'all']: + prj.make() + if args.action in ['prog', 'all']: + prj.prog() +except RuntimeError: + if not args.notool: + raise From 8c061c1fe94672806b2f5efabdee92573234f4c9 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sat, 7 Sep 2024 14:27:55 -0300 Subject: [PATCH 236/254] ci: fix issue specifyng the Python version --- .github/workflows/test.yml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 12e54f2c..076ec499 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -7,17 +7,17 @@ jobs: test: strategy: matrix: - os: [ubuntu, windows] - pyver: [3.8, 3.9, 3.10, 3.11, 3.12] + os: ['ubuntu', 'windows'] + pyver: ['3.8', '3.9', '3.10', '3.11', '3.12'] exclude: - - os: windows - pyver: 3.8 - - os: windows - pyver: 3.9 - - os: windows - pyver: 3.10 - - os: windows - pyver: 3.11 + - os: 'windows' + pyver: '3.8' + - os: 'windows' + pyver: '3.9' + - os: 'windows' + pyver: '3.10' + - os: 'windows' + pyver: '3.11' runs-on: ${{ matrix.os }}-latest name: ${{ matrix.os }} | ${{ matrix.pyver }} steps: From e313901c7cc51437f4f23eb2ab328a2ef0e847ef Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sat, 7 Sep 2024 21:54:07 -0300 Subject: [PATCH 237/254] tests: checking that paths are posix-like --- tests/test_data.py | 77 ++++++++++++++++++++++++---------------------- 1 file changed, 41 insertions(+), 36 deletions(-) diff --git a/tests/test_data.py b/tests/test_data.py index 49b0645b..d4d8d0f8 100644 --- a/tests/test_data.py +++ b/tests/test_data.py @@ -57,24 +57,24 @@ Path(tdir / 'fakedata/cons/par.xdc').resolve().as_posix(): {} }, 'params': { - 'PAR1': 'VAL1', - 'PAR2': 'VAL2', - 'PAR3': 'VAL3' + 'PAR1': 'VAL01', + 'PAR2': 'VAL02', + 'PAR3': 'VAL03' }, 'defines': { - 'DEF1': 'VAL1', - 'DEF2': 'VAL2', - 'DEF3': 'VAL3' + 'DEF1': 'VAL01', + 'DEF2': 'VAL02', + 'DEF3': 'VAL03' }, 'hooks': { - 'precfg': ['CMD1', 'CMD2'], - 'postcfg': ['CMD1', 'CMD2'], - 'presyn': ['CMD1', 'CMD2'], - 'postsyn': ['CMD1', 'CMD2'], - 'prepar': ['CMD1', 'CMD2'], - 'postpar': ['CMD1', 'CMD2'], - 'prebit': ['CMD1', 'CMD2'], - 'postbit': ['CMD1', 'CMD2'] + 'precfg': ['CMD01', 'CMD02'], + 'postcfg': ['CMD03', 'CMD04'], + 'presyn': ['CMD05', 'CMD06'], + 'postsyn': ['CMD07', 'CMD08'], + 'prepar': ['CMD09', 'CMD10'], + 'postpar': ['CMD11', 'CMD12'], + 'prebit': ['CMD13', 'CMD14'], + 'postbit': ['CMD15', 'CMD16'] } } @@ -92,26 +92,31 @@ def test_data(): prj.add_cons(str(tdir / 'fakedata/cons/all.xdc')) prj.add_cons(str(tdir / 'fakedata/cons/syn.xdc')) prj.add_cons(str(tdir / 'fakedata/cons/par.xdc')) - prj.add_param('PAR1', 'VAL1') - prj.add_param('PAR2', 'VAL2') - prj.add_param('PAR3', 'VAL3') - prj.add_define('DEF1', 'VAL1') - prj.add_define('DEF2', 'VAL2') - prj.add_define('DEF3', 'VAL3') - prj.add_hook('precfg', 'CMD1') - prj.add_hook('precfg', 'CMD2') - prj.add_hook('postcfg', 'CMD1') - prj.add_hook('postcfg', 'CMD2') - prj.add_hook('presyn', 'CMD1') - prj.add_hook('presyn', 'CMD2') - prj.add_hook('postsyn', 'CMD1') - prj.add_hook('postsyn', 'CMD2') - prj.add_hook('prepar', 'CMD1') - prj.add_hook('prepar', 'CMD2') - prj.add_hook('postpar', 'CMD1') - prj.add_hook('postpar', 'CMD2') - prj.add_hook('prebit', 'CMD1') - prj.add_hook('prebit', 'CMD2') - prj.add_hook('postbit', 'CMD1') - prj.add_hook('postbit', 'CMD2') + prj.add_param('PAR1', 'VAL01') + prj.add_param('PAR2', 'VAL02') + prj.add_param('PAR3', 'VAL03') + prj.add_define('DEF1', 'VAL01') + prj.add_define('DEF2', 'VAL02') + prj.add_define('DEF3', 'VAL03') + prj.add_hook('precfg', 'CMD01') + prj.add_hook('precfg', 'CMD02') + prj.add_hook('postcfg', 'CMD03') + prj.add_hook('postcfg', 'CMD04') + prj.add_hook('presyn', 'CMD05') + prj.add_hook('presyn', 'CMD06') + prj.add_hook('postsyn', 'CMD07') + prj.add_hook('postsyn', 'CMD08') + prj.add_hook('prepar', 'CMD09') + prj.add_hook('prepar', 'CMD10') + prj.add_hook('postpar', 'CMD11') + prj.add_hook('postpar', 'CMD12') + prj.add_hook('prebit', 'CMD13') + prj.add_hook('prebit', 'CMD14') + prj.add_hook('postbit', 'CMD15') + prj.add_hook('postbit', 'CMD16') assert prj.data == pattern, 'ERROR: unexpected data' + paths = prj.data['includes'] + list(prj.data['files'].keys()) + for path in paths: + assert '\\' not in path, ( + f"'{path}' contains a '\\' character, which is not allowed." + ) From d94057e431663c5280aedc38de847397d667d907 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sat, 7 Sep 2024 22:00:35 -0300 Subject: [PATCH 238/254] ci: moved to run regress from test.yml to the Makefile --- .github/workflows/test.yml | 4 +--- Makefile | 1 + 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 076ec499..b04f05be 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -30,6 +30,4 @@ jobs: - name: Install dependencies run: pip install . && pip install pytest - name: Run tests - run: | - make test - cd examples/projects && bash regress.sh --notool + run: make test diff --git a/Makefile b/Makefile index 2f2402c4..7893bb51 100644 --- a/Makefile +++ b/Makefile @@ -14,6 +14,7 @@ lint: test: pytest + cd examples/projects && bash regress.sh --notool clean: py3clean . From d6b773b334f6b3a705528312f0e141ae77b465c0 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sat, 9 Nov 2024 09:23:57 -0300 Subject: [PATCH 239/254] prj2bit: improve ERROR messages --- pyfpga/helpers/prj2bit.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyfpga/helpers/prj2bit.py b/pyfpga/helpers/prj2bit.py index 1124e452..ea633b6b 100644 --- a/pyfpga/helpers/prj2bit.py +++ b/pyfpga/helpers/prj2bit.py @@ -63,7 +63,7 @@ def main(): prjfile = Path(args.prjfile) if not prjfile.exists(): - sys.exit('file not found.') + sys.exit(f'ERROR: {prjfile} file not found.') directory = prjfile.parent base_name = prjfile.stem @@ -72,9 +72,9 @@ def main(): tool = '' if extension in tool_per_ext: tool = tool_per_ext[extension] - print(f'* {tool} project file found.') + print(f'INFO: {tool} project file found.') else: - sys.exit('Unknown project file extension') + sys.exit(f'ERROR: unknown project file extension ({extension})') # ------------------------------------------------------------------------- # Solving with PyFPGA From 5c5c4f99500481db7b9e2b6a1b28f84f745e96b6 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sun, 10 Nov 2024 11:47:08 -0300 Subject: [PATCH 240/254] Add constraints for the mpfs-disco-kit board --- examples/sources/cons/mpfs-disco-kit/clk.pdc | 1 + examples/sources/cons/mpfs-disco-kit/led.pdc | 1 + examples/sources/cons/mpfs-disco-kit/timing.sdc | 1 + 3 files changed, 3 insertions(+) create mode 100644 examples/sources/cons/mpfs-disco-kit/clk.pdc create mode 100644 examples/sources/cons/mpfs-disco-kit/led.pdc create mode 100644 examples/sources/cons/mpfs-disco-kit/timing.sdc diff --git a/examples/sources/cons/mpfs-disco-kit/clk.pdc b/examples/sources/cons/mpfs-disco-kit/clk.pdc new file mode 100644 index 00000000..61f7332f --- /dev/null +++ b/examples/sources/cons/mpfs-disco-kit/clk.pdc @@ -0,0 +1 @@ +set_io -port_name clk_i -DIRECTION INPUT -pin_name R18 -fixed true diff --git a/examples/sources/cons/mpfs-disco-kit/led.pdc b/examples/sources/cons/mpfs-disco-kit/led.pdc new file mode 100644 index 00000000..678d3bf4 --- /dev/null +++ b/examples/sources/cons/mpfs-disco-kit/led.pdc @@ -0,0 +1 @@ +set_io -port_name led_o -DIRECTION OUTPUT -pin_name T18 -fixed true diff --git a/examples/sources/cons/mpfs-disco-kit/timing.sdc b/examples/sources/cons/mpfs-disco-kit/timing.sdc new file mode 100644 index 00000000..00363616 --- /dev/null +++ b/examples/sources/cons/mpfs-disco-kit/timing.sdc @@ -0,0 +1 @@ +create_clock -period 20 [get_ports clk_i] From 25e683c7fa9c4c7d19c47bf99f5aed94c3efd249 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sun, 10 Nov 2024 12:06:53 -0300 Subject: [PATCH 241/254] Fix support for PolarFireSoC devices --- examples/projects/libero.py | 9 ++++++++- pyfpga/libero.py | 29 +++++++++++++++++++++++++---- pyfpga/templates/libero.jinja | 4 ++-- tests/test_part.py | 3 ++- 4 files changed, 37 insertions(+), 8 deletions(-) diff --git a/examples/projects/libero.py b/examples/projects/libero.py index 8bb94b7a..3498a6e3 100644 --- a/examples/projects/libero.py +++ b/examples/projects/libero.py @@ -6,7 +6,7 @@ parser = argparse.ArgumentParser() parser.add_argument( - '--board', choices=['maker'], default='maker' + '--board', choices=['mpfs-disco-kit', 'maker'], default='mpfs-disco-kit' ) parser.add_argument( '--source', choices=['vlog', 'vhdl', 'slog'], default='vlog' @@ -21,6 +21,13 @@ prj = Libero(odir=f'results/libero/{args.source}/{args.board}') + +if args.board == 'mpfs-disco-kit': + prj.set_part('MPFS095T-1-FCSG325E') + prj.add_param('FREQ', '50000000') + prj.add_cons('../sources/cons/mpfs-disco-kit/timing.sdc') + prj.add_cons('../sources/cons/mpfs-disco-kit/clk.pdc') + prj.add_cons('../sources/cons/mpfs-disco-kit/led.pdc') if args.board == 'maker': prj.set_part('m2s010-1-tq144') prj.add_param('FREQ', '125000000') diff --git a/pyfpga/libero.py b/pyfpga/libero.py index 2f1e4bc4..6f1bda0f 100644 --- a/pyfpga/libero.py +++ b/pyfpga/libero.py @@ -31,9 +31,10 @@ def _make_custom(self): self.data['device'] = info['device'] self.data['speed'] = info['speed'] self.data['package'] = info['package'] + self.data['prange'] = info['prange'] def _prog_custom(self): - raise NotImplementedError('Libero programming not supported') + raise NotImplementedError('Libero programming not supported yet') # pylint: disable=duplicate-code @@ -42,7 +43,7 @@ def get_info(part): """Get info about the FPGA part. :param part: the FPGA part as specified by the tool - :returns: a dictionary with the keys family, device, speed and package + :returns: a dict with the keys family, device, speed, package and prange """ part = part.lower() # Looking for the family @@ -51,6 +52,7 @@ def get_info(part): r'm2s': 'SmartFusion2', r'm2gl': 'Igloo2', r'rt4g': 'RTG4', + r'mpfs': 'PolarFireSoC', r'mpf': 'PolarFire', r'a2f': 'SmartFusion', r'afs': 'Fusion', @@ -65,10 +67,11 @@ def get_info(part): if re.match(key, part): family = value break - # Looking for the device and package + # Looking for the device, speed and package device = None speed = None package = None + prange = None aux = part.split('-') if len(aux) == 2: device = aux[0] @@ -86,7 +89,25 @@ def get_info(part): raise ValueError( 'Part must be DEVICE-SPEED-PACKAGE or DEVICE-PACKAGE' ) + # Looking for a part_range + pranges = { + 'c': 'COM', + 'e': 'EXT', + 'i': 'IND', + 'm': 'MIL', + 't1': 'TGrade1' + } + prange = 'COM' + for suffix, name in pranges.items(): + if package.endswith(suffix): + package = package[:-len(suffix)] + prange = name + break # Finish return { - 'family': family, 'device': device, 'speed': speed, 'package': package + 'family': family, + 'device': device, + 'speed': speed, + 'package': package, + 'prange': prange } diff --git a/pyfpga/templates/libero.jinja b/pyfpga/templates/libero.jinja index c64d8a5d..499a9230 100644 --- a/pyfpga/templates/libero.jinja +++ b/pyfpga/templates/libero.jinja @@ -9,8 +9,8 @@ {% if 'cfg' in steps %}# Project configuration ------------------------------------------------------- if { [ file exists {{ project }} ] } { file delete -force -- {{ project }} } -new_project -name {{ project }} -location {libero} -hdl {VERILOG} -family {SmartFusion2} -set_device -family {{ family }} -die {{ device }} -package {{ package }} -speed {{ speed }} +new_project -name {{ project }} -location libero -hdl VERILOG -family {{ family }} +set_device -family {{ family }} -die {{ device }} -package {{ package}} -speed {{ speed }} -part_range {{ prange }} {% if hooks %}{{ hooks.precfg | join('\n') }}{% endif %} diff --git a/tests/test_part.py b/tests/test_part.py index 86087946..5319e3dc 100644 --- a/tests/test_part.py +++ b/tests/test_part.py @@ -19,7 +19,8 @@ def test_libero(): 'family': 'SmartFusion2', 'device': 'm2s010', 'speed': '-1', - 'package': 'tq144' + 'package': 'tq144', + 'prange': 'COM' } assert get_info_libero('m2s010-1-tq144') == info assert get_info_libero('m2s010-tq144-1') == info From a90c1f4e11f084791a21835a366587e0f9a65b95 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sun, 10 Nov 2024 22:09:48 -0300 Subject: [PATCH 242/254] Improve PART format parsing (ise, libero and openflow) --- docs/tools.rst | 26 +++++++++++ pyfpga/ise.py | 20 ++++---- pyfpga/libero.py | 20 ++++---- pyfpga/openflow.py | 15 ++++-- pyfpga/templates/ise.jinja | 2 +- tests/test_part.py | 96 ++++++++++++++++++++++++++++++++++---- 6 files changed, 147 insertions(+), 32 deletions(-) diff --git a/docs/tools.rst b/docs/tools.rst index 1f2ee57b..38da3ef3 100644 --- a/docs/tools.rst +++ b/docs/tools.rst @@ -70,6 +70,13 @@ Example: prj = Ise() +Valid PART formats: + +.. code:: + + -- + -- + Libero ------ @@ -87,6 +94,19 @@ Example: prj = Libero() +Valid PART formats: + +.. code:: + + - + - + -- + -- + - + - + -- + -- + Openflow -------- @@ -116,6 +136,12 @@ Example: prj = Openflow() +Valid PART formats: + +.. code:: + + - + Quartus ------- diff --git a/pyfpga/ise.py b/pyfpga/ise.py index 03eb8707..5ca52f8a 100644 --- a/pyfpga/ise.py +++ b/pyfpga/ise.py @@ -43,9 +43,9 @@ def get_info(part): """Get info about the FPGA part. :param part: the FPGA part as specified by the tool - :returns: a dictionary with the keys family, device, speed and package + :returns: a dict with the keys family, device, speed and package """ - part = part.lower() + part = part.lower().replace(' ', '') # Looking for the family family = None families = { @@ -71,7 +71,7 @@ def get_info(part): if re.match(key, part): family = value break - # Looking for the device, package and speed + # Looking for the other values device = None speed = None package = None @@ -79,16 +79,18 @@ def get_info(part): if len(aux) == 3: device = aux[0] if len(aux[1]) < len(aux[2]): - speed = aux[1] + speed = f'-{aux[1]}' package = aux[2] else: - speed = aux[2] + speed = f'-{aux[2]}' package = aux[1] else: - raise ValueError( - 'Part must be DEVICE-SPEED-PACKAGE or DEVICE-PACKAGE-SPEED' - ) + valid = 'DEVICE-SPEED-PACKAGE or DEVICE-PACKAGE-SPEED' + raise ValueError(f'Invalid PART format ({valid})') # Finish return { - 'family': family, 'device': device, 'speed': speed, 'package': package + 'family': family, + 'device': device, + 'speed': speed, + 'package': package } diff --git a/pyfpga/libero.py b/pyfpga/libero.py index 6f1bda0f..e0ab20df 100644 --- a/pyfpga/libero.py +++ b/pyfpga/libero.py @@ -14,7 +14,7 @@ class Libero(Project): - """Class to support Libero.""" + """Class to support Libero projects.""" def _configure(self): tool = 'libero' @@ -45,12 +45,12 @@ def get_info(part): :param part: the FPGA part as specified by the tool :returns: a dict with the keys family, device, speed, package and prange """ - part = part.lower() + part = part.lower().replace(' ', '') # Looking for the family family = None families = { r'm2s': 'SmartFusion2', - r'm2gl': 'Igloo2', + r'm2gl': 'IGLOO2', r'rt4g': 'RTG4', r'mpfs': 'PolarFireSoC', r'mpf': 'PolarFire', @@ -67,7 +67,7 @@ def get_info(part): if re.match(key, part): family = value break - # Looking for the device, speed and package + # Looking for the other values device = None speed = None package = None @@ -75,8 +75,12 @@ def get_info(part): aux = part.split('-') if len(aux) == 2: device = aux[0] - speed = 'STD' package = aux[1] + if package[0].isdigit(): + speed = f'-{package[0]}' + package = package[1:] + else: + speed = 'STD' elif len(aux) == 3: device = aux[0] if len(aux[1]) < len(aux[2]): @@ -86,10 +90,8 @@ def get_info(part): speed = f'-{aux[2]}' package = aux[1] else: - raise ValueError( - 'Part must be DEVICE-SPEED-PACKAGE or DEVICE-PACKAGE' - ) - # Looking for a part_range + valid = 'DEVICE-[SPEED][-]PACKAGE[PRANGE][-SPEED]' + raise ValueError(f'Invalid PART format ({valid})') pranges = { 'c': 'COM', 'e': 'EXT', diff --git a/pyfpga/openflow.py b/pyfpga/openflow.py index 07cefac4..25ab9fc4 100644 --- a/pyfpga/openflow.py +++ b/pyfpga/openflow.py @@ -34,9 +34,9 @@ def get_info(part): """Get info about the FPGA part. :param part: the FPGA part as specified by the tool - :returns: a dictionary with the keys family, device and package + :returns: a dict with the keys family, device and package """ - part = part.lower() + part = part.lower().replace(' ', '') # Looking for the family family = None families = [ @@ -62,7 +62,7 @@ def get_info(part): ] if part.startswith(tuple(families)): family = 'ecp5' - # Looking for the device and package + # Looking for the other values device = None package = None aux = part.split('-') @@ -73,11 +73,16 @@ def get_info(part): device = f'{aux[0]}-{aux[1]}' package = aux[2] else: - raise ValueError('Part must be DEVICE-PACKAGE') + valid = 'DEVICE-PACKAGE' + raise ValueError(f'Invalid PART format ({valid})') if family in ['lp4k', 'hx4k']: # See http://www.clifford.at/icestorm device = device.replace('4', '8') package += ":4k" if family == 'ecp5': package = package.upper() # Finish - return {'family': family, 'device': device, 'package': package} + return { + 'family': family, + 'device': device, + 'package': package + } diff --git a/pyfpga/templates/ise.jinja b/pyfpga/templates/ise.jinja index c90f2d5e..54803e9b 100644 --- a/pyfpga/templates/ise.jinja +++ b/pyfpga/templates/ise.jinja @@ -13,7 +13,7 @@ project new {{ project }}.xise project set family {{ family }} project set device {{ device }} project set package {{ package }} -project set speed -{{ speed }} +project set speed {{ speed }} {% if hooks %}{{ hooks.precfg | join('\n') }}{% endif %} diff --git a/tests/test_part.py b/tests/test_part.py index 5319e3dc..1d155aa8 100644 --- a/tests/test_part.py +++ b/tests/test_part.py @@ -7,25 +7,105 @@ def test_ise(): info = { 'family': 'kintex7', 'device': 'xc7k160t', - 'speed': '3', + 'speed': '-3', 'package': 'fbg484' } assert get_info_ise('xc7k160t-3-fbg484') == info assert get_info_ise('xc7k160t-fbg484-3') == info + info['speed'] = '-3l' + assert get_info_ise('xc7k160t-3L-fbg484') == info + assert get_info_ise('xc7k160t-fbg484-3L') == info def test_libero(): info = { 'family': 'SmartFusion2', - 'device': 'm2s010', - 'speed': '-1', - 'package': 'tq144', + 'device': 'm2s025t', + 'speed': 'STD', + 'package': 'fg484', 'prange': 'COM' } - assert get_info_libero('m2s010-1-tq144') == info - assert get_info_libero('m2s010-tq144-1') == info - info['speed'] = 'STD' - assert get_info_libero('m2s010-tq144') == info + assert get_info_libero('M2S025T-FG484') == info + info['prange'] = 'IND' + assert get_info_libero('M2S025T-FG484I') == info + info['speed'] = '-1' + info['prange'] = 'COM' + assert get_info_libero('M2S025T-1FG484') == info + assert get_info_libero('M2S025T-1-FG484') == info + assert get_info_libero('M2S025T-FG484-1') == info + info['prange'] = 'IND' + assert get_info_libero('M2S025T-1FG484I') == info + assert get_info_libero('M2S025T-1-FG484I') == info + assert get_info_libero('M2S025T-FG484I-1') == info + info['prange'] = 'MIL' + assert get_info_libero('M2S025T-1FG484M') == info + assert get_info_libero('M2S025T-1-FG484M') == info + assert get_info_libero('M2S025T-FG484M-1') == info + info = { + 'family': 'IGLOO2', + 'device': 'm2gl025', + 'speed': 'STD', + 'package': 'fg484', + 'prange': 'COM' + } + assert get_info_libero('M2GL025-FG484') == info + info['prange'] = 'IND' + assert get_info_libero('M2GL025-FG484I') == info + info['speed'] = '-1' + info['prange'] = 'COM' + assert get_info_libero('M2GL025-1FG484') == info + assert get_info_libero('M2GL025-1-FG484') == info + assert get_info_libero('M2GL025-FG484-1') == info + info['prange'] = 'IND' + assert get_info_libero('M2GL025-1FG484I') == info + assert get_info_libero('M2GL025-1-FG484I') == info + assert get_info_libero('M2GL025-FG484I-1') == info + info['prange'] = 'MIL' + assert get_info_libero('M2GL025-1FG484M') == info + assert get_info_libero('M2GL025-1-FG484M') == info + assert get_info_libero('M2GL025-FG484M-1') == info + info['prange'] = 'TGrade1' + assert get_info_libero('M2GL025-1FG484T1') == info + assert get_info_libero('M2GL025-1-FG484T1') == info + assert get_info_libero('M2GL025-FG484T1-1') == info + info = { + 'family': 'PolarFire', + 'device': 'mpf300ts_es', + 'speed': 'STD', + 'package': 'fg484', + 'prange': 'EXT' + } + assert get_info_libero('MPF300TS_ES-FG484E') == info + info['prange'] = 'IND' + assert get_info_libero('MPF300TS_ES-FG484I') == info + info['speed'] = '-1' + info['prange'] = 'EXT' + assert get_info_libero('MPF300TS_ES-1FG484E') == info + assert get_info_libero('MPF300TS_ES-1-FG484E') == info + assert get_info_libero('MPF300TS_ES-FG484E-1') == info + info['prange'] = 'IND' + assert get_info_libero('MPF300TS_ES-1FG484I') == info + assert get_info_libero('MPF300TS_ES-1-FG484I') == info + assert get_info_libero('MPF300TS_ES-FG484I-1') == info + info = { + 'family': 'PolarFireSoC', + 'device': 'mpfs025t', + 'speed': 'STD', + 'package': 'fcvg484', + 'prange': 'EXT' + } + assert get_info_libero('MPFS025T-FCVG484E') == info + info['prange'] = 'IND' + assert get_info_libero('MPFS025T-FCVG484I') == info + info['speed'] = '-1' + info['prange'] = 'EXT' + assert get_info_libero('MPFS025T-1FCVG484E') == info + assert get_info_libero('MPFS025T-1-FCVG484E') == info + assert get_info_libero('MPFS025T-FCVG484E-1') == info + info['prange'] = 'IND' + assert get_info_libero('MPFS025T-1FCVG484I') == info + assert get_info_libero('MPFS025T-1-FCVG484I') == info + assert get_info_libero('MPFS025T-FCVG484I-1') == info def test_openflow(): From 167fbeb84b0c05a1b2b7b83c62766387dcadb2a4 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Mon, 11 Nov 2024 23:46:59 -0300 Subject: [PATCH 243/254] Initial support for Libero programming --- pyfpga/libero.py | 12 +++++------- pyfpga/templates/libero-prog.jinja | 14 ++++++-------- pyfpga/templates/openflow-prog.jinja | 1 + 3 files changed, 12 insertions(+), 15 deletions(-) diff --git a/pyfpga/libero.py b/pyfpga/libero.py index e0ab20df..01800102 100644 --- a/pyfpga/libero.py +++ b/pyfpga/libero.py @@ -21,9 +21,9 @@ 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'] = None - self.conf['prog_cmd'] = None - self.conf['prog_ext'] = None + self.conf['prog_bit'] = 'pdd' + self.conf['prog_cmd'] = f'{tool} SCRIPT:{tool}-prog.tcl' + self.conf['prog_ext'] = 'tcl' def _make_custom(self): info = get_info(self.data.get('part', 'mpf100t-1-fcg484')) @@ -33,9 +33,6 @@ def _make_custom(self): self.data['package'] = info['package'] self.data['prange'] = info['prange'] - def _prog_custom(self): - raise NotImplementedError('Libero programming not supported yet') - # pylint: disable=duplicate-code @@ -97,7 +94,8 @@ def get_info(part): 'e': 'EXT', 'i': 'IND', 'm': 'MIL', - 't1': 'TGrade1' + 't1': 'TGrade1', + 't2': 'TGrade2' } prange = 'COM' for suffix, name in pranges.items(): diff --git a/pyfpga/templates/libero-prog.jinja b/pyfpga/templates/libero-prog.jinja index f2369645..df37dc0b 100644 --- a/pyfpga/templates/libero-prog.jinja +++ b/pyfpga/templates/libero-prog.jinja @@ -1,14 +1,12 @@ +{# # -# Copyright (C) 2015-2024 PyFPGA Project +# Copyright (C) 2024 PyFPGA Project # # SPDX-License-Identifier: GPL-3.0-or-later # #} -# open_project -file {$TEMPDIR/libero.prjx} -# run_tool -name {CONFIGURE_CHAIN} -script {$TEMPDIR/flashpro.tcl} -# run_tool -name {PROGRAMDEVICE} - -# set flashpro_programmer "configure_flashpro5_prg -vpump {ON} \ -# -clk_mode {free_running_clk} -programming_method {spi_slave} \ -# -force_freq {OFF} -freq {4000000}" +if { [catch {open_project {{ project }}/{{ project }}.prjx} ] } { + open_project {{ project }}.prjx +} +run_tool -name {PROGRAMDEVICE} diff --git a/pyfpga/templates/openflow-prog.jinja b/pyfpga/templates/openflow-prog.jinja index 7956b2b3..5055b505 100644 --- a/pyfpga/templates/openflow-prog.jinja +++ b/pyfpga/templates/openflow-prog.jinja @@ -1,3 +1,4 @@ +{# # # Copyright (C) 2020-2024 PyFPGA Project # From 113f78dcee91516a97c51bdc33219b1471df99ee Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sat, 23 Nov 2024 23:44:02 -0300 Subject: [PATCH 244/254] Modified to employ absolute paths when a bitstream is specified --- pyfpga/project.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyfpga/project.py b/pyfpga/project.py index 10bb57ea..b49b2174 100644 --- a/pyfpga/project.py +++ b/pyfpga/project.py @@ -253,6 +253,9 @@ def prog(self, bitstream=None, position=1): self.logger.info('Programming') if not bitstream: bitstream = f'{self.data["project"]}.{self.conf["prog_bit"]}' + else: + bitstream = Path(bitstream).resolve().as_posix() + self.data['bitstream'] = bitstream self._prog_custom() self._create_file(f'{self.conf["tool"]}-prog', self.conf['prog_ext']) self._run(self.conf['prog_cmd'], 'prog.log') From c4340abeefe16b8710bb949459daebb41229b690 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sat, 23 Nov 2024 23:44:46 -0300 Subject: [PATCH 245/254] Improved Libero programming support --- pyfpga/libero.py | 4 ++-- pyfpga/templates/libero-prog.jinja | 11 +++++++---- pyfpga/templates/libero.jinja | 1 + 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/pyfpga/libero.py b/pyfpga/libero.py index 01800102..b0661d14 100644 --- a/pyfpga/libero.py +++ b/pyfpga/libero.py @@ -21,8 +21,8 @@ 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'] = 'pdd' - self.conf['prog_cmd'] = f'{tool} SCRIPT:{tool}-prog.tcl' + self.conf['prog_bit'] = 'ppd' + self.conf['prog_cmd'] = f'FPExpress SCRIPT:{tool}-prog.tcl' self.conf['prog_ext'] = 'tcl' def _make_custom(self): diff --git a/pyfpga/templates/libero-prog.jinja b/pyfpga/templates/libero-prog.jinja index df37dc0b..3dba4fd8 100644 --- a/pyfpga/templates/libero-prog.jinja +++ b/pyfpga/templates/libero-prog.jinja @@ -6,7 +6,10 @@ # #} -if { [catch {open_project {{ project }}/{{ project }}.prjx} ] } { - open_project {{ project }}.prjx -} -run_tool -name {PROGRAMDEVICE} +file delete -force -- libero-prog +new_project -name libero -location libero-prog -mode single + +set_programming_file -file {{ bitstream }} +set_programming_action -action {PROGRAM} + +run_selected_actions diff --git a/pyfpga/templates/libero.jinja b/pyfpga/templates/libero.jinja index 499a9230..71b9afc1 100644 --- a/pyfpga/templates/libero.jinja +++ b/pyfpga/templates/libero.jinja @@ -113,6 +113,7 @@ run_tool -name {VERIFYTIMING} {% if hooks %}{{ hooks.prebit | join('\n') }}{% endif %} run_tool -name {GENERATEPROGRAMMINGFILE} +catch { file copy -force {{ project }}/designer/{{ top }}/{{ top }}.ppd {{ project }}.ppd } {% if hooks %}{{ hooks.postbit | join('\n') }}{% endif %} From aa24928256aafe93b0b5a0a5b17436cb961bd840 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sun, 24 Nov 2024 13:15:04 -0300 Subject: [PATCH 246/254] Add to check if the bitstream exists --- pyfpga/project.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyfpga/project.py b/pyfpga/project.py index b49b2174..b5d947ac 100644 --- a/pyfpga/project.py +++ b/pyfpga/project.py @@ -245,6 +245,7 @@ def prog(self, bitstream=None, position=1): :param position: position of the device in the JTAG chain :type position: str, optional :raises ValueError: for missing or wrong values + :raises FileNotFoundError: when bitstream is not found :raises RuntimeError: error running the needed underlying tool """ self.logger.debug('Executing prog') @@ -255,6 +256,8 @@ def prog(self, bitstream=None, position=1): bitstream = f'{self.data["project"]}.{self.conf["prog_bit"]}' else: bitstream = Path(bitstream).resolve().as_posix() + if not os.path.exists(bitstream): + raise FileNotFoundError(bitstream) self.data['bitstream'] = bitstream self._prog_custom() self._create_file(f'{self.conf["tool"]}-prog', self.conf['prog_ext']) From 3666ac73500dc8b742659b2954e1c4215a271e5e Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sun, 24 Nov 2024 13:52:51 -0300 Subject: [PATCH 247/254] ise: replace file rename by copy --- pyfpga/templates/ise.jinja | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyfpga/templates/ise.jinja b/pyfpga/templates/ise.jinja index 54803e9b..34577302 100644 --- a/pyfpga/templates/ise.jinja +++ b/pyfpga/templates/ise.jinja @@ -94,7 +94,7 @@ if { [process get "Place & Route" status] == "errors" } { exit 1 } process run "Generate Programming File" if { [process get "Generate Programming File" status] == "errors" } { exit 1 } -catch { file rename -force {{ top }}.bit {{ project }}.bit } +catch { file copy -force {{ top }}.bit {{ project }}.bit } {% if hooks %}{{ hooks.postbit | join('\n') }}{% endif %} From ef4ae95cd67c132253200d4209f2bda4092467d9 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sun, 24 Nov 2024 22:59:15 -0300 Subject: [PATCH 248/254] 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: From 52ffdb54640f6129430d3bde18e576e1c46bfc72 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Sat, 22 Feb 2025 11:30:18 -0300 Subject: [PATCH 249/254] Remove application guidelines from LICENSE --- LICENSE | 53 ----------------------------------------------------- 1 file changed, 53 deletions(-) diff --git a/LICENSE b/LICENSE index ef58c6a7..94a04532 100644 --- a/LICENSE +++ b/LICENSE @@ -619,56 +619,3 @@ Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - PyFPGA - Copyright (C) 2019 Rodrigo Alejandro Melo - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - PyFPGA Copyright (C) 2019 Rodrigo Alejandro Melo - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. From 8f15a47832d16a020fff3b0972bbb9ec0e864f81 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Mon, 3 Mar 2025 20:12:03 -0300 Subject: [PATCH 250/254] Moved project history from Changelog.md (removed) to the Misc section in the documentation --- CHANGELOG.md | 90 -------------------------------------------------- docs/index.rst | 1 + docs/misc.rst | 27 +++++++++++++++ 3 files changed, 28 insertions(+), 90 deletions(-) delete mode 100644 CHANGELOG.md create mode 100644 docs/misc.rst diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 6accc7ee..00000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,90 +0,0 @@ -# Changelog - -## [v0.2.0] - 2022-05-15 - -NOTE: this log is probably outdated/incomplete, but PyFPGA will be strongly rewritten/simplified. - -* Additional supported FPGA EDA Tools: - - GHDL --synth: synthesizes VHDL sources. - - ghdl-yosys-plugin: add VHDL support to Yosys. - - Openflow: Yosys + nextpnr + icestrom/prjtrellis. -* Added CLI helper utilities: - - hdl2bit: to go from FPGA design files to a bitstream. - - prj2bit: to deal with a vendor FPGA Project file. - - bitprog: to transfer a bitstream to a supported device -* Improves into the API classes: - - Added relative_to_script to the Project class constructor. - - Added the methods set_bitstream and clean. - - Simplified code related to absolute and relative directories. - - Exported some useful lists into the Tool class. -* Examples: - - Improved examples with the addition of command-line arguments. - - Added a GHDL example. - - Added examples using VHDL with Yosys (through `ghdl-yosys-plugin`). - - Added Openflow examples. - - Fixed a small problem into examples/multi/vhdl.py. -* Testing: - - Added some tests for the new CLI utilities. - - Added vendor project files to test prj2bit. - - Added a Makefile to run some examples as a test. - -## [v0.1.0] - 2020-02-29 - -NOTE: originally released on 2020-02-29 at https://gitlab.com/rodrigomelo9/pyfpga - -* FPGA Helpers switched to be a Python package which provides an API to manage projects -* Supported FPGA EDA Tools: - - ISE and Vivado from Xilinx: bitstream generation and transfer to an FPGA (ISE also support - SPI/BPI memories) - - Quartus Prime from Intel/Altera: bitstream generation and transfer to an FPGA - - Libero-SoC from Microchip/Microsemi: bitstream generation - - Yosys: generic synthesizer or combined with ISE/Vivado (implementation and bitstream - generation) -* The API implemented by the Project class provides: - - A constructor where the TOOL must be specified and an optional PROJECT NAME can be specified - (TOOL is used by default) - - A method to set the target device PART (default values per TOOL are provided) - - A method to add multiple HDL, Constraint and Tcl files to the project (in case of VHDL an - optional PACKAGE NAME can be specified) - - A method to specify the TOP-LEVEL (NAME or FILE) - - A method to specify a different OUTPUT directory (build by default) - - Methods to generate a bitstream and transfer it to a device (running the selected EDA Tool) - - The capability of specifying an optimization strategy (area, power or speed) when the - bitstream is generated - - A method to add Verilog Included File directories - - A method to specify generics/parameters values - - Methods to add Tcl commands in up to six different parts of the Flow (workaround for not yet - implemented features) - - Optional logging capabilities which include the display of the Tool Execution Time - - A method to get some project configurations for debug purposes - - Methods to specify where to search an ip-repo, add a block design and export the hardware - (Only supported for Xilinx Vivado) -* Documentation: - - User Guide - - API Reference - - Development notes - - Tools notes -* Examples: - - General-purpose (boilerplate and capture) - - Multi-purpose (memory, parameters, projects, strategies, verilog and vhdl) - - Specific of each Tool where Constraint files are included (for the boards s6micro, mkr, - de10nano and zybo) -* Testing: - - Examples are used to test the correct Tool behaviour - - The PART specification is particularly tested for ISE and Libero - - The master Tcl is checked using Tclsh - - The top-level specification is tested with different alternatives - -## History - -The roots of this project date back to 2015, where an internal project called fpga_tools, written -in Perl, was used to populate templates for the ISE tool. - -In 2016 the project was renamed as fpga_helpers, changing Perl by Python, published as open-source -and adding support for Vivado, Quartus and time after for Libero-SoC. -The project evolved to a Makefile and two Tcl scripts, one for Synthesis and other for Programming, -all of them implementing multi-vendor support, with a few Python scripts to automate tasks. - -At the end of 2019, a new project written from scratch, called PyFPGA, superseded the previous one, -where the use of the Makefile and the Tcl scripts were replaced by a Python class used to create -customized workflows. diff --git a/docs/index.rst b/docs/index.rst index 3bfdf197..58fc8605 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -16,6 +16,7 @@ PyFPGA's documentation tools internals extending + misc .. |timestamp| date:: %Y-%m-%d %H:%M (%Z) diff --git a/docs/misc.rst b/docs/misc.rst new file mode 100644 index 00000000..494751bb --- /dev/null +++ b/docs/misc.rst @@ -0,0 +1,27 @@ +Miscellaneous +============= + +History +------- + +The origins of this project trace back to 2015, when an in-house, never-released script called **fpga_tools**, written in *Perl*, was used to populate templates for the Xilinx ISE tool. + +In 2016, the project was renamed **fpga_helpers**, transitioning from *Perl* to *Python*. +It was published as open-source on GitHub, adding support for Xilinx Vivado and Altera Quartus, and later for Microsemi (now Microchip) Libero-SoC. +It evolved into a Makefile-based system with two Tcl scripts - one for synthesis and another for programming - both designed to support multiple vendors, along with a few automation scripts. + +By the end of 2019, **PyFPGA** emerged as a complete rewrite, replacing the Makefile and Tcl scripts with a Python-based workflow system. +The project was launched on GitLab, with its first official release (0.1.0) on February 29, 2020 - just before the onset of the COVID-19 pandemic. + +Throughout 2020, support for open-source tools was gradually introduced. +Initially, Yosys was integrated as the synthesizer, while ISE/Vivado handled place-and-route and bitstream generation. +Later, support for VHDL via *ghdl-yosys-plugin* was added, followed by the introduction of OpenFlow - a fully solved with FLOSS tools workflow - at the end of the year. +Additionally, command-line utilities were introduced to simplify working with small projects and simple and quick proof-of-concepts. + +In 2021, PyFPGA was migrated from GitLab to GitHub, aligning with the broader FPGA-related FLOSS ecosystem. +That year, the codebase was significantly expanded and improved, but it also became more complex to maintain. +This led to the release of version 0.2.0 on May 15, 2022, marking a new starting point. + +Between 2023 and 2024, the project underwent a major rewrite, incorporating substantial improvements. +In 2024, support for a new vendor tool, Diamond, was contributed. +As a result, a new release is taking place in March 2025. From f2aa699cfa67e83dd2493c4fe4930ab7255a458d Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Mon, 3 Mar 2025 20:15:51 -0300 Subject: [PATCH 251/254] docs: remove deprecated images --- docs/images/images.fodg | 4215 --------------------------------------- docs/images/schema.png | Bin 45176 -> 0 bytes 2 files changed, 4215 deletions(-) delete mode 100644 docs/images/images.fodg delete mode 100644 docs/images/schema.png diff --git a/docs/images/images.fodg b/docs/images/images.fodg deleted file mode 100644 index a1db6070..00000000 --- a/docs/images/images.fodg +++ /dev/null @@ -1,4215 +0,0 @@ - - - - Rodrigo Melo2019-12-11T11:17:02.1178911472020-10-26T11:45:04.918318103PT6H57M48S85LibreOffice/6.1.5.2$Linux_X86_64 LibreOffice_project/10$Build-2 - - - 6545 - 6127 - 21540 - 15813 - - - view1 - false - false - true - true - false - false - false - false - true - 1500 - false - //////////////////////////////////////////8= - //////////////////////////////////////////8= - - false - true - true - 0 - 0 - true - true - true - 4 - 0 - -1780 - 722 - 49117 - 24439 - 1000 - 1000 - 100 - 100 - 100 - 1 - 100 - 1 - false - 1500 - false - false - - - - - true - $(brandbaseurl)/share/palette%3B$(user)/config/standard.sob - 0 - $(brandbaseurl)/share/palette%3B$(user)/config/standard.soc - $(brandbaseurl)/share/palette%3B$(user)/config/standard.sod - 1250 - false - - - en - US - - - - - - es - AR - - - - - - $(brandbaseurl)/share/palette%3B$(user)/config/standard.sog - $(brandbaseurl)/share/palette%3B$(user)/config/standard.soh - false - false - true - true - false - false - true - false - false - false - $(brandbaseurl)/share/palette%3B$(user)/config/standard.soe - false - 3 - 4 - false - 0 - low-resolution - Generic Printer - false - kQH+/0dlbmVyaWMgUHJpbnRlcgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAU0dFTlBSVAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWAAMAsgAAAAAAAAAEAAhSAAAEdAAASm9iRGF0YSAxCnByaW50ZXI9R2VuZXJpYyBQcmludGVyCm9yaWVudGF0aW9uPVBvcnRyYWl0CmNvcGllcz0xCm1hcmdpbmRhanVzdG1lbnQ9MCwwLDAsMApjb2xvcmRlcHRoPTI0CnBzbGV2ZWw9MApwZGZkZXZpY2U9MApjb2xvcmRldmljZT0wClBQRENvbnRleERhdGEKUGFnZVNpemU6QTQARHVwbGV4Ok5vbmUAABIAQ09NUEFUX0RVUExFWF9NT0RFDwBEdXBsZXhNb2RlOjpPZmY= - false - 1 - 1 - true - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Tool - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Project - - - - - - - - - - - - - - - - - Ise - - - - - - - - - - - - - - - - - Libero - - - - - - - - - - - - - - - - - Quartus - - - - - - - - - - - - - - - - - Vivado - - - - - - - - - - - - - - - - - Openflow - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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 - AJiagAUAAADA1AQsAAAAAKYmYAEAAAAwNQELAAAAgKkJWAAAAABMTcACAAAAYGoCFgAAAABT - E7AAAAAAmJqABQAAAMDUBCwAAAAApiZgAQAAADA1AQsAAACAqQlYAAAAAExNwAIAAABgagIW - AAAAAFMTsAAAAACYmoAFAAAAwNQELAAAAACmJmABAAAAMDUBCwAAAICpCVgAAAAATE3AAgAA - AGBqAhYAAAAAUxOwAAAAAJiagAUAAADA1AQsAAAAAKYmYAEAAAAwNQELAAAAgKkJWAAAAABM - TcACAAAAYGoCFgAAAABTE7AAAAAAmJqABQAAAMDUBCwAAAAApiZgAQAAADA1AQsAAACAqQlY - AAAAAExNwAIAAABgagIWAAAAAFMTsAAAAACYmoAFAAAAwNQELAAAAACmJmABAAAAMDUBCwAA - AICpCVgAAAAATE3AAgAAAGBqAhYAAAAAUxOwAAAAAJiagAUAAADA1AQsAAAAAKYmYAEAAAAw - NQELAAAAgKkJWAAAAABMTcACAAAAYGoCFgAAAABTE7AAAAAAmJqABQAAAMDUBCwAAAAApiZg - AQAAADA1AQsAAACAqQlYAAAAAExNwAIAAABgagIWAAAAAFMTsAAAAACYmoAFAAAAwNQELAAA - AACmJmABAAAAMDUBCwAAAICpCVgAAAAATE3AAgAAAGBqAhYAAAAAUxOwAAAAAJiagAUAAADA - 1AQsAAAAAKYmYAEAAAAwNQELAAAAgKkJWAAAAABMTcACAAAAYGoCFgAAAABTE7AAAAAAmJqA - BQAAAMDUBCwAAAAApiZgAQAAADA1AQsAAACAqQlYAAAAAExNwAIAAABgagIWAAAAAFMTsAAA - AACYmoAFAAAAwNQELAAAAACmJmABAAAAMDUBCwAAAICpCVgAAAAATE3AAgAAAGBqAhYAAAAA - UxOwAAAAAJiagAUAAADA1AQsAAAAAKYmYAEAAAAwNQELAAAAgKkJWAAAAABMTcACAAAAYGoC - FgAAAABTE7AAAAAAmJqABQAAAMDUBCwAAAAApiZgAQAAADA1AQsAAACAqQlYAAAAAExNwAIA - AABgagIWAAAAAFMTsAAAAACYmoAFAAAAwNQELAAAAACmJmABAAAAMDUBCwAAAICpCVgAAAAA - TE3AAgAAAGBqAhYAAAAAUxOwAAAAAJiagAUAAADA1AQsAAAAAKYmYAEAAAAwNQELAAAAgKkJ - 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 - - - - - - - - - - - - - - - - - - - - - - PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+ - CjwhLS0gQ3JlYXRlZCB3aXRoIElua3NjYXBlIChodHRwOi8vd3d3Lmlua3NjYXBlLm9yZy8p - IC0tPgo8c3ZnCiAgIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4x - LyIKICAgeG1sbnM6Y2M9Imh0dHA6Ly93ZWIucmVzb3VyY2Uub3JnL2NjLyIKICAgeG1sbnM6 - cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIgogICB4 - bWxuczpzdmc9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIgogICB4bWxucz0iaHR0cDov - L3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9y - Zy8xOTk5L3hsaW5rIgogICB4bWxuczpzb2RpcG9kaT0iaHR0cDovL3NvZGlwb2RpLnNvdXJj - ZWZvcmdlLm5ldC9EVEQvc29kaXBvZGktMC5kdGQiCiAgIHhtbG5zOmlua3NjYXBlPSJodHRw - Oi8vd3d3Lmlua3NjYXBlLm9yZy9uYW1lc3BhY2VzL2lua3NjYXBlIgogICB3aWR0aD0iMTEw - LjQyMTEiCiAgIGhlaWdodD0iMTA5Ljg0NjEiCiAgIGlkPSJzdmcyMTY5IgogICBzb2RpcG9k - aTp2ZXJzaW9uPSIwLjMyIgogICBpbmtzY2FwZTp2ZXJzaW9uPSIwLjQ1LjEiCiAgIHZlcnNp - b249IjEuMCIKICAgc29kaXBvZGk6ZG9jYmFzZT0iL2hvbWUvYmVuZS9EZXNrdG9wIgogICBz - b2RpcG9kaTpkb2NuYW1lPSJkZXNzaW4tMS5zdmciCiAgIGlua3NjYXBlOm91dHB1dF9leHRl - bnNpb249Im9yZy5pbmtzY2FwZS5vdXRwdXQuc3ZnLmlua3NjYXBlIj4KICA8ZGVmcwogICAg - IGlkPSJkZWZzMjE3MSI+CiAgICA8bGluZWFyR3JhZGllbnQKICAgICAgIGlkPSJsaW5lYXJH - cmFkaWVudDExMzAxIgogICAgICAgaW5rc2NhcGU6Y29sbGVjdD0iYWx3YXlzIj4KICAgICAg - PHN0b3AKICAgICAgICAgaWQ9InN0b3AxMTMwMyIKICAgICAgICAgb2Zmc2V0PSIwIgogICAg - ICAgICBzdHlsZT0ic3RvcC1jb2xvcjojZmZlMDUyO3N0b3Atb3BhY2l0eToxIiAvPgogICAg - ICA8c3RvcAogICAgICAgICBpZD0ic3RvcDExMzA1IgogICAgICAgICBvZmZzZXQ9IjEiCiAg - ICAgICAgIHN0eWxlPSJzdG9wLWNvbG9yOiNmZmMzMzE7c3RvcC1vcGFjaXR5OjEiIC8+CiAg - ICA8L2xpbmVhckdyYWRpZW50PgogICAgPGxpbmVhckdyYWRpZW50CiAgICAgICBncmFkaWVu - dFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIKICAgICAgIHkyPSIxNjguMTAxMiIKICAgICAgIHgy - PSIxNDcuNzc3MzciCiAgICAgICB5MT0iMTExLjkyMDUzIgogICAgICAgeDE9Ijg5LjEzNjc0 - OSIKICAgICAgIGlkPSJsaW5lYXJHcmFkaWVudDExMzA3IgogICAgICAgeGxpbms6aHJlZj0i - I2xpbmVhckdyYWRpZW50MTEzMDEiCiAgICAgICBpbmtzY2FwZTpjb2xsZWN0PSJhbHdheXMi - IC8+CiAgICA8bGluZWFyR3JhZGllbnQKICAgICAgIGlkPSJsaW5lYXJHcmFkaWVudDk1MTUi - CiAgICAgICBpbmtzY2FwZTpjb2xsZWN0PSJhbHdheXMiPgogICAgICA8c3RvcAogICAgICAg - ICBpZD0ic3RvcDk1MTciCiAgICAgICAgIG9mZnNldD0iMCIKICAgICAgICAgc3R5bGU9InN0 - b3AtY29sb3I6IzM4N2ViODtzdG9wLW9wYWNpdHk6MSIgLz4KICAgICAgPHN0b3AKICAgICAg - ICAgaWQ9InN0b3A5NTE5IgogICAgICAgICBvZmZzZXQ9IjEiCiAgICAgICAgIHN0eWxlPSJz - dG9wLWNvbG9yOiMzNjY5OTQ7c3RvcC1vcGFjaXR5OjEiIC8+CiAgICA8L2xpbmVhckdyYWRp - ZW50PgogICAgPGxpbmVhckdyYWRpZW50CiAgICAgICBncmFkaWVudFVuaXRzPSJ1c2VyU3Bh - Y2VPblVzZSIKICAgICAgIHkyPSIxMzEuODUyOTEiCiAgICAgICB4Mj0iMTEwLjE0OTE5Igog - ICAgICAgeTE9Ijc3LjA3MDI3NCIKICAgICAgIHgxPSI1NS41NDkxNzkiCiAgICAgICBpZD0i - bGluZWFyR3JhZGllbnQ5NTIxIgogICAgICAgeGxpbms6aHJlZj0iI2xpbmVhckdyYWRpZW50 - OTUxNSIKICAgICAgIGlua3NjYXBlOmNvbGxlY3Q9ImFsd2F5cyIgLz4KICA8L2RlZnM+CiAg - PHNvZGlwb2RpOm5hbWVkdmlldwogICAgIGlkPSJiYXNlIgogICAgIHBhZ2Vjb2xvcj0iI2Zm - ZmZmZiIKICAgICBib3JkZXJjb2xvcj0iIzY2NjY2NiIKICAgICBib3JkZXJvcGFjaXR5PSIx - LjAiCiAgICAgaW5rc2NhcGU6cGFnZW9wYWNpdHk9IjAuMCIKICAgICBpbmtzY2FwZTpwYWdl - c2hhZG93PSIyIgogICAgIGlua3NjYXBlOnpvb209IjAuMjQ3NDg3MzciCiAgICAgaW5rc2Nh - cGU6Y3g9Ii0yNjAuNDYzMTIiCiAgICAgaW5rc2NhcGU6Y3k9IjMxNi4wMjc0NCIKICAgICBp - bmtzY2FwZTpkb2N1bWVudC11bml0cz0icHgiCiAgICAgaW5rc2NhcGU6Y3VycmVudC1sYXll - cj0ibGF5ZXIxIgogICAgIHdpZHRoPSIxMzEuMTAyMzZweCIKICAgICBoZWlnaHQ9IjE4NC4y - NTE5N3B4IgogICAgIGlua3NjYXBlOndpbmRvdy13aWR0aD0iODcyIgogICAgIGlua3NjYXBl - OndpbmRvdy1oZWlnaHQ9IjYyNCIKICAgICBpbmtzY2FwZTp3aW5kb3cteD0iNSIKICAgICBp - bmtzY2FwZTp3aW5kb3cteT0iNDgiIC8+CiAgPG1ldGFkYXRhCiAgICAgaWQ9Im1ldGFkYXRh - MjE3NCI+CiAgICA8cmRmOlJERj4KICAgICAgPGNjOldvcmsKICAgICAgICAgcmRmOmFib3V0 - PSIiPgogICAgICAgIDxkYzpmb3JtYXQ+aW1hZ2Uvc3ZnK3htbDwvZGM6Zm9ybWF0PgogICAg - ICAgIDxkYzp0eXBlCiAgICAgICAgICAgcmRmOnJlc291cmNlPSJodHRwOi8vcHVybC5vcmcv - ZGMvZGNtaXR5cGUvU3RpbGxJbWFnZSIgLz4KICAgICAgPC9jYzpXb3JrPgogICAgPC9yZGY6 - UkRGPgogIDwvbWV0YWRhdGE+CiAgPGcKICAgICBpbmtzY2FwZTpsYWJlbD0iQ2FscXVlIDEi - CiAgICAgaW5rc2NhcGU6Z3JvdXBtb2RlPSJsYXllciIKICAgICBpZD0ibGF5ZXIxIgogICAg - IHRyYW5zZm9ybT0idHJhbnNsYXRlKC00NzMuMzYwODgsLTI1MS43MjQ4NSkiPgogICAgPGcK - ICAgICAgIGlkPSJnMTg5NCIKICAgICAgIHRyYW5zZm9ybT0idHJhbnNsYXRlKDQyOC40MjMz - OCwxODQuMjU2MSkiPgogICAgICA8cGF0aAogICAgICAgICBzdHlsZT0ib3BhY2l0eToxO2Nv - bG9yOiMwMDAwMDA7ZmlsbDp1cmwoI2xpbmVhckdyYWRpZW50OTUyMSk7ZmlsbC1vcGFjaXR5 - OjE7ZmlsbC1ydWxlOm5vbnplcm87c3Ryb2tlOm5vbmU7c3Ryb2tlLXdpZHRoOjE7c3Ryb2tl - LWxpbmVjYXA6YnV0dDtzdHJva2UtbGluZWpvaW46bWl0ZXI7bWFya2VyOm5vbmU7bWFya2Vy - LXN0YXJ0Om5vbmU7bWFya2VyLW1pZDpub25lO21hcmtlci1lbmQ6bm9uZTtzdHJva2UtbWl0 - ZXJsaW1pdDo0O3N0cm9rZS1kYXNoYXJyYXk6bm9uZTtzdHJva2UtZGFzaG9mZnNldDowO3N0 - cm9rZS1vcGFjaXR5OjE7dmlzaWJpbGl0eTp2aXNpYmxlO2Rpc3BsYXk6aW5saW5lO292ZXJm - bG93OnZpc2libGUiCiAgICAgICAgIGQ9Ik0gOTkuNzUsNjcuNDY4NzUgQyA3MS43MTgyNjgs - NjcuNDY4NzUyIDczLjQ2ODc1LDc5LjYyNSA3My40Njg3NSw3OS42MjUgTCA3My41LDkyLjIx - ODc1IEwgMTAwLjI1LDkyLjIxODc1IEwgMTAwLjI1LDk2IEwgNjIuODc1LDk2IEMgNjIuODc1 - LDk2IDQ0LjkzNzUsOTMuOTY1NzI0IDQ0LjkzNzUsMTIyLjI1IEMgNDQuOTM3NDk4LDE1MC41 - MzQyNyA2MC41OTM3NSwxNDkuNTMxMjUgNjAuNTkzNzUsMTQ5LjUzMTI1IEwgNjkuOTM3NSwx - NDkuNTMxMjUgTCA2OS45Mzc1LDEzNi40MDYyNSBDIDY5LjkzNzUsMTM2LjQwNjI1IDY5LjQz - Mzg0OCwxMjAuNzUgODUuMzQzNzUsMTIwLjc1IEMgMTAxLjI1MzY1LDEyMC43NSAxMTEuODc1 - LDEyMC43NSAxMTEuODc1LDEyMC43NSBDIDExMS44NzUsMTIwLjc1IDEyNi43ODEyNSwxMjAu - OTkwOTYgMTI2Ljc4MTI1LDEwNi4zNDM3NSBDIDEyNi43ODEyNSw5MS42OTY1NDQgMTI2Ljc4 - MTI1LDgyLjEyNSAxMjYuNzgxMjUsODIuMTI1IEMgMTI2Ljc4MTI1LDgyLjEyNDk5OCAxMjku - MDQ0NDMsNjcuNDY4NzUgOTkuNzUsNjcuNDY4NzUgeiBNIDg1LDc1LjkzNzUgQyA4Ny42NjE0 - MjksNzUuOTM3NDk4IDg5LjgxMjUsNzguMDg4NTcxIDg5LjgxMjUsODAuNzUgQyA4OS44MTI1 - MDIsODMuNDExNDI5IDg3LjY2MTQyOSw4NS41NjI1IDg1LDg1LjU2MjUgQyA4Mi4zMzg1NzEs - ODUuNTYyNTAyIDgwLjE4NzUsODMuNDExNDI5IDgwLjE4NzUsODAuNzUgQyA4MC4xODc0OTgs - NzguMDg4NTcxIDgyLjMzODU3MSw3NS45Mzc1IDg1LDc1LjkzNzUgeiAiCiAgICAgICAgIGlk - PSJwYXRoODYxNSIgLz4KICAgICAgPHBhdGgKICAgICAgICAgaWQ9InBhdGg4NjIwIgogICAg - ICAgICBkPSJNIDEwMC41NDYxLDE3Ny4zMTQ4NSBDIDEyOC41Nzc4NCwxNzcuMzE0ODUgMTI2 - LjgyNzM1LDE2NS4xNTg2IDEyNi44MjczNSwxNjUuMTU4NiBMIDEyNi43OTYxLDE1Mi41NjQ4 - NSBMIDEwMC4wNDYxLDE1Mi41NjQ4NSBMIDEwMC4wNDYxLDE0OC43ODM2IEwgMTM3LjQyMTEs - MTQ4Ljc4MzYgQyAxMzcuNDIxMSwxNDguNzgzNiAxNTUuMzU4NiwxNTAuODE3ODcgMTU1LjM1 - ODYsMTIyLjUzMzU5IEMgMTU1LjM1ODYxLDk0LjI0OTMyMyAxMzkuNzAyMzUsOTUuMjUyMzQz - IDEzOS43MDIzNSw5NS4yNTIzNDMgTCAxMzAuMzU4Niw5NS4yNTIzNDMgTCAxMzAuMzU4Niwx - MDguMzc3MzQgQyAxMzAuMzU4NiwxMDguMzc3MzQgMTMwLjg2MjI2LDEyNC4wMzM1OSAxMTQu - OTUyMzUsMTI0LjAzMzU5IEMgOTkuMDQyNDQ4LDEyNC4wMzM1OSA4OC40MjEwOTgsMTI0LjAz - MzU5IDg4LjQyMTA5OCwxMjQuMDMzNTkgQyA4OC40MjEwOTgsMTI0LjAzMzU5IDczLjUxNDg0 - OCwxMjMuNzkyNjMgNzMuNTE0ODQ4LDEzOC40Mzk4NSBDIDczLjUxNDg0OCwxNTMuMDg3MDUg - NzMuNTE0ODQ4LDE2Mi42NTg2IDczLjUxNDg0OCwxNjIuNjU4NiBDIDczLjUxNDg0OCwxNjIu - NjU4NiA3MS4yNTE2NjgsMTc3LjMxNDg1IDEwMC41NDYxLDE3Ny4zMTQ4NSB6IE0gMTE1LjI5 - NjEsMTY4Ljg0NjEgQyAxMTIuNjM0NjcsMTY4Ljg0NjEgMTEwLjQ4MzYsMTY2LjY5NTAzIDEx - MC40ODM2LDE2NC4wMzM2IEMgMTEwLjQ4MzYsMTYxLjM3MjE3IDExMi42MzQ2NywxNTkuMjIx - MSAxMTUuMjk2MSwxNTkuMjIxMSBDIDExNy45NTc1MywxNTkuMjIxMSAxMjAuMTA4NiwxNjEu - MzcyMTcgMTIwLjEwODYsMTY0LjAzMzYgQyAxMjAuMTA4NjEsMTY2LjY5NTAzIDExNy45NTc1 - MywxNjguODQ2MSAxMTUuMjk2MSwxNjguODQ2MSB6ICIKICAgICAgICAgc3R5bGU9Im9wYWNp - dHk6MTtjb2xvcjojMDAwMDAwO2ZpbGw6dXJsKCNsaW5lYXJHcmFkaWVudDExMzA3KTtmaWxs - LW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZTtzdHJva2Utd2lkdGg6 - MTtzdHJva2UtbGluZWNhcDpidXR0O3N0cm9rZS1saW5lam9pbjptaXRlcjttYXJrZXI6bm9u - ZTttYXJrZXItc3RhcnQ6bm9uZTttYXJrZXItbWlkOm5vbmU7bWFya2VyLWVuZDpub25lO3N0 - cm9rZS1taXRlcmxpbWl0OjQ7c3Ryb2tlLWRhc2hhcnJheTpub25lO3N0cm9rZS1kYXNob2Zm - c2V0OjA7c3Ryb2tlLW9wYWNpdHk6MTt2aXNpYmlsaXR5OnZpc2libGU7ZGlzcGxheTppbmxp - bmU7b3ZlcmZsb3c6dmlzaWJsZSIgLz4KICAgIDwvZz4KICA8L2c+Cjwvc3ZnPgo= - - - - - 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 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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 - KSBjrvLyclJ4IqcxbuuWVE3H1hCNOFRNixiRe0IIOjq7UFBcgqbmFnAmEwL8/aHXj74JUqdj - ERQQgKbmFrS0jc0uDgZQzTxfYrfbXp4XNuf5t954NSs/LU2mT5ICck114sQJqbyoqHRH3M50 - SZO7GZYJk+WRp/wqioL6xkYUFpegqbkZqqbBYbfDaDCMmBpmGAY+DgcCA/zR09uL1rarGrAT - k8nUYbFY/hIWHPqr9/6879PsHXED9MlRQMZVublZfQ/ce08m0bH5iqLI5Oy5J+bhoAiCgOra - WuSdLEB55Sm0tXfAI3nAgAHDMGDOjUMYhkFIcDAWzJ8HnY5Fe0cnBPHyliMMBr1osVi+9nM4 - nlu+YO7rr7z4uzNISKAP68qjMNVY6PHHHzf3C9JtA07nD0SPGKOq2qjnyLEMA7PFDH9fPwQF - BSIoMAAOux2ciQNAIHo86OruQUlpGdo6Oi5pCwrDMCrPc4VWq/WdsIiwA68+91w7fSoUkEmn - H/30p+GdrV0PCh5ht1sQlg0vSfQdnfzCGOay7JTR1MSZTB8EBQbt//Prr5bgGh9LRgGhuioR - Qph//4//WN3a0bXLLQh3S5K8cPi2lav9FwBgMppaObPpsI/N5wOfFUuOXotTXikgVNdM8fHx - +vqOjlXOAfcdTrczVpHk1bKinB+jXEnbEx3LikajsYYzmdKtVv7T+ZGR6QkJCW7a2hSQqQwK - 29Y2EOqUBm90utw3S7LnelUjCzyix8qwDE8I0Q2LMIRhGLAMo2qEePR6vdtgMDQYDIYCi9mc - wZn0mTajsXHPnj00YlBApl9U6eryOATVNavP2RvBmcwLBcEd0N8/aNRU1cgwDMsAHqvdKtss - tn5VkauhQ0OoT2hTU9Ppzg8//JBuCxlH/X+KGPgIwabfAQAAAABJRU5ErkJggg== - - - - - - - PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+ - CjwhLS0gQ3JlYXRlZCB3aXRoIElua3NjYXBlIChodHRwOi8vd3d3Lmlua3NjYXBlLm9yZy8p - IC0tPgo8c3ZnCiAgIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4x - LyIKICAgeG1sbnM6Y2M9Imh0dHA6Ly93ZWIucmVzb3VyY2Uub3JnL2NjLyIKICAgeG1sbnM6 - cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIgogICB4 - bWxuczpzdmc9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIgogICB4bWxucz0iaHR0cDov - L3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9y - Zy8xOTk5L3hsaW5rIgogICB4bWxuczpzb2RpcG9kaT0iaHR0cDovL3NvZGlwb2RpLnNvdXJj - ZWZvcmdlLm5ldC9EVEQvc29kaXBvZGktMC5kdGQiCiAgIHhtbG5zOmlua3NjYXBlPSJodHRw - Oi8vd3d3Lmlua3NjYXBlLm9yZy9uYW1lc3BhY2VzL2lua3NjYXBlIgogICB3aWR0aD0iMTEw - LjQyMTEiCiAgIGhlaWdodD0iMTA5Ljg0NjEiCiAgIGlkPSJzdmcyMTY5IgogICBzb2RpcG9k - aTp2ZXJzaW9uPSIwLjMyIgogICBpbmtzY2FwZTp2ZXJzaW9uPSIwLjQ1LjEiCiAgIHZlcnNp - b249IjEuMCIKICAgc29kaXBvZGk6ZG9jYmFzZT0iL2hvbWUvYmVuZS9EZXNrdG9wIgogICBz - b2RpcG9kaTpkb2NuYW1lPSJkZXNzaW4tMS5zdmciCiAgIGlua3NjYXBlOm91dHB1dF9leHRl - bnNpb249Im9yZy5pbmtzY2FwZS5vdXRwdXQuc3ZnLmlua3NjYXBlIj4KICA8ZGVmcwogICAg - IGlkPSJkZWZzMjE3MSI+CiAgICA8bGluZWFyR3JhZGllbnQKICAgICAgIGlkPSJsaW5lYXJH - cmFkaWVudDExMzAxIgogICAgICAgaW5rc2NhcGU6Y29sbGVjdD0iYWx3YXlzIj4KICAgICAg - PHN0b3AKICAgICAgICAgaWQ9InN0b3AxMTMwMyIKICAgICAgICAgb2Zmc2V0PSIwIgogICAg - ICAgICBzdHlsZT0ic3RvcC1jb2xvcjojZmZlMDUyO3N0b3Atb3BhY2l0eToxIiAvPgogICAg - ICA8c3RvcAogICAgICAgICBpZD0ic3RvcDExMzA1IgogICAgICAgICBvZmZzZXQ9IjEiCiAg - ICAgICAgIHN0eWxlPSJzdG9wLWNvbG9yOiNmZmMzMzE7c3RvcC1vcGFjaXR5OjEiIC8+CiAg - ICA8L2xpbmVhckdyYWRpZW50PgogICAgPGxpbmVhckdyYWRpZW50CiAgICAgICBncmFkaWVu - dFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIKICAgICAgIHkyPSIxNjguMTAxMiIKICAgICAgIHgy - PSIxNDcuNzc3MzciCiAgICAgICB5MT0iMTExLjkyMDUzIgogICAgICAgeDE9Ijg5LjEzNjc0 - OSIKICAgICAgIGlkPSJsaW5lYXJHcmFkaWVudDExMzA3IgogICAgICAgeGxpbms6aHJlZj0i - I2xpbmVhckdyYWRpZW50MTEzMDEiCiAgICAgICBpbmtzY2FwZTpjb2xsZWN0PSJhbHdheXMi - IC8+CiAgICA8bGluZWFyR3JhZGllbnQKICAgICAgIGlkPSJsaW5lYXJHcmFkaWVudDk1MTUi - CiAgICAgICBpbmtzY2FwZTpjb2xsZWN0PSJhbHdheXMiPgogICAgICA8c3RvcAogICAgICAg - ICBpZD0ic3RvcDk1MTciCiAgICAgICAgIG9mZnNldD0iMCIKICAgICAgICAgc3R5bGU9InN0 - b3AtY29sb3I6IzM4N2ViODtzdG9wLW9wYWNpdHk6MSIgLz4KICAgICAgPHN0b3AKICAgICAg - ICAgaWQ9InN0b3A5NTE5IgogICAgICAgICBvZmZzZXQ9IjEiCiAgICAgICAgIHN0eWxlPSJz - dG9wLWNvbG9yOiMzNjY5OTQ7c3RvcC1vcGFjaXR5OjEiIC8+CiAgICA8L2xpbmVhckdyYWRp - ZW50PgogICAgPGxpbmVhckdyYWRpZW50CiAgICAgICBncmFkaWVudFVuaXRzPSJ1c2VyU3Bh - Y2VPblVzZSIKICAgICAgIHkyPSIxMzEuODUyOTEiCiAgICAgICB4Mj0iMTEwLjE0OTE5Igog - ICAgICAgeTE9Ijc3LjA3MDI3NCIKICAgICAgIHgxPSI1NS41NDkxNzkiCiAgICAgICBpZD0i - bGluZWFyR3JhZGllbnQ5NTIxIgogICAgICAgeGxpbms6aHJlZj0iI2xpbmVhckdyYWRpZW50 - OTUxNSIKICAgICAgIGlua3NjYXBlOmNvbGxlY3Q9ImFsd2F5cyIgLz4KICA8L2RlZnM+CiAg - PHNvZGlwb2RpOm5hbWVkdmlldwogICAgIGlkPSJiYXNlIgogICAgIHBhZ2Vjb2xvcj0iI2Zm - ZmZmZiIKICAgICBib3JkZXJjb2xvcj0iIzY2NjY2NiIKICAgICBib3JkZXJvcGFjaXR5PSIx - LjAiCiAgICAgaW5rc2NhcGU6cGFnZW9wYWNpdHk9IjAuMCIKICAgICBpbmtzY2FwZTpwYWdl - c2hhZG93PSIyIgogICAgIGlua3NjYXBlOnpvb209IjAuMjQ3NDg3MzciCiAgICAgaW5rc2Nh - cGU6Y3g9Ii0yNjAuNDYzMTIiCiAgICAgaW5rc2NhcGU6Y3k9IjMxNi4wMjc0NCIKICAgICBp - bmtzY2FwZTpkb2N1bWVudC11bml0cz0icHgiCiAgICAgaW5rc2NhcGU6Y3VycmVudC1sYXll - cj0ibGF5ZXIxIgogICAgIHdpZHRoPSIxMzEuMTAyMzZweCIKICAgICBoZWlnaHQ9IjE4NC4y - NTE5N3B4IgogICAgIGlua3NjYXBlOndpbmRvdy13aWR0aD0iODcyIgogICAgIGlua3NjYXBl - OndpbmRvdy1oZWlnaHQ9IjYyNCIKICAgICBpbmtzY2FwZTp3aW5kb3cteD0iNSIKICAgICBp - bmtzY2FwZTp3aW5kb3cteT0iNDgiIC8+CiAgPG1ldGFkYXRhCiAgICAgaWQ9Im1ldGFkYXRh - MjE3NCI+CiAgICA8cmRmOlJERj4KICAgICAgPGNjOldvcmsKICAgICAgICAgcmRmOmFib3V0 - PSIiPgogICAgICAgIDxkYzpmb3JtYXQ+aW1hZ2Uvc3ZnK3htbDwvZGM6Zm9ybWF0PgogICAg - ICAgIDxkYzp0eXBlCiAgICAgICAgICAgcmRmOnJlc291cmNlPSJodHRwOi8vcHVybC5vcmcv - ZGMvZGNtaXR5cGUvU3RpbGxJbWFnZSIgLz4KICAgICAgPC9jYzpXb3JrPgogICAgPC9yZGY6 - UkRGPgogIDwvbWV0YWRhdGE+CiAgPGcKICAgICBpbmtzY2FwZTpsYWJlbD0iQ2FscXVlIDEi - CiAgICAgaW5rc2NhcGU6Z3JvdXBtb2RlPSJsYXllciIKICAgICBpZD0ibGF5ZXIxIgogICAg - IHRyYW5zZm9ybT0idHJhbnNsYXRlKC00NzMuMzYwODgsLTI1MS43MjQ4NSkiPgogICAgPGcK - ICAgICAgIGlkPSJnMTg5NCIKICAgICAgIHRyYW5zZm9ybT0idHJhbnNsYXRlKDQyOC40MjMz - OCwxODQuMjU2MSkiPgogICAgICA8cGF0aAogICAgICAgICBzdHlsZT0ib3BhY2l0eToxO2Nv - bG9yOiMwMDAwMDA7ZmlsbDp1cmwoI2xpbmVhckdyYWRpZW50OTUyMSk7ZmlsbC1vcGFjaXR5 - OjE7ZmlsbC1ydWxlOm5vbnplcm87c3Ryb2tlOm5vbmU7c3Ryb2tlLXdpZHRoOjE7c3Ryb2tl - LWxpbmVjYXA6YnV0dDtzdHJva2UtbGluZWpvaW46bWl0ZXI7bWFya2VyOm5vbmU7bWFya2Vy - LXN0YXJ0Om5vbmU7bWFya2VyLW1pZDpub25lO21hcmtlci1lbmQ6bm9uZTtzdHJva2UtbWl0 - ZXJsaW1pdDo0O3N0cm9rZS1kYXNoYXJyYXk6bm9uZTtzdHJva2UtZGFzaG9mZnNldDowO3N0 - cm9rZS1vcGFjaXR5OjE7dmlzaWJpbGl0eTp2aXNpYmxlO2Rpc3BsYXk6aW5saW5lO292ZXJm - bG93OnZpc2libGUiCiAgICAgICAgIGQ9Ik0gOTkuNzUsNjcuNDY4NzUgQyA3MS43MTgyNjgs - NjcuNDY4NzUyIDczLjQ2ODc1LDc5LjYyNSA3My40Njg3NSw3OS42MjUgTCA3My41LDkyLjIx - ODc1IEwgMTAwLjI1LDkyLjIxODc1IEwgMTAwLjI1LDk2IEwgNjIuODc1LDk2IEMgNjIuODc1 - LDk2IDQ0LjkzNzUsOTMuOTY1NzI0IDQ0LjkzNzUsMTIyLjI1IEMgNDQuOTM3NDk4LDE1MC41 - MzQyNyA2MC41OTM3NSwxNDkuNTMxMjUgNjAuNTkzNzUsMTQ5LjUzMTI1IEwgNjkuOTM3NSwx - NDkuNTMxMjUgTCA2OS45Mzc1LDEzNi40MDYyNSBDIDY5LjkzNzUsMTM2LjQwNjI1IDY5LjQz - Mzg0OCwxMjAuNzUgODUuMzQzNzUsMTIwLjc1IEMgMTAxLjI1MzY1LDEyMC43NSAxMTEuODc1 - LDEyMC43NSAxMTEuODc1LDEyMC43NSBDIDExMS44NzUsMTIwLjc1IDEyNi43ODEyNSwxMjAu - OTkwOTYgMTI2Ljc4MTI1LDEwNi4zNDM3NSBDIDEyNi43ODEyNSw5MS42OTY1NDQgMTI2Ljc4 - MTI1LDgyLjEyNSAxMjYuNzgxMjUsODIuMTI1IEMgMTI2Ljc4MTI1LDgyLjEyNDk5OCAxMjku - MDQ0NDMsNjcuNDY4NzUgOTkuNzUsNjcuNDY4NzUgeiBNIDg1LDc1LjkzNzUgQyA4Ny42NjE0 - MjksNzUuOTM3NDk4IDg5LjgxMjUsNzguMDg4NTcxIDg5LjgxMjUsODAuNzUgQyA4OS44MTI1 - MDIsODMuNDExNDI5IDg3LjY2MTQyOSw4NS41NjI1IDg1LDg1LjU2MjUgQyA4Mi4zMzg1NzEs - ODUuNTYyNTAyIDgwLjE4NzUsODMuNDExNDI5IDgwLjE4NzUsODAuNzUgQyA4MC4xODc0OTgs - NzguMDg4NTcxIDgyLjMzODU3MSw3NS45Mzc1IDg1LDc1LjkzNzUgeiAiCiAgICAgICAgIGlk - PSJwYXRoODYxNSIgLz4KICAgICAgPHBhdGgKICAgICAgICAgaWQ9InBhdGg4NjIwIgogICAg - ICAgICBkPSJNIDEwMC41NDYxLDE3Ny4zMTQ4NSBDIDEyOC41Nzc4NCwxNzcuMzE0ODUgMTI2 - LjgyNzM1LDE2NS4xNTg2IDEyNi44MjczNSwxNjUuMTU4NiBMIDEyNi43OTYxLDE1Mi41NjQ4 - NSBMIDEwMC4wNDYxLDE1Mi41NjQ4NSBMIDEwMC4wNDYxLDE0OC43ODM2IEwgMTM3LjQyMTEs - MTQ4Ljc4MzYgQyAxMzcuNDIxMSwxNDguNzgzNiAxNTUuMzU4NiwxNTAuODE3ODcgMTU1LjM1 - ODYsMTIyLjUzMzU5IEMgMTU1LjM1ODYxLDk0LjI0OTMyMyAxMzkuNzAyMzUsOTUuMjUyMzQz - IDEzOS43MDIzNSw5NS4yNTIzNDMgTCAxMzAuMzU4Niw5NS4yNTIzNDMgTCAxMzAuMzU4Niwx - MDguMzc3MzQgQyAxMzAuMzU4NiwxMDguMzc3MzQgMTMwLjg2MjI2LDEyNC4wMzM1OSAxMTQu - OTUyMzUsMTI0LjAzMzU5IEMgOTkuMDQyNDQ4LDEyNC4wMzM1OSA4OC40MjEwOTgsMTI0LjAz - MzU5IDg4LjQyMTA5OCwxMjQuMDMzNTkgQyA4OC40MjEwOTgsMTI0LjAzMzU5IDczLjUxNDg0 - OCwxMjMuNzkyNjMgNzMuNTE0ODQ4LDEzOC40Mzk4NSBDIDczLjUxNDg0OCwxNTMuMDg3MDUg - NzMuNTE0ODQ4LDE2Mi42NTg2IDczLjUxNDg0OCwxNjIuNjU4NiBDIDczLjUxNDg0OCwxNjIu - NjU4NiA3MS4yNTE2NjgsMTc3LjMxNDg1IDEwMC41NDYxLDE3Ny4zMTQ4NSB6IE0gMTE1LjI5 - NjEsMTY4Ljg0NjEgQyAxMTIuNjM0NjcsMTY4Ljg0NjEgMTEwLjQ4MzYsMTY2LjY5NTAzIDEx - MC40ODM2LDE2NC4wMzM2IEMgMTEwLjQ4MzYsMTYxLjM3MjE3IDExMi42MzQ2NywxNTkuMjIx - MSAxMTUuMjk2MSwxNTkuMjIxMSBDIDExNy45NTc1MywxNTkuMjIxMSAxMjAuMTA4NiwxNjEu - MzcyMTcgMTIwLjEwODYsMTY0LjAzMzYgQyAxMjAuMTA4NjEsMTY2LjY5NTAzIDExNy45NTc1 - MywxNjguODQ2MSAxMTUuMjk2MSwxNjguODQ2MSB6ICIKICAgICAgICAgc3R5bGU9Im9wYWNp - dHk6MTtjb2xvcjojMDAwMDAwO2ZpbGw6dXJsKCNsaW5lYXJHcmFkaWVudDExMzA3KTtmaWxs - LW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZTtzdHJva2Utd2lkdGg6 - MTtzdHJva2UtbGluZWNhcDpidXR0O3N0cm9rZS1saW5lam9pbjptaXRlcjttYXJrZXI6bm9u - ZTttYXJrZXItc3RhcnQ6bm9uZTttYXJrZXItbWlkOm5vbmU7bWFya2VyLWVuZDpub25lO3N0 - cm9rZS1taXRlcmxpbWl0OjQ7c3Ryb2tlLWRhc2hhcnJheTpub25lO3N0cm9rZS1kYXNob2Zm - c2V0OjA7c3Ryb2tlLW9wYWNpdHk6MTt2aXNpYmlsaXR5OnZpc2libGU7ZGlzcGxheTppbmxp - bmU7b3ZlcmZsb3c6dmlzaWJsZSIgLz4KICAgIDwvZz4KICA8L2c+Cjwvc3ZnPgo= - - - - - iVBORw0KGgoAAAANSUhEUgAAAG4AAABuCAYAAADGWyb7AAAACXBIWXMAAA61AAAOyQHg+64y - AAASZElEQVR4nO2dCXxTVb7H/0lukiZtmu4LLS0tXQBBKBTKrowgm/pQGNkGHdSPysiII46O - 4zIOT5CnTxEdB2bUx/aeg4M8ZgDL9mQRhn0riwVaWlq60DZdkybN/nJS0ubmnpvc5ubem5b+ - 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 - - - - - - - GHDL - - - - - - - - - - - - - - - - - - - Yosys - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - nextpnr-ice40 - - - - nextpnr-ecp5 - - - - icestorm - - - - prjtrellis - - - - VHDL - - - - Verilog - - - - Verilog - - - - JSON - - - - EDIF - - - - - - - - - - - - - - - - - - - - - - - - - ISE - - - - Vivado - - - - VHDL - - - - Verilog - - - - .bit - - - - - - - .bit - - - - .bit - - - - .bit - - - - - - - - - - - - - - - - - - - - - - - - - GHDL - - - - Yosys - - - - ghdl - yosys - plugin - - - - - - - - - - - - - - - - - - - - - - - Synthesis - - - - - - - - - - - - - - - - - Implementation - - - - - - - - - - - - - - - - - Bitstream - generation - - - - - - - - - - - - - - - - - Unsynthesized - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/images/schema.png b/docs/images/schema.png deleted file mode 100644 index bac9799a806ca33a1ad87ea83c26884ec50d393d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 45176 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 SS34B=Co8EWQ7QI0_ Date: Mon, 3 Mar 2025 20:21:25 -0300 Subject: [PATCH 252/254] Remove deprecated examples, to be reimplemented --- examples/misc/yosys-ise.py | 40 ----------------------------------- examples/misc/yosys-vivado.py | 40 ----------------------------------- 2 files changed, 80 deletions(-) delete mode 100644 examples/misc/yosys-ise.py delete mode 100644 examples/misc/yosys-vivado.py diff --git a/examples/misc/yosys-ise.py b/examples/misc/yosys-ise.py deleted file mode 100644 index 6a900be9..00000000 --- a/examples/misc/yosys-ise.py +++ /dev/null @@ -1,40 +0,0 @@ -"""Yosys-ISE example project.""" - -import argparse -import logging - -from fpga.project import Project - -logging.basicConfig() - -parser = argparse.ArgumentParser() -parser.add_argument( - '--action', choices=['generate', 'transfer', 'all'], default='generate', -) -parser.add_argument( - '--lang', choices=['verilog', 'vhdl'], default='verilog', -) -args = parser.parse_args() - -prj = Project('yosys-ise') -prj.set_outdir('../../build/yosys-ise-{}'.format(args.lang)) -prj.set_part('XC6SLX9-2-CSG324') - -if args.lang == 'verilog': - prj.add_vlog_include('../../hdl/headers1') - prj.add_vlog_include('../../hdl/headers2') - prj.add_files('../../hdl/blinking.v') - prj.add_files('../../hdl/top.v') -else: # args.lang == 'vhdl' - prj.add_files('../../hdl/blinking.vhdl', library='examples') - prj.add_files('../../hdl/examples_pkg.vhdl', library='examples') - prj.add_files('../../hdl/top.vhdl') - -prj.add_files('../ise/s6micro.ucf') -prj.set_top('Top') - -if args.action in ['generate', 'all']: - prj.generate() - -if args.action in ['transfer', 'all']: - prj.transfer() diff --git a/examples/misc/yosys-vivado.py b/examples/misc/yosys-vivado.py deleted file mode 100644 index 4dfae60a..00000000 --- a/examples/misc/yosys-vivado.py +++ /dev/null @@ -1,40 +0,0 @@ -"""Yosys-Vivado example project.""" - -import argparse -import logging - -from fpga.project import Project - -logging.basicConfig() - -parser = argparse.ArgumentParser() -parser.add_argument( - '--action', choices=['generate', 'transfer', 'all'], default='generate', -) -parser.add_argument( - '--lang', choices=['verilog', 'vhdl'], default='verilog', -) -args = parser.parse_args() - -prj = Project('yosys-vivado') -prj.set_outdir('../../build/yosys-vivado-{}'.format(args.lang)) -prj.set_part('xc7z010-1-clg400') - -if args.lang == 'verilog': - prj.add_vlog_include('../../hdl/headers1') - prj.add_vlog_include('../../hdl/headers2') - prj.add_files('../../hdl/blinking.v') - prj.add_files('../../hdl/top.v') -else: # args.lang == 'vhdl' - prj.add_files('../../hdl/blinking.vhdl', library='examples') - prj.add_files('../../hdl/examples_pkg.vhdl', library='examples') - prj.add_files('../../hdl/top.vhdl') - -prj.add_files('../vivado/zybo.xdc') -prj.set_top('Top') - -if args.action in ['generate', 'all']: - prj.generate() - -if args.action in ['transfer', 'all']: - prj.transfer() From 1dbe60e54676d942edf4c8aaefcd6cf8d57fbd77 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Mon, 3 Mar 2025 20:33:41 -0300 Subject: [PATCH 253/254] ci: add to publish releases into PyPi --- .github/workflows/lint.yml | 2 +- .github/workflows/pypi.yaml | 30 ++++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/pypi.yaml diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 3907a5c4..db3c4144 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -11,5 +11,5 @@ jobs: uses: actions/checkout@v4 - name: Install dependencies run: pip install pycodestyle pylint - - name: Run linters + - name: Run linting run: make lint diff --git a/.github/workflows/pypi.yaml b/.github/workflows/pypi.yaml new file mode 100644 index 00000000..21379087 --- /dev/null +++ b/.github/workflows/pypi.yaml @@ -0,0 +1,30 @@ +name: Publish Package to PyPI + +on: + push: + tags: + - '*.*.*' + +jobs: + publish: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install setuptools wheel twine + - name: Build package + run: | + python setup.py sdist bdist_wheel + - name: Upload package to PyPI + run: | + twine upload dist/* + env: + TWINE_USERNAME: __token__ + TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }} From 63fe9b4369fd713ad7ecf9d97146b079a0481b76 Mon Sep 17 00:00:00 2001 From: "Rodrigo A. Melo" Date: Mon, 3 Mar 2025 20:55:45 -0300 Subject: [PATCH 254/254] Prepare for upcoming release --- README.md | 17 ++++++++++++----- pyfpga/__init__.py | 2 +- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 28441fcf..f12caa67 100644 --- a/README.md +++ b/README.md @@ -49,15 +49,23 @@ For a comprehensive list of supported tools, features and limitations, please re ## Installation -PyFPGA requires Python>=3.8. +> **NOTE:** PyFPGA requires Python >= 3.8. -At the moment, it's only available as a git repository hosted on GitHub. It can be installed with pip: +PyFPGA can be installed in several ways: + +1. From PyPi using pip: + +``` +pip install pyfpga +``` + +2. From the GitHub repository: ``` pip install 'git+https://github.com/PyFPGA/pyfpga#egg=pyfpga' ``` -Alternatively, you can get a copy of the repository either through git clone or downloading a tarball/zipfile, and then: +3. Clone/download the repository and install it manually: ``` git clone https://github.com/PyFPGA/pyfpga.git @@ -65,8 +73,7 @@ cd pyfpga pip install -e . ``` -> With `-e` (`--editable`) your application is installed into site-packages via a kind of symlink. -> That allows pulling changes through git or changing the branch, avoiding the need to reinstall the package. +> **NOTE:** with `-e` (`--editable`), the application is installed into site-packages via a symlink, which allows you to pull changes through git or switch branches without reinstalling the package. ## Similar projects diff --git a/pyfpga/__init__.py b/pyfpga/__init__.py index b9bae46e..7400b52f 100644 --- a/pyfpga/__init__.py +++ b/pyfpga/__init__.py @@ -1,3 +1,3 @@ """PyFPGA""" -__version__ = '0.3.0-dev' +__version__ = '0.3.0'