diff --git a/tools/dynamatic/dynamatic.cpp b/tools/dynamatic/dynamatic.cpp index eee7b726a2..251718aa2b 100644 --- a/tools/dynamatic/dynamatic.cpp +++ b/tools/dynamatic/dynamatic.cpp @@ -70,6 +70,9 @@ static cl::opt exitOnFailure( "If specified, exits the frontend automatically on command failure"), cl::init(false), cl::cat(mainCategory)); +static constexpr llvm::StringLiteral VHDL("vhdl"); +static constexpr llvm::StringLiteral VERILOG("verilog"); + namespace { enum class CommandResult { SYNTAX_ERROR, FAIL, SUCCESS, EXIT, HELP }; } // namespace @@ -92,12 +95,12 @@ struct FrontendState { std::string dynamaticPath; std::string vivadoPath = "/tools/Xilinx/Vivado/2019.1/"; std::string fpUnitsGenerator = "flopoco"; + llvm::StringLiteral hdl = VHDL; // By default, the clock period is 4 ns double targetCP = 4.0; std::optional sourcePath = std::nullopt; std::string outputDir = "out"; - FrontendState(StringRef cwd) : cwd(cwd), dynamaticPath(cwd) {}; bool sourcePathIsSet(StringRef keyword); @@ -268,14 +271,16 @@ class SetCP : public Command { class SetOutputDir : public Command { public: SetOutputDir(FrontendState &state) - : Command("set-output-dir", "Sets the name of the dir to perform HLS in. If not set, defaults to 'out'", state) { + : Command("set-output-dir", + "Sets the name of the dir to perform HLS in. If not set, " + "defaults to 'out'", + state) { addPositionalArg({"out_dir", "out dir name"}); } CommandResult execute(CommandArguments &args) override; }; - class Compile : public Command { public: static constexpr llvm::StringLiteral FAST_TOKEN_DELIVERY = @@ -646,7 +651,8 @@ CommandResult SetOutputDir::execute(CommandArguments &args) { llvm::StringRef outputDir = args.positionals.front(); // reject trivial bad cases - if (outputDir.empty() || outputDir == "." || outputDir == ".." || outputDir.endswith("/")) + if (outputDir.empty() || outputDir == "." || outputDir == ".." || + outputDir.endswith("/")) return CommandResult::FAIL; // reject illegal chars @@ -733,6 +739,7 @@ CommandResult WriteHDL::execute(CommandArguments &args) { if (auto it = args.options.find(HDL); it != args.options.end()) { if (it->second == "verilog") { hdl = "verilog"; + state.hdl = VERILOG; } else if (it->second == "verilog-beta") { hdl = "verilog-beta"; } else if (it->second == "smv") { @@ -764,15 +771,28 @@ CommandResult Simulate::execute(CommandArguments &args) { } else { llvm::errs() << "Unknow Simulator '" << it->second << "', possible options are 'ghdl', " - "'xsim', and 'vsim'.\n"; + "'xsim', 'vsim' and 'verilator'.\n"; return CommandResult::FAIL; } } + if (simulator == "ghdl" && state.hdl != VHDL) { + llvm::errs() << "Simulator 'ghdl' is not compatible with this HDL. Use " + "'vsim', 'xsim' or 'verilator'. \n"; + return CommandResult::FAIL; + } + + if (simulator == "verilator" && state.hdl != VERILOG) { + llvm::errs() + << "Simulator 'verilator' is not compatible with this HDL. Use " + "'vsim', 'xsim' or 'ghdl'. \n"; + return CommandResult::FAIL; + } + return execCmd(script, state.dynamaticPath, state.getKernelDir(), state.getOutputDir(), state.getKernelName(), state.vivadoPath, state.fpUnitsGenerator == "vivado" ? "true" : "false", - simulator); + simulator, state.hdl); } CommandResult Visualize::execute(CommandArguments &args) { @@ -960,4 +980,4 @@ int main(int argc, char **argv) { free(rawInput); } return 0; -} +} \ No newline at end of file diff --git a/tools/dynamatic/scripts/simulate.sh b/tools/dynamatic/scripts/simulate.sh index 2de89856c2..8fcdf6d4a7 100755 --- a/tools/dynamatic/scripts/simulate.sh +++ b/tools/dynamatic/scripts/simulate.sh @@ -14,6 +14,7 @@ KERNEL_NAME=$4 VIVADO_PATH=$5 VIVADO_FPU=$6 SIMULATOR_NAME=$7 +HDL_TYPE=$8 # Generated directories/files SIM_DIR="$(realpath "$OUTPUT_DIR/sim")" @@ -63,11 +64,19 @@ cp "$SRC_DIR/$KERNEL_NAME.c" "$C_SRC_DIR" cp "$SRC_DIR/$KERNEL_NAME.h" "$C_SRC_DIR" 2> /dev/null # Copy TB supplementary files (memory model, etc.) -cp "$RESOURCE_DIR/templates_vhdl/template_tb_join.vhd" "$COSIM_HDL_SRC_DIR/tb_join.vhd" -cp "$RESOURCE_DIR/templates_vhdl/template_two_port_RAM.vhd" "$COSIM_HDL_SRC_DIR/two_port_RAM.vhd" -cp "$RESOURCE_DIR/templates_vhdl/template_single_argument.vhd" "$COSIM_HDL_SRC_DIR/single_argument.vhd" -cp "$RESOURCE_DIR/templates_vhdl/template_simpackage.vhd" "$COSIM_HDL_SRC_DIR/simpackage.vhd" -cp "$RESOURCE_DIR/modelsim.ini" "$HLS_VERIFY_DIR/modelsim.ini" +if [ "$HDL_TYPE" = "verilog" ]; then + cp "$RESOURCE_DIR/templates_verilog/template_tb_join.v" "$COSIM_HDL_SRC_DIR/tb_join.v" + cp "$RESOURCE_DIR/templates_verilog/template_two_port_RAM.v" "$COSIM_HDL_SRC_DIR/two_port_RAM.sv" + cp "$RESOURCE_DIR/templates_verilog/template_single_argument.v" "$COSIM_HDL_SRC_DIR/single_argument.sv" + cp "$RESOURCE_DIR/modelsim.ini" "$HLS_VERIFY_DIR/modelsim.ini" + cp "$RESOURCE_DIR/verilator_main.cpp" "$HLS_VERIFY_DIR/verilator_main.cpp" +else + cp "$RESOURCE_DIR/templates_vhdl/template_tb_join.vhd" "$COSIM_HDL_SRC_DIR/tb_join.vhd" + cp "$RESOURCE_DIR/templates_vhdl/template_two_port_RAM.vhd" "$COSIM_HDL_SRC_DIR/two_port_RAM.vhd" + cp "$RESOURCE_DIR/templates_vhdl/template_single_argument.vhd" "$COSIM_HDL_SRC_DIR/single_argument.vhd" + cp "$RESOURCE_DIR/templates_vhdl/template_simpackage.vhd" "$COSIM_HDL_SRC_DIR/simpackage.vhd" + cp "$RESOURCE_DIR/modelsim.ini" "$HLS_VERIFY_DIR/modelsim.ini" +fi # Compile kernel's main function to generate inputs and golden outputs for the # simulation @@ -89,6 +98,7 @@ if [ "$VIVADO_FPU" = "true" ]; then --kernel-name="$KERNEL_NAME" \ --handshake-mlir="$OUTPUT_DIR/comp/handshake_export.mlir" \ --simulator="$SIMULATOR_NAME" \ + --hdl="$HDL_TYPE" \ --vivado-fpu \ > "../report.txt" 2>&1 else @@ -97,6 +107,7 @@ else --kernel-name="$KERNEL_NAME" \ --handshake-mlir="$OUTPUT_DIR/comp/handshake_export.mlir" \ --simulator="$SIMULATOR_NAME" \ + --hdl="$HDL_TYPE" \ > "../report.txt" 2>&1 fi -exit_on_fail "Simulation failed" "Simulation succeeded" +exit_on_fail "Simulation failed" "Simulation succeeded" \ No newline at end of file diff --git a/tools/hls-verifier/hls-verifier.cpp b/tools/hls-verifier/hls-verifier.cpp index a289b3c1fa..5fed892cf5 100644 --- a/tools/hls-verifier/hls-verifier.cpp +++ b/tools/hls-verifier/hls-verifier.cpp @@ -140,9 +140,16 @@ int main(int argc, char **argv) { cl::value_desc("vivado-fpu"), cl::init(false)); cl::opt simulatorType( - "simulator", cl::desc("Simulator of choice (options: xsim, ghdl, vsim)"), + "simulator", + cl::desc("Simulator of choice (options: xsim, ghdl, vsim, verilator)"), cl::value_desc("Simulator of choice"), cl::init("vsim")); + cl::opt hdlType("hdl", + cl::desc("HDL used for simulation. Can either " + "be 'vhdl' (default) or 'verilog'"), + cl::value_desc("HDL for simulation"), + cl::init("vhdl")); + cl::ParseCommandLineOptions(argc, argv, R"PREFIX( This is the hls-verifier tool for comparing C and VHDL/Verilog outputs. @@ -178,7 +185,9 @@ int main(int argc, char **argv) { handshake::FuncOp funcOp = dyn_cast(modOp->lookupSymbol(hlsKernelName)); - VerificationContext ctx(simPathName, hlsKernelName, &funcOp, vivadoFPU); + HdlType hdl = (hdlType == "verilog") ? VERILOG : VHDL; + + VerificationContext ctx(simPathName, hlsKernelName, &funcOp, vivadoFPU, hdl); // Generate hls_verify_.vhd vhdlTbCodegen(ctx); @@ -191,6 +200,8 @@ int main(int argc, char **argv) { simulator = std::make_unique(&ctx); } else if (simulatorType == "xsim") { simulator = std::make_unique(&ctx); + } else if (simulatorType == "verilator") { + simulator = std::make_unique(&ctx); } else { logErr(LOG_TAG, "Wrong Simulator (use vsim, xsim, ghdl, verilator)"); return 1; @@ -211,4 +222,4 @@ int main(int argc, char **argv) { return 1; } return 0; -} +} \ No newline at end of file diff --git a/tools/hls-verifier/include/HlsVhdlTb.h b/tools/hls-verifier/include/HlsVhdlTb.h index eb5199cc14..318b0c4ad2 100644 --- a/tools/hls-verifier/include/HlsVhdlTb.h +++ b/tools/hls-verifier/include/HlsVhdlTb.h @@ -10,8 +10,16 @@ #define HLS_VERIFIER_HLS_VHDL_TB_H #include "VerificationContext.h" +#include "dynamatic/Support/Utils/Utils.h" #include "mlir/Support/IndentedOstream.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringExtras.h" +#include "llvm/Support/FormatVariadic.h" +#include +#include #include +#include +#include #include using namespace std; @@ -24,9 +32,20 @@ use ieee.std_logic_textio.all; use ieee.numeric_std.all; use std.textio.all; use work.sim_package.all; +entity tb is +end entity tb; + +architecture behavior of tb is + )DELIM"; -static const string COMMON_TB_BODY = R"DELIM( +static const string VERILOG_LIBRARY_HEADER = R"DELIM( +`timescale 1ns/1ps +module tb; + +)DELIM"; + +static const string VHDL_COMMON_TB_BODY = R"DELIM( generate_sim_done_proc : process begin @@ -134,7 +153,112 @@ begin end process; )DELIM"; -static const string PROC_WRITE_TRANSACTIONS = R"DELIM( +static const string VERILOG_COMMON_TB_BODY = R"DELIM( +// Equivalent of VHDL generate_sim_done_proc +initial begin + while (transaction_idx != TRANSACTION_NUM) begin + @(posedge tb_clk); + end + + repeat(3)@(posedge tb_clk); + + $display("NORMAL EXIT (note: failure is to force the simulator to stop)"); + $finish; + + forever @(posedge tb_clk); +end + +// Equivalent of VHDL gen_sim_latency_proc +always @(posedge tb_clk) begin + if (tb_global_valid && tb_global_ready) begin + $display("Simulation done! Latency = %0d cycles", + ($time - RESET_LATENCY) / (2 * HALF_CLK_PERIOD)); + end +end + + +// Equivalent of VHDL gen_clock_proc +initial begin + tb_clk = 1'b0; + forever begin + #HALF_CLK_PERIOD tb_clk = ~tb_clk; + end +end + +// Equivalent of VHDL gen_reset_proc +initial begin + tb_rst = 1'b1; + #RESET_LATENCY; + tb_rst = 1'b0; +end + +// Equivalent of VHDL acknowledge_tb_end +always @(posedge tb_clk or posedge tb_rst) begin + if (tb_rst) begin + tb_global_ready <= 1'b1; + tb_stop <= 1'b0; + end else begin + if (tb_global_valid) begin + tb_global_ready <= 1'b0; + tb_stop <= 1'b1; + end + end +end + +// Equivalent of VHDL generate_idle_signal +always @(posedge tb_clk or posedge tb_rst) begin + if (tb_rst) begin + tb_temp_idle <= 1'b1; + end else begin + // Default keep + tb_temp_idle <= tb_temp_idle; + + if (tb_start_valid) + tb_temp_idle <= 1'b0; + + if (tb_stop) + tb_temp_idle <= 1'b1; + end +end + +// Equivalent of VHDL generate_start_signal +always @(posedge tb_clk or posedge tb_rst) begin + if (tb_rst) begin + tb_start_valid <= 1'b0; + tb_started <= 1'b0; + end else begin + if (!tb_started) begin + tb_start_valid <= 1'b1; + tb_started <= 1'b1; + end else begin + tb_start_valid <= tb_start_valid & (~tb_start_ready); + end + end +end + +// Equivalent of transaction_increment +initial begin + wait (tb_rst == 1'b0); + + while (tb_temp_idle != 1'b1) + @(posedge tb_clk); + + // Wait until first non-idle + wait (tb_temp_idle == 1'b0); + + forever begin + while (tb_temp_idle != 1'b1) + @(posedge tb_clk); + + transaction_idx = transaction_idx + 1; + + wait (tb_temp_idle == 1'b0); + end +end + +)DELIM"; + +static const string VHDL_PROC_WRITE_TRANSACTIONS = R"DELIM( write_output_transactor_{0}_runtime_proc : process file fp : TEXT; variable fstatus : FILE_OPEN_STATUS; @@ -166,6 +290,49 @@ begin end process; )DELIM"; +static const string VERILOG_PROC_WRITE_TRANSACTIONS = R"DELIM( +// Equivalent of VHDL write_output_transactor_{0}_runtime_proc +integer fp_{0}; + +initial begin + fp_{0} = $fopen(OUTPUT_{0}, "w"); + if (fp_{0} == 0) begin + $display("Open file %0s failed!!!", OUTPUT_{0}); + $display("ERROR: Simulation using HLS TB failed."); + $finish; + end + + // Write header + $fdisplay(fp_{0}, "[[[runtime]]]"); + $fclose(fp_{0}); + + while (transaction_idx != TRANSACTION_NUM) begin + @(posedge tb_clk); + end + + @(posedge tb_clk); + @(posedge tb_clk); + + fp_{0} = $fopen(OUTPUT_{0}, "a"); // append mode + if (fp_{0} == 0) begin + $display("Open file %0s failed!!!", OUTPUT_{0}); + $display("ERROR: Simulation using HLS TB failed."); + $finish; + end + + $fdisplay(fp_{0}, "[[[/runtime]]]"); + $fclose(fp_{0}); + + forever @(posedge tb_clk); +end +)DELIM"; + +enum TypeConstant { INTEGER, STRING, TIME }; + +enum SimulatorSignalType { REGISTER, WIRE }; + +std::string toBinaryString(int initialValue, unsigned int bitwidth); + // Get input argument of type Ty and their associated names template llvm::SmallVector> @@ -229,6 +396,37 @@ static const string END_READY = "end_ready"; // else as the D_IN0_PORT (i.e., the first port of the dual-port RAM). static const string RET_VALUE_NAME = "out0"; +// using ConnectedValueType = std::variant; + +struct SignalAssignment { + enum AssignmentType { SIGNAL, CONST_ZERO, CONST_ONE, CONST_VEC_ZERO, OPEN }; + std::string name = "DEFAULT"; + AssignmentType type = SIGNAL; + SignalAssignment(const std::string &name) : name(name) {}; + + SignalAssignment(AssignmentType type) : name("DEFAULT"), type(type) {}; + + std::string emit(VerificationContext &ctx) { + switch (type) { + case SignalAssignment::CONST_ZERO: + return ctx.simLanguage == VHDL ? "'0'" : "1'b0"; + break; + case SignalAssignment::CONST_ONE: + return ctx.simLanguage == VHDL ? "'1'" : "1'b1"; + break; + case SignalAssignment::OPEN: + return ctx.simLanguage == VHDL ? "open" : ""; + break; + case SignalAssignment::CONST_VEC_ZERO: + return ctx.simLanguage == VHDL ? "(others => '0')" : "0"; + break; + case SignalAssignment::SIGNAL: + return name; + break; + } + } +}; + // This is a helper class to generete a HDL instance // Example: // Instance("my_module", "my_instance") @@ -250,9 +448,73 @@ static const string RET_VALUE_NAME = "out0"; class Instance { std::string moduleName; std::string instanceName; - std::vector> connections; + std::vector> connections; std::vector> params; + void emitVhdl(mlir::raw_indented_ostream &os, VerificationContext &ctx) { + os << instanceName << ": entity work." << moduleName << "\n"; + + if (!params.empty()) { + os << "generic map(\n"; + os.indent(); + + llvm::SmallVector portmappings; + + for (auto &[port, value] : params) { + portmappings.push_back(llvm::formatv("{0} => {1}", port, value)); + } + + os << llvm::join(portmappings, ",\n") << "\n"; + + os.unindent(); + os << ")\n"; + } + os << "port map(\n"; + os.indent(); + + llvm::SmallVector connectionmappings; + for (auto &[port, sig] : connections) { + connectionmappings.push_back( + llvm::formatv("{0} => {1}", port, sig.emit(ctx))); + } + os << llvm::join(connectionmappings, ",\n") << "\n"; + + os.unindent(); + os << ");\n\n"; + } + + void emitVerilog(mlir::raw_indented_ostream &os, VerificationContext &ctx) { + os << moduleName; + + if (!params.empty()) { + os << " #(\n"; + os.indent(); + + llvm::SmallVector portmappings; + + for (auto &[port, value] : params) { + portmappings.push_back(llvm::formatv(".{0}({1})", port, value)); + } + os << llvm::join(portmappings, ",\n") << "\n"; + + os.unindent(); + os << ")"; + } + + os << " " << instanceName << " (\n"; + os.indent(); + + llvm::SmallVector connectionmappings; + for (auto &[port, sig] : connections) { + connectionmappings.push_back( + llvm::formatv(".{0}({1})", port, sig.emit(ctx))); + } + os << llvm::join(connectionmappings, ",\n") << "\n"; + + os.unindent(); + os << ");\n\n"; + } + public: Instance(std::string module, std::string inst, const std::vector &p = {}) @@ -264,62 +526,70 @@ class Instance { } Instance &connect(const std::string &port, const std::string &signal) { - connections.emplace_back(port, signal); + connections.emplace_back(port, SignalAssignment(signal)); return *this; } - void emitVhdl(mlir::raw_indented_ostream &os) { - os << instanceName << ": entity work." << moduleName << "\n"; + Instance &connect(const std::string &port, + SignalAssignment::AssignmentType value) { + connections.emplace_back(port, SignalAssignment(value)); + return *this; + } + + void emit(mlir::raw_indented_ostream &os, VerificationContext &ctx) { // VHDL port map needs a continuous assignment std::sort(connections.begin(), connections.end(), [](const auto &a, const auto &b) { return a.first < b.first; }); - if (!params.empty()) { - os << "generic map(\n"; - os.indent(); - for (size_t i = 0; i < params.size(); ++i) { - os << params[i].first << " => " << params[i].second; - if (i != params.size() - 1) - os << ","; - os << "\n"; - } - os.unindent(); - os << ")\n"; + if (ctx.simLanguage == VHDL) { + emitVhdl(os, ctx); } - os << "port map(\n"; - os.indent(); - for (size_t i = 0; i < connections.size(); ++i) { - const auto &[port, sig] = connections[i]; - os << port << " => " << sig; - if (i != connections.size() - 1) - os << ","; - os << "\n"; + + if (ctx.simLanguage == VERILOG) { + emitVerilog(os, ctx); } - os.unindent(); - os << ");\n\n"; } }; void vhdlTbCodegen(VerificationContext &ctx); // Declare a signal. Usage: -// - declareSTL(os, "signal_name"); declares a std_logic signal -// - declareSTL(os, "signal_name", "SIGNAL_WIDTH"); declares a std_logic_vector -inline void declareSTL(mlir::raw_indented_ostream &os, const string &name, - std::optional size = std::nullopt, - std::optional initialValue = std::nullopt) { - os << "signal " << name << " : std_logic"; - if (size) - os << "_vector(" << *size << " - 1 downto 0)"; - if (initialValue) - os << " := " << *initialValue; - os << ";\n"; -} +// - declareWire(ctx, os, "signal_name"); declares a std_logic signal +// - declareWire(ctx, os, "signal_name", SIGNAL_WIDTH); declares a +// std_logic_vector +void declareWire(VerificationContext &ctx, mlir::raw_indented_ostream &os, + const string &name, + std::optional size = std::nullopt, + std::optional initialValue = std::nullopt); -inline void declareConstant(mlir::raw_indented_ostream &os, const string &name, - const string &type, const string &value) { - os << "constant " << name << " : " << type << " := " << value << ";\n"; +void declareReg(VerificationContext &ctx, mlir::raw_indented_ostream &os, + const string &name, + std::optional size = std::nullopt, + std::optional initialValue = std::nullopt); + +inline void declareConstant(VerificationContext &ctx, + mlir::raw_indented_ostream &os, const string &name, + TypeConstant type, const string &value) { + if (ctx.simLanguage == VHDL) { + os << "constant " << name << " : "; + switch (type) { + case INTEGER: + os << "INTEGER := " << value; + break; + + case STRING: + os << "STRING := " << value; + break; + + case TIME: + os << "TIME := " << value << " ns"; + } + os << ";\n"; + } + if (ctx.simLanguage == VERILOG) { + os << "parameter " << name << " = " << value << ";\n"; + } } #endif // HLS_VERIFIER_HLS_VHDL_TB_H diff --git a/tools/hls-verifier/include/Simulators.h b/tools/hls-verifier/include/Simulators.h index 2361d1cd76..09c355140e 100644 --- a/tools/hls-verifier/include/Simulators.h +++ b/tools/hls-verifier/include/Simulators.h @@ -10,6 +10,7 @@ #ifndef HLS_VERIFIER_SIMULATORS_H #define HLS_VERIFIER_SIMULATORS_H +#include "Utilities.h" #include "VerificationContext.h" #include "dynamatic/Support/System.h" #include "mlir/Support/LogicalResult.h" @@ -44,8 +45,8 @@ class XSimSimulator : public Simulator { XSimSimulator(VerificationContext *context) : Simulator(context) {} void execSimulation() const override { - exec("xelab", "-prj", ctx->getXsimPrjFilePath(), "work.tb", "-s", "tb", - "-R"); + exec("xelab", "-prj", ctx->getXsimPrjFilePath(), "work.tb", "--timescale", + "1ns/1ps", "-s", "tb", "-R"); } mlir::LogicalResult generateScripts() const override { @@ -53,6 +54,8 @@ class XSimSimulator : public Simulator { getListOfFilesInDirectory(ctx->getHdlSrcDir(), ".vhd"); vector filelistVerilog = getListOfFilesInDirectory(ctx->getHdlSrcDir(), ".v"); + vector fileListSystemVerilog = + getListOfFilesInDirectory(ctx->getHdlSrcDir(), ".sv"); std::error_code ec; llvm::raw_fd_ostream os(ctx->getXsimPrjFilePath(), ec); @@ -63,6 +66,9 @@ class XSimSimulator : public Simulator { for (auto &it : filelistVerilog) os << "verilog work " << it << "\n"; + for (auto &it : fileListSystemVerilog) + os << "sv work " << it << "\n"; + return mlir::success(); } }; @@ -156,6 +162,8 @@ class VSimSimulator : public Simulator { getListOfFilesInDirectory(ctx->getHdlSrcDir(), ".vhd"); vector filelistVerilog = getListOfFilesInDirectory(ctx->getHdlSrcDir(), ".v"); + vector fileListSystemVerilog = + getListOfFilesInDirectory(ctx->getHdlSrcDir(), ".sv"); std::error_code ec; llvm::raw_fd_ostream os(ctx->getModelsimDoFilePath(), ec); @@ -173,6 +181,9 @@ class VSimSimulator : public Simulator { for (auto &it : filelistVerilog) os << "project addfile " << it << "\n"; + for (auto &it : fileListSystemVerilog) + os << "project addfile " << it << "\n"; + os << "project calculateorder\n"; os << "project compileall\n"; if (ctx->useVivadoFPU()) { @@ -188,4 +199,45 @@ class VSimSimulator : public Simulator { } }; +class Verilator : public Simulator { + +public: + Verilator(VerificationContext *context) : Simulator(context) {} + + void execSimulation() const override { + exec("bash", ctx->getVerilatorShFilePath()); + } + + mlir::LogicalResult generateScripts() const override { + + vector filelistVerilog = + getListOfFilesInDirectory(ctx->getHdlSrcDir(), ".v"); + vector fileListSystemVerilog = + getListOfFilesInDirectory(ctx->getHdlSrcDir(), ".sv"); + + if (filelistVerilog.empty()) { + return mlir::failure(); + } + + std::error_code ec; + llvm::raw_fd_ostream os(ctx->getVerilatorShFilePath(), ec); + + os << "verilator --trace -Mdir ./verilator -cc "; + + for (auto &it : filelistVerilog) + os << it << " "; + for (auto &it : fileListSystemVerilog) + os << it << " "; + + os << "--exe verilator_main.cpp --trace-underscore --Wno-UNOPTFLAT " + "--top-module tb --timing -Wno-REALCVT\n"; + + os << "make -j -C ./verilator/ -f Vtb.mk Vtb\n"; + + os << "./verilator/Vtb\n"; + + return mlir::success(); + } +}; + #endif // HLS_VERIFIER_SIMULATORS_H \ No newline at end of file diff --git a/tools/hls-verifier/include/VerificationContext.h b/tools/hls-verifier/include/VerificationContext.h index 28a59a138d..d0b242beae 100644 --- a/tools/hls-verifier/include/VerificationContext.h +++ b/tools/hls-verifier/include/VerificationContext.h @@ -29,14 +29,17 @@ static const std::string C_OUT_DIR = "C_OUT"; static const std::string VSIM_SCRIPT_FILE = "simulation_vsim.do"; static const std::string GHDL_SCRIPT_FILE = "simulation_ghdl.sh"; static const std::string XSIM_SCRIPT_FILE = "simulation_xsim.prj"; +static const std::string VERILATOR_SCRIPT_FILE = "simulation_verilator.sh"; static const std::string HLS_VERIFY_DIR = "HLS_VERIFY"; +enum HdlType { VHDL, VERILOG }; + struct VerificationContext { VerificationContext(const std::string &simPath, const std::string &cFuvFunctionName, - handshake::FuncOp *funcOp, bool vivadoFPU) + handshake::FuncOp *funcOp, bool vivadoFPU, HdlType hdl) : simPath(simPath), funcOp(funcOp), kernelName(cFuvFunctionName), - vivadoFPU(vivadoFPU) {} + vivadoFPU(vivadoFPU), simLanguage(hdl) {} static const char SEP = std::filesystem::path::preferred_separator; @@ -52,15 +55,23 @@ struct VerificationContext { // Whether to use Vivado FPU for floating-point operations bool vivadoFPU; + // Wheter to use VHDL or VERILOG for the testbench + HdlType simLanguage; + bool useVivadoFPU() const { return vivadoFPU; } std::string getVhdlTestbenchPath() const { return getHdlSrcDir() + SEP + "tb_" + kernelName + ".vhd"; } + std::string getVerilogTestbenchPath() const { + return getHdlSrcDir() + SEP + "tb_" + kernelName + ".v"; + } + std::string getModelsimDoFilePath() const { return VSIM_SCRIPT_FILE; } std::string getGhdlShFilePath() const { return GHDL_SCRIPT_FILE; } std::string getXsimPrjFilePath() const { return XSIM_SCRIPT_FILE; } + std::string getVerilatorShFilePath() const { return VERILATOR_SCRIPT_FILE; } std::string getCOutDir() const { return simPath + SEP + C_OUT_DIR; } diff --git a/tools/hls-verifier/lib/HlsVhdlTb.cpp b/tools/hls-verifier/lib/HlsVhdlTb.cpp index a35f5b5279..678223dce8 100644 --- a/tools/hls-verifier/lib/HlsVhdlTb.cpp +++ b/tools/hls-verifier/lib/HlsVhdlTb.cpp @@ -8,6 +8,7 @@ #include #include +#include #include "HlsVhdlTb.h" #include "VerificationContext.h" @@ -16,6 +17,7 @@ #include "mlir/IR/BuiltinTypes.h" #include "mlir/Support/IndentedOstream.h" #include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringExtras.h" #include "llvm/Support/FormatVariadic.h" using std::tuple; @@ -66,46 +68,50 @@ struct MemRefToDualPortRAM { } void declareConstants(mlir::raw_indented_ostream &os, + VerificationContext &ctx, const std::string &inputVectorPath, const std::string &outputFilePath) { int dataWidth = type.getElementTypeBitWidth(); int dataDepth = type.getNumElements(); int addrWidth = max((int)ceil(log2(dataDepth)), 1); - declareConstant(os, "INPUT_" + argName, "STRING", + declareConstant(ctx, os, "INPUT_" + argName, STRING, "\"" + inputVectorPath + "/input_" + argName + ".dat" + "\""); - declareConstant(os, "OUTPUT_" + argName, "STRING", + declareConstant(ctx, os, "OUTPUT_" + argName, STRING, "\"" + outputFilePath + "/output_" + argName + ".dat" + "\""); - declareConstant(os, "DATA_WIDTH_" + argName, "INTEGER", + declareConstant(ctx, os, "DATA_WIDTH_" + argName, INTEGER, to_string(dataWidth)); - declareConstant(os, "ADDR_WIDTH_" + argName, "INTEGER", + declareConstant(ctx, os, "ADDR_WIDTH_" + argName, INTEGER, to_string(addrWidth)); - declareConstant(os, "DATA_DEPTH_" + argName, "INTEGER", + declareConstant(ctx, os, "DATA_DEPTH_" + argName, INTEGER, to_string(dataDepth)); } // Declare signals appear in the circuit interface - void declareSignals(mlir::raw_indented_ostream &os) { + void declareSignals(mlir::raw_indented_ostream &os, + VerificationContext &ctx) { for (auto &[_, sigName, bitwidth] : memrefToDPRAM) { if (sigName == WE0_PORT or sigName == CE1_PORT) { - declareSTL(os, argName + "_" + sigName, std::nullopt); + declareWire(ctx, os, argName + "_" + sigName, std::nullopt); } else { - declareSTL(os, argName + "_" + sigName, to_string(bitwidth)); + declareWire(ctx, os, argName + "_" + sigName, bitwidth); } } int dataWidth = type.getElementTypeBitWidth(); // Declare unused interfaces in the two port RAM - declareSTL(os, argName + "_" + D_OUT0_PORT, to_string(dataWidth)); - declareSTL(os, argName + "_" + D_IN1_PORT, to_string(dataWidth), - "(others => \'0\')"); + declareWire(ctx, os, argName + "_" + D_OUT0_PORT, dataWidth); + + declareWire(ctx, os, argName + "_" + D_IN1_PORT, dataWidth, 0); + // The write enable of the read interface is not used - declareSTL(os, argName + "_" + WE1_PORT, nullopt, "\'0\'"); + declareWire(ctx, os, argName + "_" + WE1_PORT, nullopt, 0); // The read enable of the write interface is not used - declareSTL(os, argName + "_" + CE0_PORT, nullopt, "\'1\'"); + declareWire(ctx, os, argName + "_" + CE0_PORT, nullopt, 1); } - void instantiateRAMModel(mlir::raw_indented_ostream &os) { + void instantiateRAMModel(mlir::raw_indented_ostream &os, + VerificationContext &ctx) { Instance memInst("two_port_RAM", "mem_inst_" + argName); memInst.parameter(IN_FILE_PARAM, "INPUT_" + argName) @@ -124,9 +130,9 @@ struct MemRefToDualPortRAM { memInst.connect(D_OUT0_PORT, argName + "_" + D_OUT0_PORT); memInst.connect(D_IN1_PORT, argName + "_" + D_IN1_PORT); memInst.connect(WE1_PORT, argName + "_" + WE1_PORT); - memInst.connect(CE0_PORT, "\'1\'"); + memInst.connect(CE0_PORT, SignalAssignment::CONST_ONE); - memInst.emitVhdl(os); + memInst.emit(os, ctx); } void connectToDuv(Instance &duvInst) { @@ -135,19 +141,98 @@ struct MemRefToDualPortRAM { } }; +std::string toBinaryString(int initialValue, unsigned int bitwidth) { + boost::dynamic_bitset<> intInBinary(bitwidth, initialValue); + std::string binaryString; + to_string(intInBinary, binaryString); + return binaryString; +}; + +// Writes the Verilog signal declaration to ostream. +// Examples: +// wire = ; +// reg [ -1 : 0] = 'b; +// (initialValue in binary representation) +void verilogSignalDeclaration(mlir::raw_indented_ostream &os, + const string &name, bool isWire, + std::optional size = std::nullopt, + std::optional initialValue = std::nullopt) { + os << (isWire ? "wire " : "reg "); + if (size) + os << "[" << *size << "-1 : 0] "; + os << name; + if (initialValue) { + unsigned int bitwidth = size ? *size : 1; + if (bitwidth > 1) { + std::string binaryString = toBinaryString(*initialValue, bitwidth); + os << " = " << bitwidth << "'b" << binaryString; + } else { + os << " = " << initialValue; + } + } + os << ";\n"; +} + +// Writes the VHDL signal declaration to ostream. +// Examples: +// signal : std_logic := ''; +// signal : std_logic_vector( -1 downto 0) := ""; +// (initialValue in binary representation) +void vhdlSignalDeclaration(mlir::raw_indented_ostream &os, const string &name, + std::optional size = std::nullopt, + std::optional initialValue = std::nullopt) { + os << "signal " << name << " : std_logic"; + if (size) + os << "_vector(" << *size << " - 1 downto 0)"; + if (initialValue) { + unsigned int bitwidth = size ? *size : 1; + if (bitwidth > 1) { + std::string binaryString = toBinaryString(*initialValue, bitwidth); + os << " := \"" << binaryString << "\""; + } else { + os << " := '" << *initialValue << "'"; + } + } + os << ";\n"; +} + +void declareWire(VerificationContext &ctx, mlir::raw_indented_ostream &os, + const string &name, std::optional size, + std::optional initialValue) { + if (ctx.simLanguage == VHDL) { + vhdlSignalDeclaration(os, name, size, initialValue); + } + if (ctx.simLanguage == VERILOG) { + verilogSignalDeclaration(os, name, true, size, initialValue); + } +} + +void declareReg(VerificationContext &ctx, mlir::raw_indented_ostream &os, + const string &name, std::optional size, + std::optional initialValue) { + + if (ctx.simLanguage == VHDL) { + vhdlSignalDeclaration(os, name, size, initialValue); + } + if (ctx.simLanguage == VERILOG) { + verilogSignalDeclaration(os, name, false, size, initialValue); + } +} + // Centralizes the signal declaration for single argument models (both as inputs // and outputs) void declareSignalsSingleArgumentModel(mlir::raw_indented_ostream &os, + VerificationContext &ctx, const std::string &argName, unsigned dataWidth) { // The single argument block needs to define all these signals, regardless // of using as an input argument or an output argument - declareSTL(os, argName + "_" + CE0_PORT); - declareSTL(os, argName + "_" + WE0_PORT); - declareSTL(os, argName + "_din0", to_string(dataWidth)); - declareSTL(os, argName + "_dout0", to_string(dataWidth)); - declareSTL(os, argName + "_dout0_valid"); - declareSTL(os, argName + "_dout0_ready"); + declareWire(ctx, os, argName + "_" + CE0_PORT); + declareWire(ctx, os, argName + "_" + WE0_PORT); + declareWire(ctx, os, argName + "_din0", dataWidth); + declareWire(ctx, os, argName + "_dout0", dataWidth); + declareWire(ctx, os, argName + "_dout0_valid"); + declareWire(ctx, os, argName + "_dout0_ready"); } void commonSingleArgumentDeclaration(Instance &inst, @@ -181,6 +266,7 @@ struct StartToChannelConnector { : type(type), argName(argName) {} void declareConstants(mlir::raw_indented_ostream &os, + VerificationContext &ctx, const std::string &inputVectorPath, const std::string &outputFilePath) { @@ -188,28 +274,30 @@ struct StartToChannelConnector { // specify any input vector file (""). std::string inputFile = "\"" + inputVectorPath + "/input_" + argName + ".dat" + "\""; - declareConstant(os, "INPUT_" + argName, "STRING", inputFile); + declareConstant(ctx, os, "INPUT_" + argName, STRING, inputFile); - declareConstant(os, "OUTPUT_" + argName, "STRING", + declareConstant(ctx, os, "OUTPUT_" + argName, STRING, "\"" + outputFilePath + "/output_" + argName + ".dat" + "\""); int dataWidth = type.getDataBitWidth(); - declareConstant(os, "DATA_WIDTH_" + argName, "INTEGER", + declareConstant(ctx, os, "DATA_WIDTH_" + argName, INTEGER, to_string(dataWidth)); } - void declareSignals(mlir::raw_indented_ostream &os) { - declareSignalsSingleArgumentModel(os, argName, type.getDataBitWidth()); + void declareSignals(mlir::raw_indented_ostream &os, + VerificationContext &ctx) { + declareSignalsSingleArgumentModel(os, ctx, argName, type.getDataBitWidth()); } - void instantiateSingleArgumentModel(mlir::raw_indented_ostream &os) { + void instantiateSingleArgumentModel(mlir::raw_indented_ostream &os, + VerificationContext &ctx) { Instance argInst("single_argument", "arg_inst_" + argName); commonSingleArgumentDeclaration(argInst, argName); - argInst.connect(CE0_PORT, "'1'") - .connect(WE0_PORT, "'0'") - .connect(D_IN0_PORT, "(others => '0')"); - argInst.emitVhdl(os); + argInst.connect(CE0_PORT, SignalAssignment::CONST_ONE) + .connect(WE0_PORT, SignalAssignment::CONST_ZERO) + .connect(D_IN0_PORT, SignalAssignment::CONST_VEC_ZERO); + argInst.emit(os, ctx); } void connectToDuv(Instance &duvInst) { @@ -228,38 +316,41 @@ struct ChannelToEndConnector { : type(type), argName(argName) {} void declareConstants(mlir::raw_indented_ostream &os, + VerificationContext &ctx, const std::string &inputVectorPath, const std::string &outputFilePath) { // If the single argument is an output (e.g., the return value), we don't // specify any input vector file (""). std::string inputFile = "\"\""; - declareConstant(os, "INPUT_" + argName, "STRING", inputFile); + declareConstant(ctx, os, "INPUT_" + argName, STRING, inputFile); - declareConstant(os, "OUTPUT_" + argName, "STRING", + declareConstant(ctx, os, "OUTPUT_" + argName, STRING, "\"" + outputFilePath + "/output_" + argName + ".dat" + "\""); int dataWidth = type.getDataBitWidth(); - declareConstant(os, "DATA_WIDTH_" + argName, "INTEGER", + declareConstant(ctx, os, "DATA_WIDTH_" + argName, INTEGER, to_string(dataWidth)); } - void declareSignals(mlir::raw_indented_ostream &os) { + void declareSignals(mlir::raw_indented_ostream &os, + VerificationContext &ctx) { // The valid signal from the circuit, which drives the write enable (we) // pin of the single enable module - declareSTL(os, argName + "_valid"); - declareSTL(os, argName + "_ready"); - declareSignalsSingleArgumentModel(os, argName, type.getDataBitWidth()); + declareWire(ctx, os, argName + "_valid"); + declareWire(ctx, os, argName + "_ready"); + declareSignalsSingleArgumentModel(os, ctx, argName, type.getDataBitWidth()); } - void instantiateSingleArgumentModel(mlir::raw_indented_ostream &os) { + void instantiateSingleArgumentModel(mlir::raw_indented_ostream &os, + VerificationContext &ctx) { Instance argInst("single_argument", "arg_inst_" + argName); commonSingleArgumentDeclaration(argInst, argName); - argInst.connect(CE0_PORT, "'1'") + argInst.connect(CE0_PORT, SignalAssignment::CONST_ONE) .connect(WE0_PORT, argName + "_valid") .connect(D_IN0_PORT, argName + "_din0"); - argInst.emitVhdl(os); + argInst.emit(os, ctx); } void connectToDuv(Instance &duvInst) { @@ -295,8 +386,8 @@ struct StartToControlConnector { // ready signals are ignored. // // [TODO] Should this handshake also happen only once per TB transaction? - duvInst.connect(argName + "_valid", "\'1\'") - .connect(argName + "_ready", "open"); + duvInst.connect(argName + "_valid", SignalAssignment::CONST_ONE) + .connect(argName + "_ready", SignalAssignment::OPEN); } } }; @@ -312,9 +403,10 @@ struct ControlToEndConnector { ControlToEndConnector(handshake::ControlType type, const std::string &argName) : type(type), argName(argName) {} - void declareSignals(mlir::raw_indented_ostream &os) { - declareSTL(os, argName + "_valid"); - declareSTL(os, argName + "_ready"); + void declareSignals(mlir::raw_indented_ostream &os, + VerificationContext &ctx) { + declareWire(ctx, os, argName + "_valid"); + declareWire(ctx, os, argName + "_ready"); } void connectToDuv(Instance &duvInst) { duvInst.connect(argName + "_valid", argName + "_valid") @@ -337,13 +429,13 @@ void getConstantDeclaration(mlir::raw_indented_ostream &os, getInputArguments(funcOp)) { StartToChannelConnector c(type, argName); - c.declareConstants(os, inputVectorPath, outputFilePath); + c.declareConstants(os, ctx, inputVectorPath, outputFilePath); } // The files and configuration of the two port RAM model of the arrays for (auto &[type, argName] : getInputArguments(funcOp)) { MemRefToDualPortRAM m(type, argName); - m.declareConstants(os, inputVectorPath, outputFilePath); + m.declareConstants(os, ctx, inputVectorPath, outputFilePath); } // The files and configuration of the single_argument model of the data output @@ -351,11 +443,11 @@ void getConstantDeclaration(mlir::raw_indented_ostream &os, for (auto &[type, argName] : getOutputArguments(funcOp)) { ChannelToEndConnector c(type, argName); - c.declareConstants(os, inputVectorPath, outputFilePath); + c.declareConstants(os, ctx, inputVectorPath, outputFilePath); } - declareConstant(os, "HALF_CLK_PERIOD", "TIME", "2.00 ns"); - declareConstant(os, "RESET_LATENCY", "TIME", "10.00 ns"); - declareConstant(os, "TRANSACTION_NUM", "INTEGER", to_string(1)); + declareConstant(ctx, os, "HALF_CLK_PERIOD", TIME, "2.00"); + declareConstant(ctx, os, "RESET_LATENCY", TIME, "10.00"); + declareConstant(ctx, os, "TRANSACTION_NUM", INTEGER, to_string(1)); } // This writes the signal declarations fot the testbench @@ -367,26 +459,26 @@ void getSignalDeclaration(mlir::raw_indented_ostream &os, handshake::FuncOp *funcOp = ctx.funcOp; - declareSTL(os, "tb_clk", std::nullopt, "'0'"); - declareSTL(os, "tb_rst", std::nullopt, "'0'"); + declareReg(ctx, os, "tb_clk", std::nullopt, 0); + declareReg(ctx, os, "tb_rst", std::nullopt, 0); // The interface that indicates the global "start" signal. - declareSTL(os, "tb_start_valid", std::nullopt, "'0'"); - declareSTL(os, "tb_start_ready", std::nullopt, "'0'"); + declareReg(ctx, os, "tb_start_valid", std::nullopt, 0); + declareWire(ctx, os, "tb_start_ready", std::nullopt, 0); // Testbench state signal. - declareSTL(os, "tb_started"); + declareReg(ctx, os, "tb_started"); // The interface that indicates the global "done" signal. - declareSTL(os, "tb_global_valid"); - declareSTL(os, "tb_global_ready"); - declareSTL(os, "tb_stop"); + declareWire(ctx, os, "tb_global_valid"); + declareReg(ctx, os, "tb_global_ready"); + declareReg(ctx, os, "tb_stop"); // Signals of data input channels for (auto &[type, argName] : getInputArguments(funcOp)) { StartToChannelConnector c(type, argName); - c.declareSignals(os); + c.declareSignals(os, ctx); } // Signals of control input channels @@ -400,27 +492,33 @@ void getSignalDeclaration(mlir::raw_indented_ostream &os, for (auto &[type, argName] : getInputArguments(funcOp)) { MemRefToDualPortRAM m(type, argName); - m.declareSignals(os); + m.declareSignals(os, ctx); } // Signals of data output channels for (auto &[type, argName] : getOutputArguments(funcOp)) { ChannelToEndConnector c(type, argName); - c.declareSignals(os); + c.declareSignals(os, ctx); } // Signals of control output channels for (auto &[type, argName] : getOutputArguments(funcOp)) { ControlToEndConnector c(type, argName); - c.declareSignals(os); + c.declareSignals(os, ctx); } os << "\n"; - declareSTL(os, "tb_temp_idle", std::nullopt, "'1'"); - os << "shared variable transaction_idx : INTEGER := 0;\n"; + declareReg(ctx, os, "tb_temp_idle", std::nullopt, 1); + + if (ctx.simLanguage == VHDL) { + os << "shared variable transaction_idx : INTEGER := 0;\n"; + } + if (ctx.simLanguage == VERILOG) { + os << " integer transaction_idx = 0;\n"; + } os.flush(); } @@ -433,19 +531,19 @@ void getMemoryInstanceGeneration(mlir::raw_indented_ostream &os, for (auto &[type, argName] : getInputArguments(funcOp)) { StartToChannelConnector c(type, argName); - c.instantiateSingleArgumentModel(os); + c.instantiateSingleArgumentModel(os, ctx); } // Instantiate dual port RAMs for the memory interfaces for (auto &[type, argName] : getInputArguments(funcOp)) { MemRefToDualPortRAM m(type, argName); - m.instantiateRAMModel(os); + m.instantiateRAMModel(os, ctx); } for (auto &[type, argName] : getOutputArguments(funcOp)) { ChannelToEndConnector c(type, argName); - c.instantiateSingleArgumentModel(os); + c.instantiateSingleArgumentModel(os, ctx); } } @@ -496,65 +594,103 @@ void getDuvInstanceGeneration(mlir::raw_indented_ostream &os, c.connectToDuv(duvInst); } - duvInst.emitVhdl(os); + duvInst.emit(os, ctx); } void deriveGlobalCompletionSignal(mlir::raw_indented_ostream &os, VerificationContext &ctx) { // @Jiahui17: I assume that the results only contain handshake channels. - unsigned idx = 0; Instance joinInst("tb_join", "join_valids"); - for (auto &[type, argName] : - getOutputArguments(ctx.funcOp)) { - joinInst.connect("ins_valid(" + std::to_string(idx) + ")", - argName + "_valid"); - joinInst.connect("ins_ready(" + std::to_string(idx++) + ")", - argName + "_ready"); + if (ctx.simLanguage == VHDL) { + unsigned idx = 0; + for (auto &[type, argName] : + getOutputArguments(ctx.funcOp)) { + joinInst.connect("ins_valid(" + std::to_string(idx) + ")", + argName + "_valid"); + joinInst.connect("ins_ready(" + std::to_string(idx++) + ")", + argName + "_ready"); + } + + for (auto &[type, argName] : + getOutputArguments(ctx.funcOp)) { + + joinInst.connect("ins_valid(" + std::to_string(idx) + ")", + argName + "_valid"); + joinInst.connect("ins_ready(" + std::to_string(idx++) + ")", + argName + "_ready"); + } + + joinInst.parameter("SIZE", std::to_string(/* Size = last index + 1 */ idx)); } - for (auto &[type, argName] : - getOutputArguments(ctx.funcOp)) { - joinInst.connect("ins_valid(" + std::to_string(idx) + ")", - argName + "_valid"); - joinInst.connect("ins_ready(" + std::to_string(idx++) + ")", - argName + "_ready"); + if (ctx.simLanguage == VERILOG) { + llvm::SmallVector insValids; + llvm::SmallVector insReadys; + + for (auto &[type, argName] : + getOutputArguments(ctx.funcOp)) { + insValids.push_back(llvm::formatv("{0}_valid", argName)); + insReadys.push_back(llvm::formatv("{0}_ready", argName)); + } + + for (auto &[type, argName] : + getOutputArguments(ctx.funcOp)) { + insValids.push_back(llvm::formatv("{0}_valid", argName)); + insReadys.push_back(llvm::formatv("{0}_ready", argName)); + } + joinInst.connect("ins_valid", "{" + llvm::join(insValids, ", ") + "}"); + joinInst.connect("ins_ready", "{" + llvm::join(insReadys, ", ") + "}"); + + joinInst.parameter( + "SIZE", std::to_string(/* Size = last index + 1 */ insValids.size())); } - joinInst.parameter("SIZE", std::to_string(/* Size = last index + 1 */ idx)); joinInst.connect("outs_valid", "tb_global_valid"); joinInst.connect("outs_ready", "tb_global_ready"); - joinInst.emitVhdl(os); + joinInst.emit(os, ctx); } void getOutputTagGeneration(mlir::raw_indented_ostream &os, VerificationContext &ctx) { handshake::FuncOp *funcOp = ctx.funcOp; + auto template_write_transaction = (ctx.simLanguage == VHDL) + ? VHDL_PROC_WRITE_TRANSACTIONS + : VERILOG_PROC_WRITE_TRANSACTIONS; + // Reading / Dumping the content of the memory into the file for (auto &[type, argName] : getInputArguments(funcOp)) { - os << llvm::formatv(PROC_WRITE_TRANSACTIONS.c_str(), argName); + os << llvm::formatv(template_write_transaction.c_str(), argName); } // Reading / Dumping the content of the memory into the file for (auto &[type, argName] : getInputArguments(funcOp)) { - os << llvm::formatv(PROC_WRITE_TRANSACTIONS.c_str(), argName); + os << llvm::formatv(template_write_transaction.c_str(), argName); } // Reading / Dumping the content of the memory into the file for (auto &[type, argName] : getOutputArguments(funcOp)) { - os << llvm::formatv(PROC_WRITE_TRANSACTIONS.c_str(), argName); + os << llvm::formatv(template_write_transaction.c_str(), argName); } } void vhdlTbCodegen(VerificationContext &ctx) { std::error_code ec; - llvm::raw_fd_ostream fileStream(ctx.getVhdlTestbenchPath(), ec); + std::string filename; + if (ctx.simLanguage == VHDL) { + filename = ctx.getVhdlTestbenchPath(); + } + if (ctx.simLanguage == VERILOG) { + filename = ctx.getVerilogTestbenchPath(); + } + llvm::raw_fd_ostream fileStream(filename, ec); + if (ec) { llvm::errs() << "Error opening file: " << ec.message() << "\n"; // Handle error appropriately, e.g., return, exit, etc. @@ -562,22 +698,35 @@ void vhdlTbCodegen(VerificationContext &ctx) { } mlir::raw_indented_ostream os(fileStream); - os << VHDL_LIBRARY_HEADER; - os << "entity tb is\n"; - os << "end entity tb;\n\n"; - os << "architecture behavior of tb is\n\n"; - os.indent(); - getConstantDeclaration(os, ctx); - getSignalDeclaration(os, ctx); - os.unindent(); - os << "begin\n\n"; - os.indent(); - getDuvInstanceGeneration(os, ctx); - getMemoryInstanceGeneration(os, ctx); - deriveGlobalCompletionSignal(os, ctx); - getOutputTagGeneration(os, ctx); - os << COMMON_TB_BODY; - os.unindent(); - os << "end architecture behavior;\n"; - os.flush(); + if (ctx.simLanguage == VHDL) { + os << VHDL_LIBRARY_HEADER; + os.indent(); + getConstantDeclaration(os, ctx); + getSignalDeclaration(os, ctx); + os.unindent(); + os << "begin\n\n"; + os.indent(); + getDuvInstanceGeneration(os, ctx); + getMemoryInstanceGeneration(os, ctx); + deriveGlobalCompletionSignal(os, ctx); + getOutputTagGeneration(os, ctx); + os << VHDL_COMMON_TB_BODY; + os.unindent(); + os << "end architecture behavior;\n"; + os.flush(); + } + + if (ctx.simLanguage == VERILOG) { + os << VERILOG_LIBRARY_HEADER; + getConstantDeclaration(os, ctx); + getSignalDeclaration(os, ctx); + getDuvInstanceGeneration(os, ctx); + getMemoryInstanceGeneration(os, ctx); + deriveGlobalCompletionSignal(os, ctx); + getOutputTagGeneration(os, ctx); + os << VERILOG_COMMON_TB_BODY; + os.unindent(); + os << "endmodule\n"; + os.flush(); + } } diff --git a/tools/hls-verifier/resources/templates_verilog/template_single_argument.v b/tools/hls-verifier/resources/templates_verilog/template_single_argument.v index e43f23be2c..88d172e5e7 100644 --- a/tools/hls-verifier/resources/templates_verilog/template_single_argument.v +++ b/tools/hls-verifier/resources/templates_verilog/template_single_argument.v @@ -1,6 +1,3 @@ - -`timescale 1 ns / 1 ps - module single_argument ( clk, rst, @@ -17,7 +14,7 @@ module single_argument ( //------------------------Local signal------------------- parameter TV_IN = ""; parameter TV_OUT = ""; -parameter DATA_WIDTH = 32'd 32; +parameter DATA_WIDTH = 32; // Input and Output input clk; @@ -30,17 +27,20 @@ input dout0_ready; output reg dout0_valid; input done; - // Inner signals -reg [DATA_WIDTH - 1 : 0] mem; reg tokenEmitted; +reg [DATA_WIDTH-1:0] mem; +reg memReady; + +initial begin + mem = '0; + memReady = 1'b0; +end -reg writed_flag; -event write_process_done; //------------------------Task and function-------------- task read_token; input integer fp; - output reg [127 :0] token; + output string token; integer ret; begin token = ""; @@ -49,138 +49,132 @@ task read_token; end endtask -//------------------------Read array------------------- -// Read data form file to array -initial begin : read_file_process +//------------------------Read array------------------- +initial begin : file_to_mem integer fp; - integer err; + string token; integer ret; - reg [127 : 0] token; - reg [ 8*5 : 1] str; reg [ DATA_WIDTH - 1 : 0 ] mem_tmp; - integer transaction_idx; - integer i; - transaction_idx = 0; - - if(TV_IN != "")begin - - wait(rst === 0); - @(write_process_done); - fp = $fopen(TV_IN,"r"); - if(fp == 0) begin // Failed to open file - $display("Failed to open file \"%s\"!", TV_IN); - $finish; + int transaction_num; + + if (TV_IN != "") begin + wait (!rst); + transaction_num = 0; + + fp = $fopen(TV_IN, "r"); + if (fp == 0) begin + $fatal("ERROR: Could not open file %s", TV_IN); end + + // [[[runtime]]] read_token(fp, token); - if (token != "[[[runtime]]]") begin // Illegal format - $display("ERROR: Simulation using HLS TB failed."); - $finish; + if (token != "[[[runtime]]]") begin + $fatal("ERROR: Simulation failed."); end + + // Parse transactions read_token(fp, token); - while (token != "[[[/runtime]]]") begin + while (token != "[[[/runtime]]]") begin if (token != "[[transaction]]") begin - $display("ERROR: Simulation using HLS TB failed."); - $finish; + $display("ERROR: Simulation using HLS TB failed."); + $finish; end - read_token(fp, token); // skip transaction number - read_token(fp,token); - ret = $sscanf(token, "0x%x", mem_tmp); + + // discard transaction number + read_token(fp, token); + + // wait for done + @(posedge clk); + wait (done); + + // read data + read_token(fp, token); + ret = $sscanf(token, "%x", mem_tmp); + + mem = mem_tmp; + memReady = 1'b1; + if (ret != 1) begin - $display("Failed to parse token!"); - $finish; + $display("Failed to parse token!"); + $finish; end - @(write_process_done); - read_token(fp, token); + + read_token(fp,token); + wait (!done); + + // [[/transaction]] if(token != "[[/transaction]]") begin - $display("ERROR: Simulation using HLS TB failed."); - $finish; + $display("ERROR: Simulation using HLS TB failed."); + $finish; end read_token(fp, token); - transaction_idx = transaction_idx + 1; + + wait (!done); + transaction_num++; end - $fclose(fp); + $fclose(fp); end end + // Read data from array to RTL always @ (posedge clk or posedge rst) begin if(rst) begin tokenEmitted <= 1'b0; dout0 <= {DATA_WIDTH{1'b0}}; dout0_valid <= 1'b0; - end else begin - if(!(tokenEmitted)) begin + end else begin + if(!tokenEmitted && memReady) begin tokenEmitted <= 1'b1; dout0 <= mem; dout0_valid <= 1'b1; end else begin - if (dout0_ready) - dout0_valid <=1'b0; + dout0_valid <= dout0_valid & ~dout0_ready; end end end - //------------------------Write array------------------- // Write data from RTL to array -always @ (posedge clk) begin - if((we0 == 1) && (ce0 == 1)) begin +always @(posedge clk) begin + if ((we0 == 1) && (ce0 == 1)) begin mem <= din0; - $display("din0: %b",din0); end end - // Write data from array to file -initial begin : write_file_proc +initial begin : mem_to_file integer fp; - integer transaction_num; - reg [ 8*5 : 1] str; - integer i; - transaction_num = 0; - writed_flag = 1; - - if(TV_OUT !="") begin - wait(rst === 0); - - // To read the input files properly 'done' is set to '1' - // at initialization. We skip this first 'done === 1' and - // start writing the output the second time 'done' is - // equal to '1'. - while(done == 0) begin - -> write_process_done; - @(negedge clk); - end + int transaction_num; + string token; - wait(done === 0); + if (TV_OUT != "") begin + wait (!rst); + transaction_num = 0; + // skip first done + while (!done) @(posedge clk); + wait (!done); - @(negedge clk); while(1) begin - while(done == 0) begin - -> write_process_done; - @(negedge clk); - end + while (!done) @(posedge clk); + fp = $fopen(TV_OUT, "a"); - if(fp == 0) begin // Failed to open file - $display("Failed to open file \"%s\"!", TV_OUT); - $finish; + if (fp == 0) begin + $fatal("ERROR: Could not open file %s", TV_OUT); end $fdisplay(fp, "[[transaction]] %d", transaction_num); - $display("mem: %b", mem); $fdisplay(fp,"0x%x",mem); $fdisplay(fp, "[[/transaction]]"); - transaction_num = transaction_num + 1; + transaction_num++; $fclose(fp); - writed_flag = 1; - -> write_process_done; - @(negedge clk); + + wait (!done); end end end - -endmodule +endmodule \ No newline at end of file diff --git a/tools/hls-verifier/resources/templates_verilog/template_tb_join.v b/tools/hls-verifier/resources/templates_verilog/template_tb_join.v index ec4854035d..adeddabc67 100644 --- a/tools/hls-verifier/resources/templates_verilog/template_tb_join.v +++ b/tools/hls-verifier/resources/templates_verilog/template_tb_join.v @@ -32,4 +32,4 @@ module tb_join #( end end -endmodule +endmodule \ No newline at end of file diff --git a/tools/hls-verifier/resources/templates_verilog/template_two_port_RAM.v b/tools/hls-verifier/resources/templates_verilog/template_two_port_RAM.v index b3a0d035a5..848e1db5ea 100644 --- a/tools/hls-verifier/resources/templates_verilog/template_two_port_RAM.v +++ b/tools/hls-verifier/resources/templates_verilog/template_two_port_RAM.v @@ -47,7 +47,7 @@ event write_process_done; //------------------------Task and function-------------- task read_token; input integer fp; - output reg [127 :0] token; + output string token; integer ret; begin token = ""; @@ -63,7 +63,7 @@ initial begin : read_file_process integer fp; integer err; integer ret; - reg [127 : 0] token; + string token; reg [ 8*5 : 1] str; reg [ DATA_WIDTH - 1 : 0 ] mem_tmp; integer transaction_idx; @@ -201,4 +201,4 @@ always @ (posedge clk) begin $display($time,"NOTE:read & write conflict----port0 read and port1 write to the same address:%h at the same clock. Write first Mode.",address0); end -endmodule +endmodule \ No newline at end of file diff --git a/tools/hls-verifier/resources/verilator_main.cpp b/tools/hls-verifier/resources/verilator_main.cpp new file mode 100644 index 0000000000..1e0399db61 --- /dev/null +++ b/tools/hls-verifier/resources/verilator_main.cpp @@ -0,0 +1,83 @@ +// DESCRIPTION: Verilator testbench simulation +// +// Based on +// https://github.com/verilator/verilator/blob/master/examples/make_tracing_c/sim_main.cpp +// by Wilson Snyder, 2017. +// +//====================================================================== + +// For std::unique_ptr +#include + +// Include common routines +#include + +// Include model header, generated from Verilating "tb_.v" +#include "Vtb.h" + +// Legacy function required only so linking works on Cygwin and MSVC++ +double sc_time_stamp() { return 0; } + +int main(int argc, char **argv) { + + // Create logs/ directory in case we have traces to put under it + Verilated::mkdir("logs"); + + // Using unique_ptr is similar to + // "VerilatedContext* contextp = new VerilatedContext" then deleting at end. + const std::unique_ptr contextp{new VerilatedContext}; + // Do not instead make Vtop as a file-scope static variable, as the + // "C++ static initialization order fiasco" may cause a crash + + // Set debug level, 0 is off, 9 is highest presently used + // May be overridden by commandArgs argument parsing + contextp->debug(0); + + // Peak number of threads the model will use + // (e.g. match the --threads setting of the Verilation) + contextp->threads(1); + + // Randomization reset policy + // May be overridden by commandArgs argument parsing + contextp->randReset(2); + + // Verilator must compute traced signals + contextp->traceEverOn(true); + + // Pass arguments so Verilated code can see them, e.g. $value$plusargs + // This needs to be called before you create any model + contextp->commandArgs(argc, argv); + + // Construct the Verilated model, from Vtb.h generated from Verilating + // "tb_.v". Using unique_ptr is similar to "Vtb* tb = new Vtb" + // then deleting at end. "tb" will be the hierarchical name of the module. + const std::unique_ptr tb{new Vtb{contextp.get(), "tb"}}; + + // Simulate until $finish + while (!contextp->gotFinish()) { + + contextp->timeInc(1); // 1 timeprecision period passes... + + // Evaluate model + // (If you have multiple models being simulated in the same + // timestep then instead of eval(), call eval_step() on each, then + // eval_end_step() on each. See the manual.) + tb->eval(); + } + + // Final model cleanup + tb->final(); + +// Coverage analysis (calling write only after the test is known to pass) +#if VM_COVERAGE + Verilated::mkdir("logs"); + contextp->coveragep()->write("logs/coverage.dat"); +#endif + + // Final simulation summary + contextp->statsPrintSummary(); + + // Return good completion status + // Don't use exit() or destructor won't get called + return 0; +} \ No newline at end of file