Skip to content

Commit

Permalink
Merge pull request #383 from UebelAndre/verilator
Browse files Browse the repository at this point in the history
Delete non-deterministic outputs from VerilatorCompile actions
  • Loading branch information
QuantamHD authored Feb 26, 2025
2 parents caa84ee + 8e2b7ee commit 74ab54c
Show file tree
Hide file tree
Showing 6 changed files with 269 additions and 67 deletions.
23 changes: 20 additions & 3 deletions verilator/defs.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

"""Functions for verilator."""

load("@bazel_skylib//rules:common_settings.bzl", "BuildSettingInfo")
load("@bazel_tools//tools/cpp:toolchain_utils.bzl", "find_cpp_toolchain")
load("@rules_cc//cc:defs.bzl", "CcInfo")
load("//verilog:defs.bzl", "VerilogInfo")
Expand Down Expand Up @@ -122,6 +123,7 @@ def _verilator_cc_library(ctx):
prefix = "V" + ctx.attr.module_top

args = ctx.actions.args()
args.add(verilator_toolchain.verilator)
args.add("--no-std")
args.add("--cc")
args.add("--Mdir", verilator_output.path)
Expand All @@ -134,22 +136,27 @@ def _verilator_cc_library(ctx):
args.add_all(verilator_toolchain.extra_vopts)
args.add_all(ctx.attr.vopts, expand_directories = False)

env = {}
if verilator_toolchain._avoid_nondeterministic_outputs:
env["VERILATOR_AVOID_NONDETERMINISTIC_OUTPUTS"] = "1"

ctx.actions.run(
arguments = [args],
mnemonic = "VerilatorCompile",
executable = verilator_toolchain.verilator,
executable = ctx.executable._process_wrapper,
tools = verilator_toolchain.all_files,
inputs = verilog_files,
outputs = [verilator_output],
progress_message = "[Verilator] Compiling {}".format(ctx.label),
env = env,
)

verilator_output_cpp = ctx.actions.declare_directory(ctx.label.name + "_cpp")
verilator_output_hpp = ctx.actions.declare_directory(ctx.label.name + "_h")

cp_args = ctx.actions.args()
cp_args.add("--src_output", verilator_output_cpp.path)
cp_args.add("--hdr_output", verilator_output_hpp.path)
cp_args.add(verilator_output_cpp.path, format = "--src_output=%s")
cp_args.add(verilator_output_hpp.path, format = "--hdr_output=%s")
cp_args.add_all([verilator_output], map_each = _only_cpp, format_each = "--src=%s")
cp_args.add_all([verilator_output], map_each = _only_hpp, format_each = "--hdr=%s")

Expand Down Expand Up @@ -208,6 +215,12 @@ verilator_cc_library = rule(
executable = True,
default = Label("//verilator/private:verilator_copy_tree"),
),
"_process_wrapper": attr.label(
doc = "The Verilator process wrapper binary.",
executable = True,
cfg = "exec",
default = Label("//verilator/private:verilator_process_wrapper"),
),
},
provides = [
CcInfo,
Expand All @@ -228,12 +241,16 @@ def _verilator_toolchain_impl(ctx):
deps = ctx.attr.deps,
extra_vopts = ctx.attr.extra_vopts,
all_files = all_files,
_avoid_nondeterministic_outputs = ctx.attr.avoid_nondeterministic_outputs[BuildSettingInfo].value,
)]

verilator_toolchain = rule(
doc = "Define a Verilator toolchain.",
implementation = _verilator_toolchain_impl,
attrs = {
"avoid_nondeterministic_outputs": attr.label(
default = Label("//verilator/settings:avoid_nondeterministic_outputs"),
),
"deps": attr.label_list(
doc = "Global Verilator dependencies to link into downstream targets.",
providers = [CcInfo],
Expand Down
12 changes: 9 additions & 3 deletions verilator/private/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
load("@rules_python//python:defs.bzl", "py_binary")
load("@rules_cc//cc:defs.bzl", "cc_binary")

py_binary(
cc_binary(
name = "verilator_copy_tree",
srcs = ["verilator_copy_tree.py"],
srcs = ["verilator_copy_tree.cc"],
visibility = ["//visibility:public"],
)

cc_binary(
name = "verilator_process_wrapper",
srcs = ["verilator_process_wrapper.cc"],
visibility = ["//visibility:public"],
)
97 changes: 97 additions & 0 deletions verilator/private/verilator_copy_tree.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/**
* @file verilator_copy_tree.cc
* @brief A tool for copying generated verilator outputs into unique source and header directories.
*/

#include <sys/stat.h>

#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iostream>
#include <string>
#include <vector>

#ifdef _WIN32
#include <direct.h>
#define mkdir(dir, mode) _mkdir(dir)
#else
#include <unistd.h>
#endif

void create_directory(const std::string& path) {
if (mkdir(path.c_str(), 0700) != 0 && errno != EEXIST) {
std::cerr << "Error creating directory: " << path << std::endl;
std::exit(1);
}
}

void copy_file(const std::string& src, const std::string& dest) {
std::ifstream src_stream(src, std::ios::binary);
std::ofstream dest_stream(dest, std::ios::binary);
if (!src_stream || !dest_stream) {
std::cerr << "Error copying " << src << " to " << dest << std::endl;
return;
}
dest_stream << src_stream.rdbuf();
}

int main(int argc, char* argv[]) {
if (argc < 5) {
std::cerr << "Usage: " << argv[0]
<< " --src_output=<src_dir> --hdr_output=<hdr_dir> --src="
"<file1> ... --hdr=<file2> ..."
<< std::endl;
return 1;
}

std::string src_output = {};
std::string hdr_output = {};
std::vector<std::string> srcs = {};
std::vector<std::string> hdrs = {};

for (int i = 1; i < argc; ++i) {
std::string arg = argv[i];
size_t pos = arg.find('=');
if (pos != std::string::npos) {
std::string key = arg.substr(0, pos);
std::string value = arg.substr(pos + 1);
if (key == "--src_output") {
src_output = value;
} else if (key == "--hdr_output") {
hdr_output = value;
} else if (key == "--src") {
srcs.push_back(value);
} else if (key == "--hdr") {
hdrs.push_back(value);
} else {
std::cerr << "Unexpected argument: " << arg << std::endl;
return 1;
}
} else {
std::cerr << "Unexpected argument: " << arg << std::endl;
return 1;
}
}

if (src_output.empty() || hdr_output.empty()) {
std::cerr << "Both --src_output and --hdr_output must be specified."
<< std::endl;
return 1;
}

create_directory(src_output);
create_directory(hdr_output);

for (const std::string& file : srcs) {
copy_file(file,
src_output + "/" + file.substr(file.find_last_of("/\\") + 1));
}

for (const std::string& file : hdrs) {
copy_file(file,
hdr_output + "/" + file.substr(file.find_last_of("/\\") + 1));
}

return 0;
}
61 changes: 0 additions & 61 deletions verilator/private/verilator_copy_tree.py

This file was deleted.

132 changes: 132 additions & 0 deletions verilator/private/verilator_process_wrapper.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
/**
* @file verilator_process_wrapper.cc
* @brief A process wrapper for the `VerilatorCompile` Bazel action.
*/

#include <cstdlib>
#include <iostream>
#include <string>
#include <vector>

#ifdef _WIN32
#include <windows.h>
#else
#include <dirent.h>
#include <unistd.h>
#endif

/**
* @brief Checks if a filename ends with any of the given suffixes.
*
* @param filename The name of the file.
* @param suffixes A vector of suffixes to match.
* @return true if the filename ends with any suffix, false otherwise.
*/
bool ends_with_any(const std::string& filename,
const std::vector<std::string>& suffixes) {
for (const auto& suffix : suffixes) {
if (filename.size() >= suffix.size() &&
filename.compare(filename.size() - suffix.size(), suffix.size(),
suffix) == 0) {
return true;
}
}
return false;
}

/**
* @brief Deletes files in the specified directory that match given suffixes.
*
* @param dir The directory to scan for matching files.
* @param suffixes The list of suffixes to check for deletion.
* * @return A non-zero exit code if any issues occurred.
*/
int delete_matching_files(const std::string& dir,
const std::vector<std::string>& suffixes) {
if (dir.empty()) return 0;

#ifdef _WIN32
std::string search_path = dir + "\\*";
WIN32_FIND_DATAA find_data = {};
HANDLE h_find = FindFirstFileA(search_path.c_str(), &find_data);

if (h_find != INVALID_HANDLE_VALUE) {
do {
std::string filename = find_data.cFileName;
if (ends_with_any(filename, suffixes)) {
std::string file_path = dir + "\\" + filename;
if (!DeleteFileA(file_path.c_str())) {
std::cerr << "Error: Failed to delete: " << file_path
<< std::endl;
return 1;
}
}
} while (FindNextFileA(h_find, &find_data) != 0);
FindClose(h_find);
}
#else
DIR* dir_stream = opendir(dir.c_str());
if (!dir_stream) {
std::cerr << "Error accessing directory: " << dir << std::endl;
return 1;
}

struct dirent* entry = nullptr;
while ((entry = readdir(dir_stream)) != nullptr) {
std::string filename = entry->d_name;
if (ends_with_any(filename, suffixes)) {
std::string file_path = dir + "/" + filename;
int result = remove(file_path.c_str());
if (result != 0) {
std::cerr << "Error: Failed to delete: " << file_path
<< std::endl;
return result;
}
}
}
closedir(dir_stream);
#endif
return 0;
}

int main(int argc, char* argv[]) {
std::string output_dir{};
std::vector<std::string> command{};

// Parse arguments
for (int i = 1; i < argc; ++i) {
std::string arg = argv[i];

command.push_back(arg);

// Store the output directory value
if (arg == "--Mdir" && i + 1 < argc) {
output_dir = argv[i + 1];
}
}

if (command.empty()) {
std::cerr << "Error: No command provided to execute." << std::endl;
return 1;
}

// Execute verilator command.
std::string cmd = {};
for (const std::string& part : command) {
cmd += part + " ";
}
int result = std::system(cmd.c_str());
if (result != 0) {
return result;
}

// Delete any non-deterministic files.
if (std::getenv("VERILATOR_AVOID_NONDETERMINISTIC_OUTPUTS") != nullptr) {
std::vector<std::string> suffixes = {"__verFiles.dat"};
if (delete_matching_files(output_dir, suffixes)) {
return 1;
}
}

return 0;
}
11 changes: 11 additions & 0 deletions verilator/settings/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
load("@bazel_skylib//rules:common_settings.bzl", "bool_flag")

package(default_visibility = ["//visibility:public"])

# A build flag for deleting files known to be non-deterministic from `VerilatorCompile`
# actions such as the `__verFiles.dat` file which contains timing data for each generated
# file.
bool_flag(
name = "avoid_nondeterministic_outputs",
build_setting_default = False,
)

0 comments on commit 74ab54c

Please sign in to comment.