Skip to content

Commit

Permalink
Merge pull request #1 from NikLeberg/ci/dood
Browse files Browse the repository at this point in the history
ci: dood
  • Loading branch information
NikLeberg authored Dec 27, 2023
2 parents 4a7c4ad + 6c7ace8 commit 3c473ca
Show file tree
Hide file tree
Showing 12 changed files with 161 additions and 85 deletions.
43 changes: 30 additions & 13 deletions .devcontainer/.env
Original file line number Diff line number Diff line change
@@ -1,32 +1,49 @@
# Common arguments valid for all on-demand tools.
function get_common_args () {
common_vols="--volumes-from $(cat /proc/self/cgroup | head -n 1 | cut -d '/' -f3)"
if [ -n "$GITHUB_ACTIONS" ]; then
common_vols="--volume /home/runner/work:/__w"
common_misc="--workdir $(pwd) --rm"
else
common_vols="--volumes-from $(cat /proc/self/cgroup | head -n 1 | cut -d '/' -f3)"
common_misc="--workdir $(pwd) --interactive --tty --rm"
fi
common_disp="--env=DISPLAY=:0 --volume=/tmp/.X11-unix/:/tmp/.X11-unix/"
common_misc="--workdir $(pwd) --interactive --tty --rm"
common_args="$common_vols $common_disp $common_misc"
echo $common_args
}
export -f get_common_args

# Python interpreter, programming language.
function python () {
python_args="--hostname python --entrypoint python $(get_common_args) -e LIBS -e LIB_PATHS -e IGNORED_FILES"
docker run $python_args python:slim $*
}
export -f python
function python_bash () {
python_args="--hostname python --entrypoint bash $(get_common_args)"
docker run $python_args python:slim $*
}
export -f python_bash

# QuestaSim tool, simulation of HDL.
function vsim () {
questa_args="--hostname questasim --mac-address=00:ab:ab:ab:ab:ab $(get_common_args)"
docker run $questa_args ghcr.io/nikleberg/questasim:staging $*
docker run $questa_args ghcr.io/nikleberg/questasim $*
}
export -f vsim
function vcom () {
questa_args="--hostname questasim --entrypoint vcom $(get_common_args)"
docker run $questa_args ghcr.io/nikleberg/questasim:staging $*
docker run $questa_args ghcr.io/nikleberg/questasim $*
}
export -f vcom
function questa_make () {
questa_args="--hostname questasim --mac-address=00:ab:ab:ab:ab:ab --entrypoint make $(get_common_args)"
docker run $questa_args ghcr.io/nikleberg/questasim:staging $*
docker run $questa_args ghcr.io/nikleberg/questasim $*
}
export -f questa_make
function questa_bash () {
questa_args="--hostname questasim --mac-address=00:ab:ab:ab:ab:ab --entrypoint bash $(get_common_args)"
docker run $questa_args ghcr.io/nikleberg/questasim:staging $*
docker run $questa_args ghcr.io/nikleberg/questasim $*
}
export -f questa_bash

Expand All @@ -37,42 +54,42 @@ function command_not_found_handle () {
if [[ $1 =~ ^quartus.*$ ]]; then
quartus_args="--hostname quartus --entrypoint $1 $(get_common_args)"
shift
docker run $quartus_args ghcr.io/nikleberg/quartus:staging $*
docker run $quartus_args ghcr.io/nikleberg/quartus $*
return
fi
return 127 # not a quartus command
}
export -f command_not_found_handle
function quartus_make () {
quartus_args="--hostname quartus --entrypoint make $(get_common_args)"
docker run $quartus_args ghcr.io/nikleberg/quartus:staging $*
docker run $quartus_args ghcr.io/nikleberg/quartus $*
}
export -f quartus_make
function quartus_bash () {
quartus_args="--hostname quartus --entrypoint bash $(get_common_args)"
docker run $quartus_args ghcr.io/nikleberg/quartus:staging $*
docker run $quartus_args ghcr.io/nikleberg/quartus $*
}
export -f quartus_bash

# Make & GCC for Host + RISC-V cross-compiler.
function riscv32-unknown-elf-gcc () {
riscv_args="--hostname riscv-gcc --entrypoint riscv32-unknown-elf-gcc $(get_common_args)"
docker run $riscv_args ghcr.io/nikleberg/riscv-gcc:staging $*
docker run $riscv_args ghcr.io/nikleberg/riscv-gcc $*
}
export -f riscv32-unknown-elf-gcc
function openocd () {
riscv_args="--hostname riscv-gcc --entrypoint openocd $(get_common_args)"
docker run $riscv_args ghcr.io/nikleberg/riscv-gcc:staging $*
docker run $riscv_args ghcr.io/nikleberg/riscv-gcc $*
}
export -f openocd
function riscv_make () {
riscv_args="--hostname riscv-gcc --entrypoint make $(get_common_args)"
docker run $riscv_args ghcr.io/nikleberg/riscv-gcc:staging $*
docker run $riscv_args ghcr.io/nikleberg/riscv-gcc $*
}
export -f riscv_make
function riscv_bash () {
riscv_args="--hostname riscv-gcc --entrypoint bash $(get_common_args)"
docker run $riscv_args ghcr.io/nikleberg/riscv-gcc:staging $*
docker run $riscv_args ghcr.io/nikleberg/riscv-gcc $*
}
export -f riscv_bash

Expand Down
4 changes: 3 additions & 1 deletion .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
steps:
- uses: actions/checkout@v3
with:
submodules: recursive
submodules: true

- name: Build Software (for Sim)
run: |
Expand All @@ -26,12 +26,14 @@ jobs:
run: |
cd build
make questa work.top
make questa test
make clean
- name: Run Simulation (GHDL)
run: |
cd build
make oss work.top
make oss test
make clean
- name: Build Software (for Syn)
Expand Down
99 changes: 53 additions & 46 deletions scripts/dep_parse.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,36 @@
#!/usr/bin/env python3

import sys, re, os
import networkx as nx
from pathlib import Path

class _DiGraph:
# modelled after networkx
nodes = {}
edges = []

def add_node(self, node, **kwargs):
self.nodes[node] = kwargs

def remove_node(self, node):
self.nodes.pop(node, None)
self.edges = [
(n_from, n_to) for (n_from, n_to) in self.edges
if n_from != node and n_to != node
]

def add_edge(self, node_from, node_to):
for node in [node_from, node_to]:
if not node in self.nodes:
self.add_node(node)
self.edges.append((node_from, node_to))

def copy(self):
return self.nodes.copy()

def out_edges(self, node):
return [(n_from, n_to) for (n_from, n_to) in self.edges if n_from == node]


class VHDLDependencyParser:
TESTBENCH_FILE_REGEX = r"(_[tT][bB]\.)|(\/[tT][bB]_)"

Expand Down Expand Up @@ -36,7 +63,7 @@ class VHDLDependencyParser:
]

def __init__(self):
self._dep_graph = nx.DiGraph()
self._dep_graph = _DiGraph()

def glob_parse(self, path, library="work", ignore=None):
if isinstance(path, str):
Expand Down Expand Up @@ -85,19 +112,9 @@ def remove(self, units=["ieee", "std"]):
if any([n.startswith(unit + ".") for unit in units]):
self._dep_graph.remove_node(n)

def deps_to_dot(self, output_file="deps_graph.dot"):
nx.drawing.nx_pydot.write_dot(self._dep_graph, output_file)

def deps_of(self, unit):
dep_nodes = nx.descendants(self._dep_graph, unit) | {unit}
subgraph = self._dep_graph.subgraph(dep_nodes)
ordered_nodes = nx.topological_sort(subgraph)
# all_files = nx.get_node_attributes(self._dep_graph, "file")
# ordered_files = [file for node in ordered_nodes if (file := all_files.get(node)) is not None]
return list(ordered_nodes)

def write_makefile_rules(self):
for n in self._dep_graph.nodes():
def write_makefile_rules(self, file):
f = open(file, mode="w")
for n in self._dep_graph.nodes:
node = self._dep_graph.nodes[n]
# name of the design unit (entity, architecture, package, etc.)
design_unit = n.replace("*", "ANY") # make does not like "*"
Expand All @@ -109,32 +126,36 @@ def write_makefile_rules(self):
obj_file = "obj/" + str(file.relative_to(root)) + ".o"
# ignore referenced design units that we do not know a file for
if not "ANY" in design_unit and not obj_file:
print(f"Referenced design unit '{design_unit}' is not defined in any file. Ignoring.", file=sys.stderr)
print(f"Referenced design unit '{design_unit}' is not defined in any file. Ignoring.")
continue
# print design unit object dependencies
# <design_unit>: <object_file_dependencies>
# @touch <design_unit>
print(f"du/{design_unit}: {obj_file}")
print(f"\t@echo [DU] {design_unit}")
print("\t@mkdir -p $(@D)")
print("\t@touch $@")
print(f"du/{design_unit}: {obj_file}", file=f)
print(f"\t@echo [DU] {design_unit}", file=f)
print("\t@mkdir -p $(@D)", file=f)
print("\t@touch $@", file=f)
# print obj file dependency-only rule
# <obj_file>: <design_unit_dependencies>
if obj_file:
print(f"{obj_file}:", end=" ")
print(f"{obj_file}:", end=" ", file=f)
for (_, du_dep) in self._dep_graph.out_edges(n):
node_dep = self._dep_graph.nodes[du_dep]
# If dependency (e.g. entity and architecture) are in the
# same file, then ignore it, compiling the file will resolve
# it. If not done then make warns about circular rules.
if node.get("file") != node_dep.get("file"):
print("du/" + du_dep.replace("*", "ANY"), end=" ")
print("")
print("du/" + du_dep.replace("*", "ANY"), end=" ", file=f)
print("", file=f)
# add testbenches to OBJS_TB, other sources to OBJS
if re.search(self.TESTBENCH_FILE_REGEX, obj_file, re.IGNORECASE):
print(f"OBJS_TB += {obj_file}")
print(f"OBJS_TB += {obj_file}", file=f)
# if this is a entity (first level), then add to testbenches
if design_unit.count(".") == 1:
print(f"TESTBENCHES += {design_unit}", file=f)
else:
print(f"OBJS += {obj_file}")
print(f"OBJS += {obj_file}", file=f)
f.close()

def _parse_file(self, filepath, library="work"):
with open(filepath, mode="r", encoding="utf-8") as file:
Expand All @@ -152,16 +173,10 @@ def _parse_src_to_defines(self, src, library):
for regex in self.PACKAGE_DEF_REGEX:
for match in re.finditer(regex, src, re.IGNORECASE):
defines.add(f"{library}.{match.group('name')}")
# # defining any entity?
# for regex in self.ENTITY_DEF_REGEX:
# for match in re.finditer(regex, src, re.IGNORECASE):
# defines.add(f"{library}.{match.group('name')}")
# # automatically also defines the "catch-all" architecture
# uses.add(f"{library}.{match.group('name')}.*")
# # defining any architecture?
# for regex in self.ARCH_DEF_REGEX:
# for match in re.finditer(regex, src, re.IGNORECASE):
# defines.add(f"{library}.{match.group('entity')}.{match.group('name')}")
# defining any entity?
for regex in self.ENTITY_DEF_REGEX:
for match in re.finditer(regex, src, re.IGNORECASE):
defines.add(f"{library}.{match.group('name')}")
return defines

def _parse_src_to_uses(self, src):
Expand All @@ -188,12 +203,6 @@ def _parse_src_to_defines_and_uses(self, src, library):
for match in re.finditer(regex, src, re.IGNORECASE):
defines.add(f"{library}.{match.group('name')}.body")
uses.add(f"{library}.{match.group('name')}")
# defining any entity?
for regex in self.ENTITY_DEF_REGEX:
for match in re.finditer(regex, src, re.IGNORECASE):
defines.add(f"{library}.{match.group('name')}")
# automatically also defines the "catch-all" architecture
# uses.add(f"{library}.{match.group('name')}.*")
# defining any architecture?
for regex in self.ARCH_DEF_REGEX:
for match in re.finditer(regex, src, re.IGNORECASE):
Expand All @@ -209,23 +218,22 @@ def _parse_src_to_defines_and_uses(self, src, library):
def _is_refering_to(self, u, v):
if "*" in u and not "*" in v:
# convert to regex pattern
pattern = u.replace(".", "\.").replace("*", ".+")
pattern = u.replace(".", "\\.").replace("*", ".+")
match = re.search(pattern, v, re.IGNORECASE)
if match:
return True
return False


if __name__ == "__main__":
print("Scanning dependencies...", file=sys.stderr)
print("Scanning dependencies...")

vhdl_parser = VHDLDependencyParser()

# Call this script with these environment variables set:
# - LIBS: space separated list of library names
# - LIB_PATHS: space separated list of corresponding library paths
# - IGNORED_FILES: space separated list of files or file patterns to ignore

libs = os.getenv("LIBS", "").split()
paths = os.getenv("LIB_PATHS", "").split()
ignore = os.getenv("IGNORED_FILES", "").split()
Expand All @@ -243,5 +251,4 @@ def _is_refering_to(self, u, v):
vhdl_parser.remove(["ieee", "std"])
vhdl_parser.resolve()

vhdl_parser.deps_to_dot()
vhdl_parser.write_makefile_rules()
vhdl_parser.write_makefile_rules("deps.d")
3 changes: 3 additions & 0 deletions scripts/makefile.def
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ IGNORED_FILES += ../lib/neorv32/sim/simple/neorv32_tb.simple.vhd
IGNORED_FILES += ../lib/neorv32/sim/neorv32_tb.vhd
IGNORED_FILES += ../lib/neorv32/sim/uart_rx_pkg.vhd
IGNORED_FILES += ../lib/neorv32/sim/uart_rx.vhd
IGNORED_FILES += ../lib/neorv32/rtl/core/mem/neorv32_imem.legacy.vhd
IGNORED_FILES += ../lib/neorv32/rtl/core/mem/neorv32_dmem.legacy.vhd
IGNORED_FILES += ../vhdl/vga/tb/vga_tb.vhdl
# Toplevel of design.
TOP ?= top

Expand Down
37 changes: 24 additions & 13 deletions scripts/makefile.inc
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ include $(PROJ_ROOT)/scripts/makefile.def

$(info Running main Makefile.)

# Define bash as the shell to use for sub-shells. The flag --rcfile is set to
# the .env file containing aliases of commands for running in docker containers.
SHELL = /bin/bash
.SHELLFLAGS = --rcfile ../.devcontainer/.env -lc
# Define bash as the shell to use for sub-shells. The environment variable
# $BASH_ENV points to the .env file containing aliases of commands for running
# in docker containers. These aliases can then be used in recipies.
SHELL = BASH_ENV=../.devcontainer/.env /bin/bash

# Helper variables.
tab := $(shell echo -e "\t")
Expand Down Expand Up @@ -33,34 +33,45 @@ help:
# Evaluate dependencies of relevant library files on change.
DEP_PARSE_SCRIPT := $(PROJ_ROOT)/scripts/dep_parse.py
deps.d: $(DEP_PARSE_SCRIPT) $(VHDL_FILES) $(PSL_FILES)
@$(DEP_PARSE_SCRIPT) > deps.d
MAKE_CLEAN += deps.d deps_graph.dot
@python $(DEP_PARSE_SCRIPT)
MAKE_CLEAN += deps.d

-include last.tool
LAST_TOOL ?=
MAKE_CLEAN += last.tool

.PHONY: questa
questa: deps.d
questa: deps.d questa_tool
@questa_make -f $(PROJ_ROOT)/scripts/makefile.questa $(MAKECMDGOALS) PROJ_ROOT=$(PROJ_ROOT)

.PHONY: oss
oss: deps.d
oss: deps.d oss_tool
@oss_make -f $(PROJ_ROOT)/scripts/makefile.oss $(MAKECMDGOALS) PROJ_ROOT=$(PROJ_ROOT)

.PHONY: quartus
quartus: deps.d
quartus: deps.d quartus_tool
@quartus_make -f $(PROJ_ROOT)/scripts/makefile.quartus $(MAKECMDGOALS) PROJ_ROOT=$(PROJ_ROOT)

%_tool:
@test -z "$(LAST_TOOL)" || test "$(LAST_TOOL)" == "$*"
@echo "LAST_TOOL=$*" > last.tool

# Do nothing targets, they are defined in the tool makefiles.
.PHONY: sim test
sim test:
@

.PHONY: clean
clean:
clean: $(LAST_TOOL)_clean
@rm -f $(MAKE_CLEAN)
@rm -rf du/
@rm -rf obj/
@questa_make -f $(PROJ_ROOT)/scripts/makefile.questa clean
@oss_make -f $(PROJ_ROOT)/scripts/makefile.oss clean
@quartus_make -f $(PROJ_ROOT)/scripts/makefile.quartus clean

_clean:
@

%_clean:
@$*_make -f $(PROJ_ROOT)/scripts/makefile.$* clean

# Catch-All target. Actual work should be done in sub-makes.
%:
Expand Down
Loading

0 comments on commit 3c473ca

Please sign in to comment.