4 Commits

Author SHA1 Message Date
033dede087 Add copy feature 2024-03-20 17:36:13 +01:00
a8ed470e7d Updated Makefile for consistent build output paths and dynamic project references
Enhanced the Makefile to use a centralized `BUILD_DIR` variable for executable paths, increasing consistency across the build process. Adjusted the project reference generation to dynamically pair test files with their respective libraries, ensuring a more accurate and maintainable build configuration. This change streamlines the build workflow and mitigates potential errors due to path mismatches or hard-coded library links.
2024-03-10 16:53:24 +01:00
139e4718fa Update Makefile to version 1.1.1 2024-03-10 16:11:26 +01:00
11d446ec3e Update Makefile for ISIM test enhancements and version bump
**This changes based on [Wayne Booth](https://github.com/WayneBooth/Xilinx-ISE-Makefile/tree/master)

Introduced support for running and building individual ISIM testbenches to streamline testing of VHDL and Verilog modules. The update modifies the Makefile to include options for a graphical user interface and command extraction from testbench files. Simplified the `test` target into `buildtest` and `runtest` targets for better modularity and clearer separation of the build and execution phases. Also incremented the Makefile version to reflect these significant changes to the testing workflow.
2024-03-10 16:09:21 +01:00
18 changed files with 234 additions and 659 deletions

6
.gitignore vendored
View File

@@ -1,5 +1 @@
working/
reports/
*__pycache__*
*.venv
.doit.db
working/

View File

@@ -12,7 +12,7 @@
# Version
###########################################################################
Makefile_Version := 1.1.5
Makefile_Version := 1.1.3
$(info ISE Makefile Version: $(Makefile_Version))
###########################################################################
@@ -40,7 +40,6 @@ endif
TOPLEVEL ?= $(PROJECT)
CONSTRAINTS ?= $(PROJECT).ucf
BUILD_DIR ?= working
REPORT_DIR ?= reports
BITFILE ?= $(BUILD_DIR)/$(PROJECT).bit
COMMON_OPTS ?= -intstyle xflow
@@ -53,7 +52,7 @@ TRACE_OPTS ?= -v 3 -n 3
FUSE_OPTS ?= -incremental
ISIM_OPTS ?= -gui
ISIM_CMD ?=
ISIM_CMD ?= vcd dumpfile $@.vcd\nvcd dumpvars -m /UUT\nrun all\nvcd dumpflush\nquit
PROGRAMMER ?= none
PROGRAMMER_PRE ?=
@@ -144,12 +143,10 @@ default: $(BITFILE)
clean:
rm -rf $(BUILD_DIR)
rm -rf $(REPORT_DIR)
$(BUILD_DIR)/$(PROJECT).prj: ../project.cfg
@echo "Updating $@"
@mkdir -p $(BUILD_DIR)
@mkdir -p $(REPORT_DIR)
@rm -f $@
@$(foreach idx,$(shell seq 1 $(words $(V_PATHS))),echo "verilog $(word $(idx),$(V_LIBS)) \"../$(word $(idx),$(V_PATHS))\"" >> $@;)
@$(foreach idx,$(shell seq 1 $(words $(VHD_PATHS))),echo "vhdl $(word $(idx),$(VHD_LIBS)) \"../$(word $(idx),$(VHD_PATHS))\"" >> $@;)
@@ -177,34 +174,29 @@ $(BUILD_DIR)/$(PROJECT).scr: ../project.cfg
$(BITFILE): ../project.cfg $(V_PATHS) $(VHD_PATHS) ../$(CONSTRAINTS) $(BUILD_DIR)/$(PROJECT).prj $(BUILD_DIR)/$(PROJECT).scr
@mkdir -p $(BUILD_DIR)
@mkdir -p $(REPORT_DIR)
$(call RUN,xst) $(COMMON_OPTS) \
-ifn $(PROJECT).scr
@cp ./$(BUILD_DIR)/$(PROJECT).srp $(REPORT_DIR)/$(PROJECT).SynthesisReport
$(call RUN,ngdbuild) $(COMMON_OPTS) $(NGDBUILD_OPTS) \
-p $(TARGET_PART) -uc ../../$(CONSTRAINTS) \
$(PROJECT).ngc $(PROJECT).ngd
$(call RUN,map) $(COMMON_OPTS) $(MAP_OPTS) \
-p $(TARGET_PART) \
-w $(PROJECT).ngd -o $(PROJECT).map.ncd $(PROJECT).pcf
@cp ./$(BUILD_DIR)/$(PROJECT).map.mrp $(REPORT_DIR)/$(PROJECT).MapReport
$(call RUN,par) $(COMMON_OPTS) $(PAR_OPTS) \
-w $(PROJECT).map.ncd $(PROJECT).ncd $(PROJECT).pcf
@cp ./$(BUILD_DIR)/$(PROJECT).par $(REPORT_DIR)/$(PROJECT).PlaceRouteReport
$(call RUN,bitgen) $(COMMON_OPTS) $(BITGEN_OPTS) \
-w $(PROJECT).ncd $(PROJECT).bit
@echo "\e[1;32m============ OK ============\e[m\n\n"
@echo "\e[1;33m============ Reports.. ===========\e[m\n"
@echo "\e[1;97m==== Synthesis Summary Report ====\e[m"
@echo "\e[1;35m ./$(REPORT_DIR)/$(PROJECT).SynthesisReport\e[m\n"
@echo "\e[1;35m ./$(BUILD_DIR)/$(PROJECT).srp\e[m\n"
@echo "\e[1;97m======= Map Summary Report =======\e[m"
@echo "\e[1;35m ./$(REPORT_DIR)/$(PROJECT).MapReport\e[m\n"
@echo "\e[1;35m ./$(BUILD_DIR)/$(PROJECT).map.mrp\e[m\n"
@echo "\e[1;97m======= PAR Summary Report =======\e[m"
@echo "\e[1;35m ./$(REPORT_DIR)/$(PROJECT).PlaceRouteReport\e[m\n"
@echo "\e[1;35m ./$(BUILD_DIR)/$(PROJECT).par\e[m\n"
@echo "\e[1;97m===== Pinout Summary Report ======\e[m"
@cp ./$(BUILD_DIR)/$(PROJECT)_pad.txt $(REPORT_DIR)/$(PROJECT).PinoutReport
@echo "\e[1;35m ./$(REPORT_DIR)/$(PROJECT).PinoutReport\e[m\n"
@echo "\e[1;35m ./$(BUILD_DIR)/$(PROJECT)_pad.txt\e[m\n"
copy: $(BITFILE)
@cp $(BITFILE) $(COPY_TARGET_DIR)/$(PROJECT).bit
@echo "\n\e[1;32m= Copy bitfile successful =\e[m\n"
@@ -218,8 +210,7 @@ trace: ../project.cfg $(BITFILE)
$(PROJECT).ncd $(PROJECT).pcf
@echo "\n\e[1;33m============ Reports.. ===========\e[m\n"
@echo "\e[1;97m===== Timing Summary Report ======\e[m"
@cp ./$(BUILD_DIR)/$(PROJECT).twr $(REPORT_DIR)/$(PROJECT).TimingReport
@echo "\e[1;35m ./$(REPORT_DIR)/$(PROJECT).TimingReport\e[m\n"
@echo "\e[1;35m ./$(BUILD_DIR)/$(PROJECT).twr\e[m\n"
test: buildtest runtest

226
README.md
View File

@@ -1 +1,225 @@
**Todo**
# Xilinx ISE Makefile
Tired of clicking around in Xilinx ISE? Run your builds from the command line!
## Forked from..
The original project is located at [Xilinx-ISE-Makefile](https://github.com/duskwuff/Xilinx-ISE-Makefile) and was created by [duskwuff](github.com/duskwuff/).
Many thanks for the good work!
## Requirements
- Xilinx ISE, ideally 14.7 (the final version)
Works great on Linux. Windows Subsystem for Linux is tested and works well.
- GNU (or compatible?) Make
Install this through Cygwin on Windows.
## Creating a project
To start building a project, you will need to create a file `project.cfg` in
the top level of your project. This file is a text file sourced by Make, so
it consists of `KEY = value` pairs. It must define at least the following keys:
- `PROJECT`
The name of the project, used as a name for certain intermediate files, and
as the default name for the top-level module and constraints file.
- `TARGET_PART`
The full part-speed-package identifier for the Xilinx part to be targeted,
e.g. `xc6slx9-2-tqg144`.
- `XILINX`
The path to the appropriate binaries directory of the target Xilinx ISE
install, e.g.
`/cygdrive/c/Xilinx/14.7/ISE_DS/ISE`
or
`/opt/Xilinx/14.7/ISE_DS/ISE`
for typical installs.
- `VSOURCE` and/or `VHDSOURCE`
The space-separated names of all Verilog and/or VHDL source files to be
used in the project.
You can define these on multiple lines using `+=`, e.g.
VSOURCE += foo.v
VSOURCE += bar.v
You can also add a library name to the source file, e.g.
VSOURCE += my_lib:foo.v
VSOURCE += my_lib:bar.v
The default library name is `work`.
A simple `project.cfg` may thus resemble:
PROJECT = example
TARGET_PART = xc6slx9-2-cpg196
XILINX = /cygdrive/c/Xilinx/14.7/ISE_DS/ISE/bin/nt64
VSOURCE = example.v
A number of other keys can be set in the project configuration, including:
- `XILINX_PLATFORM`
The Xilinx name for the platform to build for, e.g. `nt64` or `lin`.
`nt64` is used by default for Windows systems, and `lin64` for Linux
systems, so you only need to set this if you explicitly need to use the
32-bit version of the tools for some reason.
- `TOPLEVEL`
The name of the top-level module to be used in the project.
(Defaults to `$PROJECT`.)
- `CONSTRAINTS`
The name of the constraints file (`.ucf`) to be used for the project.
(Defaults to `$PROJECT.ucf`.)
- `COMMON_OPTS`
Extra command-line options to be passed to all ISE executables. Defaults to
`-intstyle xflow`.
- `XST_OPTS`, `NGDBUILD_OPTS`, `MAP_OPTS`, `PAR_OPTS`, `BITGEN_OPTS`,
`TRACE_OPTS`, `FUSE_OPTS`
Extra command-line options to be passed to the corresponding ISE tools.
Defaults is:
```
XST_OPTS ?=
NGDBUILD_OPTS ?=
MAP_OPTS ?= -detail
PAR_OPTS ?=
BITGEN_OPTS ?=
TRACE_OPTS ?= -v 3 -n 3
FUSE_OPTS ?= -incremental
```
Note that `XST_OPTS` will not appear on the command line during
compilation, as the XST options are embedded in a script file.
`MAP_OPTS` and `PAR_OPTS` can be set to `-mt 2` to use multithreading,
which may speed up compilation of large designs.
`BITGEN_OPTS` can be set to `-g Compress` to apply bitstream compression.
- `PROGRAMMER`
The name of the programmer to be used for `make prog`. Currently supported
values are:
- `impact`
Uses Xilinx iMPACT for programming, using a batch file named
`impact.cmd` by default. The iMPACT command line may be overridden by
setting `IMPACT_OPTS`.
A typical batch file may resemble:
setMode -bscan
setCable -p auto
addDevice -p 1 -file build/projectname.bit
program -p 1
quit
- `digilent`
Uses the Digilent JTAG utility for programming, which must be installed
separately. The name of the board must be set as `DJTG_DEVICE`; the
path to the djtgcfg executable can be set as `DJTG_EXE`, and the index
of the device can be set as `DJTG_INDEX`. You can set the flash index
with `DJTG_FLASH_INDEX`.
- `xc3sprog`
Uses the xc3sprog utility for programming, which must also be installed
separately. The cable name must be set as `XC3SPROG_CABLE`; additional
options can be set as `XC3SPROG_OPTS`.
- `PROGRAMMER_PRE`
A command to be run before programming. This can be used to use `sudo` or
`yes` to confirm programming.
## Targets
The Xilinx ISE Makefile implements the following targets:
- `make default` (or just `make`)
Builds the bitstream.
- `make clean`
Removes the build directory.
- `make prog`
Writes the bitstream to a target device. Requires some additional
configuration; see below for details.
- `make flash`
Writes the bitstream to a flash device.
**This is currently only for digilent implemented.**
## Console output
After a successful build, you will find the paths to the generated **reports** on the console. E.g.:
```
============ Reports.. ===========
==== Synthesis Summary Report ====
./build/Example.srp
======= Map Summary Report =======
./build/Example.map.mrp
======= PAR Summary Report =======
./build/Example.par
===== Pinout Summary Report ======
./build/Example_pad.txt
```
## Unimplemented features
The following features are not currently implemented. (Pull requests are
encouraged!)
- Generation of SPI or other unusual programming files
- CPLD synthesis
- Synthesis tools other than XST
- Display and/or handling of warnings and errors from `build/_xmsgs`
- Running unit tests
- Anything else (open an issue?)
## License
To the extent possible under law, the author(s) have dedicated all copyright
and related and neighboring rights to this software to the public domain
worldwide. This software is distributed without any warranty.
See LICENSE.md for details.

78
dodo.py
View File

@@ -1,78 +0,0 @@
from pathlib import Path
from tasks.generate_prj import generate_prj
from tasks.generate_scr import generate_scr
from tools.config import parse_project_cfg
from tools.defaults import with_defaults
import subprocess
import os
from pathlib import Path
HERE = Path(__file__).parent.resolve() # Verzeichnis, in dem dodo.py liegt
CFG_PATH = HERE.parent / "project.cfg"
TOML_PATH = HERE.parent / "vhdl_ls.toml"
def _get_project_name(cfg_path: Path) -> str:
return with_defaults(parse_project_cfg(cfg_path))["PROJECT"]
def task_generate_prj():
cfg_path = Path(CFG_PATH)
toml_path = Path(TOML_PATH)
project = _get_project_name(cfg_path)
prj_path = Path("working") / f"{project}.prj"
return {
"actions": [(generate_prj, [prj_path, cfg_path, toml_path])],
"file_dep": [cfg_path, toml_path],
"targets": [prj_path],
"verbosity": 2,
}
def task_generate_scr():
cfg_path = Path(CFG_PATH)
project = _get_project_name(cfg_path)
scr_path = Path("working") / f"{project}.scr"
return {
"actions": [(generate_scr, [scr_path, cfg_path])],
"file_dep": [cfg_path],
"targets": [scr_path],
"verbosity": 2,
}
def task_xst():
"""Run XST synthesizer"""
cfg_path = Path(CFG_PATH)
cfg = with_defaults(parse_project_cfg(cfg_path))
build_dir = Path(cfg["BUILD_DIR"])
project = cfg["PROJECT"]
scr_file = f"{project}.scr"
scr_path = build_dir / scr_file
prj_path = build_dir / f"{project}.prj"
xilinx_path = Path(cfg["XILINX"])
xilinx_platform = "lin64" # TODO: dynamisch ermitteln, falls nötig
xst_exe = xilinx_path / "bin" / xilinx_platform / "xst"
common_opts = cfg.get("COMMON_OPTS", "")
def action():
print(f"============ Running XST ============")
print(f"> {xst_exe} {common_opts} -ifn {scr_file}")
subprocess.run(
[str(xst_exe), "-ifn", scr_file],
cwd=build_dir,
check=True
)
return {
"actions": [action],
"file_dep": [scr_path, prj_path], # <== beides!
"targets": [Path(cfg["REPORT_DIR"]) / f"{project}.SynthesisReport"],
"verbosity": 2,
"task_dep": ["generate_scr", "generate_prj"],
}

View File

@@ -1,110 +0,0 @@
## Main settings.. ##
# Project name
# @remark The name of the project is used as default name for the top module and the ucf file
PROJECT =
# Target device
# @example xc3s1200e-4-fg320 | xc5vlx50t-1-ff1136
TARGET_PART =
# Path to the Xilinx ISE installation
XILINX = /opt/Xilinx/14.7/ISE_DS/ISE
# Optional the name of the top module (default is the project name)
# TOPLEVEL =
# Optional the path/name of the ucf file (default is the project name)
# CONSTRAINTS =
# Optional a target to copy the bit file to (make copy)
# COPY_TARGET_DIR =
## ## ## ## ## ## ## ##
# ---------------------
## Source files settings.. ##
# The source files to be compiled
# @example `VSOURCE += src/main.v` (add a single Verilog file per line)
# @example `VHDSOURCE += src/main.vhd` (add a single VHDL file per line)
## Test files settings.. ##
# The testbench files to be compiled
# @example `VTEST += tests/main_tb.v` (add a single Verilog testbench file per line)
# @example `VHDTEST += tests/main_tb.vhd` (add a single VHDL testbench file per line)
## ## ## ## ## ## ## ##
# ---------------------
## ISE executable settings.. ##
# General command line options to be passed to all ISE executables (default is `-intstyle xflow`)
# COMMON_OPTS =
# Options for the XST synthesizer
# @example -register_balancing (yes|no)
# @example -opt_mode (speed|area)
# @example -opt_level (1|2)
XST_OPTS =
# Options for the NGDBuild tool
# NGDBUILD_OPTS =
# Options for the MAP tool
# @example -mt 2 (multi-threading with 2 threads)
# @example -cm speed (speed optimization)
# @example -ol high
# @example -detail
# @example -timing
MAP_OPTS =
# Options for the PAR tool
# @example -mt 2 (multi-threading with 2 threads)
# @example -ol high
PAR_OPTS =
# Options for the BitGen tool
# @example -g Compress (compress bitstream)
# @example -g StartupClk:Cclk (specify the startup clock to onboard clock)
# @example -g StartupClk:JtagClk (specify the startup clock to JTAG clock)
BITGEN_OPTS = -g StartupClk:JtagClk
# Options for the Trace tool
# TRACE_OPTS =
# Options for the Fuse tool
# FUSE_OPTS =
# Options for the ISim simulator
# @example -gui (start the simulator in GUI mode)
# ISIM_OPTS =
# Options for the ISim batch file
# @example vcd dumpfile $@.vcd \n vcd dumpvars -m /UUT \n run all \n vcd dumpflush \n quit
# ISIM_CMD =
## ## ## ## ## ## ## ##
# ---------------------
## Programmer settings.. ##
# The programmer to use
# @example impact | digilent | xc3sprog
# @remark impact is the default Xilinx programmer and you must create a impact.cmd file in the root directory..
PROGRAMMER =
## Digilent JTAG cable settings
# @remark Use the `djtgcfg enum` command to list all available devices
# DJTG_DEVICE = DOnbUsb
# The index of the JTAG device for the `prog` target
# DJTG_INDEX = 0
# The index of the flash device for the `flash` target
# DJTG_FLASH_INDEX = 1
## ## ## ## ## ## ## ##
# ---------------------

View File

@@ -1,3 +0,0 @@
toml
pytest
doit

View File

@@ -1,14 +0,0 @@
import os
from tools.config import get_vhdl_sources_and_tests
from tools.paths import REL_FROM_WORKING_TO_ROOT as REL_ROOT, ROOT, WORKING
from pathlib import Path
def generate_prj(prj_path: Path, *_):
sources, _ = get_vhdl_sources_and_tests()
prj_path.parent.mkdir(parents=True, exist_ok=True)
with prj_path.open("w", encoding="utf-8") as f:
for libname, files in sources.items():
for file in files:
rel_path = os.path.relpath(ROOT / file, WORKING)
f.write(f"vhdl {libname} {rel_path}\n")

View File

@@ -1,33 +0,0 @@
from tools.config import parse_project_cfg
from pathlib import Path
def generate_scr(scr_path: Path, cfg_path: Path):
cfg = parse_project_cfg(cfg_path)
project = cfg["PROJECT"]
top = cfg.get("TOPLEVEL", project)
target_part = cfg["TARGET_PART"]
xst_opts = cfg.get("XST_OPTS", "")
# SCR-Datei vollständig in einer Zeile, wie im Makefile
scr_parts = [
"run",
f"-ifn {project}.prj",
f"-ofn {project}.ngc",
"-ifmt mixed",
]
# Optional: Optionen aus XST_OPTS hinzufügen
if xst_opts.strip():
scr_parts += xst_opts.strip().split()
scr_parts += [
f"-top {top}",
"-ofmt NGC",
f"-p {target_part}",
]
scr_line = " ".join(scr_parts).replace("\n", " ").replace("\r", " ")
scr_path.parent.mkdir(parents=True, exist_ok=True)
scr_path.write_text(scr_line, encoding="utf-8")

View File

@@ -1,32 +0,0 @@
import subprocess
from tools.paths import WORKING, REPORTS, PROJECT_CFG
from tools.config import parse_project_cfg
from tools.defaults import with_defaults
from pathlib import Path
def run_xst():
cfg = with_defaults(parse_project_cfg(PROJECT_CFG))
project = cfg["PROJECT"]
scr_file = f"{project}.scr"
scr_path = WORKING / scr_file
xilinx_path = Path(cfg["XILINX"])
xilinx_platform = "lin64" # TODO: Optional dynamisch machen
xst_exe = xilinx_path / "bin" / xilinx_platform / "xst"
common_opts = cfg.get("COMMON_OPTS", "")
if not xst_exe.exists():
raise FileNotFoundError(f"xst executable not found at {xst_exe}")
if not scr_path.exists():
raise FileNotFoundError(f"SCR file not found: {scr_path}")
print(f"\n============ Running XST ============\n")
print(f"> {xst_exe} {common_opts} -ifn {scr_file}\n")
subprocess.run(
[str(xst_exe), *common_opts.split(), "-ifn", scr_file],
cwd=WORKING,
check=True
)

View File

View File

@@ -1,72 +0,0 @@
from tools.config import parse_vhdl_ls_toml
from pathlib import Path
import tempfile
import textwrap
import pytest
def test_parsing_skips_third_party_libs():
content = textwrap.dedent("""
[libraries.lib]
files = ["src/main.vhd"]
[libraries.XILINX]
files = ["/opt/Xilinx/abc.vhd"]
is_third_party = true
""")
with tempfile.NamedTemporaryFile("w+", suffix=".toml", delete=False) as f:
f.write(content)
f.flush()
result = parse_vhdl_ls_toml(Path(f.name))
assert "work" in result
assert "XILINX" not in result
assert result["work"] == [Path("src/main.vhd")]
def test_parsing_multiple_libraries():
content = textwrap.dedent("""
[libraries.lib]
files = ["src/foo.vhd", "src/bar.vhd"]
[libraries.myip]
files = ["ipcore/top.vhd"]
""")
with tempfile.NamedTemporaryFile("w+", suffix=".toml", delete=False) as f:
f.write(content)
f.flush()
result = parse_vhdl_ls_toml(Path(f.name))
assert "work" in result
assert "myip" in result
assert result["work"] == [Path("src/foo.vhd"), Path("src/bar.vhd")]
assert result["myip"] == [Path("ipcore/top.vhd")]
def test_empty_toml_file():
with tempfile.NamedTemporaryFile("w+", suffix=".toml", delete=False) as f:
f.write("") # Empty file
f.flush()
result = parse_vhdl_ls_toml(Path(f.name))
assert result == {}
def test_missing_file_raises_error():
with pytest.raises(FileNotFoundError):
parse_vhdl_ls_toml(Path("nonexistent.toml"))
def test_libname_translation_to_work():
content = textwrap.dedent("""
[libraries.lib]
files = ["src/xyz.vhd"]
""")
with tempfile.NamedTemporaryFile("w+", suffix=".toml", delete=False) as f:
f.write(content)
f.flush()
result = parse_vhdl_ls_toml(Path(f.name))
assert "work" in result
assert "lib" not in result
assert result["work"] == [Path("src/xyz.vhd")]

View File

@@ -1,47 +0,0 @@
import tempfile
from pathlib import Path
from tasks.generate_prj import generate_prj
def write_file(path: Path, content: str):
path.write_text(content.strip(), encoding="utf-8")
def test_generate_prj_single_lib(tmp_path):
cfg_path = tmp_path / "project.cfg"
toml_path = tmp_path / "vhdl_ls.toml"
prj_path = tmp_path / "working" / "MyProject.prj"
write_file(cfg_path, "PROJECT = MyProject\nTARGET_PART = dummy\nXILINX = /some/path")
write_file(toml_path, """
[libraries.lib]
files = ["src/main.vhd"]
""")
generate_prj(prj_path, cfg_path, toml_path)
assert prj_path.exists()
content = prj_path.read_text()
assert "vhdl work ../src/main.vhd" in content
def test_generate_prj_multiple_libs(tmp_path):
cfg_path = tmp_path / "project.cfg"
toml_path = tmp_path / "vhdl_ls.toml"
prj_path = tmp_path / "working" / "MyProject.prj"
write_file(cfg_path, "PROJECT = MyProject\nTARGET_PART = dummy\nXILINX = /some/path")
write_file(toml_path, """
[libraries.lib]
files = ["src/A.vhd", "src/B.vhd"]
[libraries.otherlib]
files = ["src/C.vhd"]
""")
generate_prj(prj_path, cfg_path, toml_path)
content = prj_path.read_text()
assert "vhdl work ../src/A.vhd" in content
assert "vhdl work ../src/B.vhd" in content
assert "vhdl otherlib ../src/C.vhd" in content

View File

@@ -1,44 +0,0 @@
from pathlib import Path
from tasks.generate_scr import generate_scr
def write_file(path: Path, content: str):
path.write_text(content.strip(), encoding="utf-8")
def test_generate_scr_basic(tmp_path):
cfg_path = tmp_path / "project.cfg"
scr_path = tmp_path / "working" / "MyProject.scr"
write_file(cfg_path, """
PROJECT = MyProject
TARGET_PART = xc3s50-4-pq208
XILINX = /some/path
""")
generate_scr(scr_path, cfg_path)
content = scr_path.read_text()
assert "-ifn MyProject.prj" in content
assert "-ofn MyProject.ngc" in content
assert "-top MyProject" in content
assert "-p xc3s50-4-pq208" in content
def test_generate_scr_with_top_and_opts(tmp_path):
cfg_path = tmp_path / "project.cfg"
scr_path = tmp_path / "working" / "MyProject.scr"
write_file(cfg_path, """
PROJECT = MyProject
TARGET_PART = xc3s200-5-ft256
TOPLEVEL = TopModule
XST_OPTS = -opt_mode Speed -opt_level 2
XILINX = /some/path
""")
generate_scr(scr_path, cfg_path)
content = scr_path.read_text()
assert "-top TopModule" in content
assert "-opt_mode Speed -opt_level 2" in content

View File

@@ -1,51 +0,0 @@
from build.tools.config import parse_project_cfg
from pathlib import Path
import tempfile
import textwrap
def test_parse_complex_project_cfg_correctly():
cfg = textwrap.dedent("""
## Main settings.. ##
PROJECT = SpriteChannel
TARGET_PART = xc3s1200e-4-fg320
XILINX = /opt/Xilinx/14.7/ISE_DS/ISE
TOPLEVEL = SpriteChannel
CONSTRAINTS = src/SpriteChannel.ucf
# Sources (should be ignored)
VHDSOURCE += src/GenericCounter.vhd
VSOURCE += src/something.v
VHDTEST += test/SpriteChannel_tb.vhd
# Options
XST_OPTS += -opt_mode Speed
XST_OPTS += -opt_level 2
MAP_OPTS = -detail -timing -ol high
PAR_OPTS = -ol high
BITGEN_OPTS = -g StartupClk:JtagClk
# Programmer
PROGRAMMER = digilent
""")
with tempfile.NamedTemporaryFile("w+", suffix=".cfg", delete=False) as f:
f.write(cfg)
f.flush()
result = parse_project_cfg(Path(f.name))
assert result["PROJECT"] == "SpriteChannel"
assert result["TARGET_PART"] == "xc3s1200e-4-fg320"
assert result["XILINX"] == "/opt/Xilinx/14.7/ISE_DS/ISE"
assert result["TOPLEVEL"] == "SpriteChannel"
assert result["CONSTRAINTS"] == "src/SpriteChannel.ucf"
assert result["XST_OPTS"] == "-opt_mode Speed -opt_level 2"
assert result["MAP_OPTS"] == "-detail -timing -ol high"
assert result["PAR_OPTS"] == "-ol high"
assert result["BITGEN_OPTS"] == "-g StartupClk:JtagClk"
assert result["PROGRAMMER"] == "digilent"
# Sicherstellen, dass SOURCE-Felder ignoriert wurden
assert "VHDSOURCE" not in result
assert "VSOURCE" not in result
assert "VHDTEST" not in result

View File

View File

@@ -1,88 +0,0 @@
from tools.paths import ROOT, PROJECT_CFG, VHDL_LS_TOML
from pathlib import Path
from typing import Dict, List, Optional
import toml
import re
from typing import Tuple
from tools.defaults import with_defaults
def parse_vhdl_ls_toml(toml_path: Optional[Path] = None) -> Dict[str, List[Path]]:
if toml_path is None:
toml_path = VHDL_LS_TOML
if not toml_path.exists():
raise FileNotFoundError(f"{toml_path} not found.")
with open(toml_path, "r", encoding="utf-8") as f:
raw = toml.load(f)
libraries = raw.get("libraries", {})
parsed: Dict[str, List[Path]] = {}
for libname, content in libraries.items():
if content.get("is_third_party", False):
continue
files = content.get("files", [])
actual_libname = "work" if libname == "lib" else libname
parsed[actual_libname] = [Path(f) for f in files]
return parsed
def parse_project_cfg(cfg_path: Optional[Path] = None) -> Dict[str, str]:
cfg_path = cfg_path or PROJECT_CFG
if not cfg_path.exists():
raise FileNotFoundError(f"{cfg_path} not found.")
result: Dict[str, str] = {}
ignored_keys = {"VHDSOURCE", "VSOURCE", "VHDTEST", "VTEST"}
with cfg_path.open("r", encoding="utf-8") as f:
for line in f:
line = line.strip()
# Kommentare und Leerzeilen überspringen
if not line or line.startswith("#"):
continue
if "+=" in line:
key, value = map(str.strip, line.split("+=", 1))
if key in ignored_keys:
continue
if key in result:
result[key] += f" {value}"
else:
result[key] = value
elif "=" in line:
key, value = map(str.strip, line.split("=", 1))
if key in ignored_keys:
continue
result[key] = value
return result
def get_vhdl_sources_and_tests() -> Tuple[Dict[str, List[Path]], Dict[str, List[Path]]]:
cfg = with_defaults(parse_project_cfg())
test_filter = cfg.get("TEST_FILTER", r"^tests/|_tb\.vhd$") # Default-Fallback
try:
pattern = re.compile(test_filter)
except re.error as e:
raise ValueError(f"Invalid TEST_FILTER regex: {e}")
all_sources = parse_vhdl_ls_toml()
normal_sources: Dict[str, List[Path]] = {}
test_sources: Dict[str, List[Path]] = {}
for lib, files in all_sources.items():
for file in files:
# relative Pfade als Strings prüfen
rel_path = str(file)
if pattern.search(rel_path):
test_sources.setdefault(lib, []).append(file)
else:
normal_sources.setdefault(lib, []).append(file)
return normal_sources, test_sources

View File

@@ -1,43 +0,0 @@
# tools/defaults.py
from typing import Dict
def get_default_config() -> Dict[str, str]:
return {
"TOPLEVEL": "", # wird dynamisch ersetzt
"CONSTRAINTS": "",
"BUILD_DIR": "working",
"REPORT_DIR": "reports",
"COMMON_OPTS": "-intstyle xflow",
"XST_OPTS": "",
"NGDBUILD_OPTS": "",
"MAP_OPTS": "-detail",
"PAR_OPTS": "",
"BITGEN_OPTS": "",
"TRACE_OPTS": "-v 3 -n 3",
"FUSE_OPTS": "-incremental",
"ISIM_OPTS": "-gui",
"ISIM_CMD": "",
"PROGRAMMER": "none",
"PROGRAMMER_PRE": "",
"IMPACT_OPTS": "-batch impact.cmd",
"DJTG_EXE": "djtgcfg",
"DJTG_DEVICE": "DJTG_DEVICE-NOT-SET",
"DJTG_INDEX": "0",
"DJTG_FLASH_INDEX": "1",
"XC3SPROG_EXE": "xc3sprog",
"XC3SPROG_CABLE": "none",
"XC3SPROG_OPTS": "",
}
def with_defaults(project_cfg: Dict[str, str]) -> Dict[str, str]:
merged = get_default_config()
merged.update(project_cfg)
# Fallbacks sicherstellen (kein leerer oder None-Wert bleibt erhalten)
if not merged.get("TOPLEVEL"):
merged["TOPLEVEL"] = merged.get("PROJECT", "")
if not merged.get("CONSTRAINTS"):
merged["CONSTRAINTS"] = f"{merged.get('PROJECT', '')}.ucf"
# Alle Felder final auf gültigen string casten (zur Sicherheit)
return {k: (v if isinstance(v, str) else str(v)) for k, v in merged.items()}

View File

@@ -1,21 +0,0 @@
import os
from pathlib import Path
import sys
# Ermittle absoluten Pfad dieser Datei
HERE = Path(__file__).resolve()
# Gehe zurück von tools/ → build/ → Projekt-Root
ROOT = HERE.parent.parent.parent
# Abgeleitete Pfade
BUILD = ROOT / "build"
WORKING = BUILD / "working"
REPORTS = BUILD / "reports"
# Relativer Pfad von WORKING zurück zur Projektwurzel
REL_FROM_WORKING_TO_ROOT = Path(os.path.relpath(ROOT, WORKING))
# Standard-Konfigurationsdateien
PROJECT_CFG = ROOT / "project.cfg"
VHDL_LS_TOML = ROOT / "vhdl_ls.toml"