From a2fae5eb4b9d73a4cfc8c346c5fa6f722cea3d78 Mon Sep 17 00:00:00 2001 From: Tracy Narine <76846523+tmnarine@users.noreply.github.com> Date: Wed, 27 May 2026 14:33:19 -0400 Subject: [PATCH] Add `+verilator+log+file` (#4505) (#7645) Fixes #4505. --- bin/verilator | 1 + docs/guide/exe_sim.rst | 5 ++ include/verilated.cpp | 63 +++++++++++++++++++ include/verilated.h | 11 ++++ test_regress/t/t_runflag_logfile.out | 5 ++ test_regress/t/t_runflag_logfile.py | 22 +++++++ test_regress/t/t_runflag_logfile.v | 22 +++++++ test_regress/t/t_runflag_logfile_debugger.py | 35 +++++++++++ test_regress/t/t_runflag_logfile_debugger.v | 25 ++++++++ test_regress/t/t_runflag_logfile_error.out | 3 + test_regress/t/t_runflag_logfile_error.py | 22 +++++++ test_regress/t/t_runflag_logfile_error.v | 14 +++++ test_regress/t/t_runflag_logfile_open_bad.out | 2 + test_regress/t/t_runflag_logfile_open_bad.py | 35 +++++++++++ test_regress/t/t_runflag_logfile_open_bad.v | 12 ++++ 15 files changed, 277 insertions(+) create mode 100644 test_regress/t/t_runflag_logfile.out create mode 100755 test_regress/t/t_runflag_logfile.py create mode 100644 test_regress/t/t_runflag_logfile.v create mode 100755 test_regress/t/t_runflag_logfile_debugger.py create mode 100644 test_regress/t/t_runflag_logfile_debugger.v create mode 100644 test_regress/t/t_runflag_logfile_error.out create mode 100755 test_regress/t/t_runflag_logfile_error.py create mode 100644 test_regress/t/t_runflag_logfile_error.v create mode 100644 test_regress/t/t_runflag_logfile_open_bad.out create mode 100755 test_regress/t/t_runflag_logfile_open_bad.py create mode 100644 test_regress/t/t_runflag_logfile_open_bad.v diff --git a/bin/verilator b/bin/verilator index d933ec176..60abb6cc2 100755 --- a/bin/verilator +++ b/bin/verilator @@ -597,6 +597,7 @@ description of these arguments. +verilator+debugi+ Enable debugging at a level +verilator+error+limit+ Set error limit +verilator+help Show help + +verilator+log+file+ Log stdout and stderr output to filename +verilator+noassert Disable assert checking +verilator+prof+exec+file+ Set execution profile filename +verilator+prof+exec+start+ Set execution profile starting point diff --git a/docs/guide/exe_sim.rst b/docs/guide/exe_sim.rst index cb0faef15..14fecf63e 100644 --- a/docs/guide/exe_sim.rst +++ b/docs/guide/exe_sim.rst @@ -48,6 +48,11 @@ Options: Display help and exit. +.. option:: +verilator+log+file+ + + Log all stdout and stderr to the specified output filename. If not specified + the normal stdout/stderr streams are used. + .. option:: +verilator+noassert Disable assert checking per runtime argument. This is the same as diff --git a/include/verilated.cpp b/include/verilated.cpp index 82869fab1..b366fbbbc 100644 --- a/include/verilated.cpp +++ b/include/verilated.cpp @@ -61,6 +61,7 @@ #include #include #include +#include #include #include #include @@ -73,6 +74,9 @@ // clang-format off #if defined(_WIN32) || defined(__MINGW32__) # include // mkdir +# include // open, read, write, close +# define STDOUT_FILENO _fileno(stdout) +# define STDERR_FILENO _fileno(stderr) #endif #ifdef __GLIBC__ # include @@ -2158,7 +2162,13 @@ IData VL_SYSTEM_IW(int lhswords, const WDataInP lhsp) VL_MT_SAFE { return VL_SYSTEM_IN(lhs); } IData VL_SYSTEM_IN(const std::string& lhs) VL_MT_SAFE { + VerilatedContext* ctxp = Verilated::threadContextp(); + const bool sending = ctxp->logOutputToFile(); + // system() command may echo and read so restore default stdout and stderr if sending + if (sending) ctxp->logRestoreOutput(); const int code = std::system(lhs.c_str()); // Yes, std::system() is threadsafe + // Log to file again if we were sending + if (sending) ctxp->logOutputToFile(true /* append */); return code >> 8; // Want exit status } @@ -2868,6 +2878,9 @@ VerilatedContext::VerilatedContext() m_ns.m_profExecFilename = "profile_exec.dat"; m_ns.m_profVltFilename = "profile.vlt"; m_ns.m_solverProgram = VlOs::getenvStr("VERILATOR_SOLVER", VL_SOLVER_DEFAULT); + m_ns.m_logFD = -1; + m_ns.m_stdoutFD = -1; + m_ns.m_stderrFD = -1; m_fdps.resize(31); std::fill(m_fdps.begin(), m_fdps.end(), static_cast(nullptr)); m_fdFreeMct.resize(30); @@ -2879,6 +2892,7 @@ VerilatedContext::VerilatedContext() VerilatedContext::~VerilatedContext() { checkMagic(this); m_magic = 0x1; // Arbitrary but 0x1 is what Verilator src uses for a deleted pointer + logRestoreOutput(); } void VerilatedContext::checkMagic(const VerilatedContext* contextp) { @@ -2948,6 +2962,52 @@ std::string VerilatedContext::coverageFilename() const VL_MT_SAFE { const VerilatedLockGuard lock{m_mutex}; return m_ns.m_coverageFilename; } +void VerilatedContext::logFilename(const std::string& flag) VL_MT_SAFE { + const VerilatedLockGuard lock{m_mutex}; + assert(m_ns.m_logFD == -1); + m_ns.m_logFilename = flag; +} +std::string VerilatedContext::logFilename() const VL_MT_SAFE { + const VerilatedLockGuard lock{m_mutex}; + return m_ns.m_logFilename; +} +bool VerilatedContext::logOutputToFile() const VL_MT_SAFE { + const VerilatedLockGuard lock{m_mutex}; + return m_ns.m_logFD >= 0; +} +void VerilatedContext::logOutputToFile(bool append) VL_MT_SAFE { + int log_fd = ::open(m_ns.m_logFilename.c_str(), + O_WRONLY | O_CREAT | (append ? O_APPEND : O_TRUNC), 0666); + std::string error_msg; + if (log_fd >= 0) { + const VerilatedLockGuard lock{m_mutex}; + m_ns.m_logFD = log_fd; + // See https://man7.org/linux/man-pages/man2/dup.2.html + m_ns.m_stdoutFD = ::dup(STDOUT_FILENO); // Save original fd + ::dup2(m_ns.m_logFD, STDOUT_FILENO); // Replace STDOUT_FILENO with m_logFD + m_ns.m_stderrFD = ::dup(STDERR_FILENO); // Save original fd + ::dup2(m_ns.m_logFD, STDERR_FILENO); // Replace STDERR_FILENO with m_logFD + } else { + const VerilatedLockGuard lock{m_mutex}; + m_ns.m_logFD = -1; + const std::string op_str = append ? "appended" : "created"; + error_msg = "Logfile " + m_ns.m_logFilename + " cannot be " + op_str; + } + if (!error_msg.empty()) { VL_FATAL_MT("", 0, "", error_msg.c_str()); } +} +void VerilatedContext::logRestoreOutput() VL_MT_SAFE { + const VerilatedLockGuard lock{m_mutex}; + if (m_ns.m_logFD >= 0) { + std::fflush(stdout); // Flush logfile + std::fflush(stderr); + ::dup2(m_ns.m_stdoutFD, STDOUT_FILENO); // Restore STDOUT_FILENO with saved m_stdoutFD + ::dup2(m_ns.m_stderrFD, STDERR_FILENO); // Restore STDERR_FILENO with saved m_stderrFD + ::close(m_ns.m_logFD); // Close logfile + m_ns.m_logFD = -1; + m_ns.m_stdoutFD = -1; + m_ns.m_stderrFD = -1; + } +} void VerilatedContext::dumpfile(const std::string& flag) VL_MT_SAFE_EXCLUDES(m_timeDumpMutex) { const VerilatedLockGuard lock{m_timeDumpMutex}; m_dumpfile = flag; @@ -3277,6 +3337,9 @@ void VerilatedContextImp::commandArgVl(const std::string& arg) { VL_PRINTF_MT("For help, please see 'verilator --help'\n"); VL_FATAL_MT("COMMAND_LINE", 0, "", "Exiting due to command line argument (not an error)"); + } else if (commandArgVlString(arg, "+verilator+log+file+", str)) { + logFilename(str); + logOutputToFile(false /* append */); } else if (arg == "+verilator+noassert") { assertOn(false); } else if (commandArgVlUint64(arg, "+verilator+prof+exec+start+", u64)) { diff --git a/include/verilated.h b/include/verilated.h index e139b1a25..707ede5ce 100644 --- a/include/verilated.h +++ b/include/verilated.h @@ -409,6 +409,7 @@ protected: uint32_t m_profExecWindow = 2; // +prof+exec+window size // Slow path std::string m_coverageFilename; // +coverage+file filename + std::string m_logFilename; // +log+file filename std::string m_profExecFilename; // +prof+exec+file filename std::string m_profVltFilename; // +prof+vlt filename std::string m_solverLogFilename; // SMT solver log filename @@ -417,6 +418,9 @@ protected: VlOs::DeltaCpuTime m_cpuTimeStart{false}; // CPU time, starts when create first model VlOs::DeltaWallTime m_wallTimeStart{false}; // Wall time, starts when create first model std::vector m_traceBaseModelCbs; // Callbacks to traceRegisterModel + int m_stdoutFD; // Duplicated stdout file descriptor + int m_stderrFD; // Duplicated stderr file descriptor + int m_logFD; // Log file descriptor } m_ns; mutable VerilatedMutex m_argMutex; // Protect m_argVec, m_argVecLoaded @@ -650,6 +654,13 @@ public: std::string coverageFilename() const VL_MT_SAFE; void coverageFilename(const std::string& flag) VL_MT_SAFE; + // Internal: logfile + std::string logFilename() const VL_MT_SAFE; + void logFilename(const std::string& flag) VL_MT_SAFE; + bool logOutputToFile() const VL_MT_SAFE; + void logOutputToFile(bool append) VL_MT_SAFE; + void logRestoreOutput() VL_MT_SAFE; + // Internal: $dumpfile std::string dumpfile() const VL_MT_SAFE_EXCLUDES(m_timeDumpMutex); void dumpfile(const std::string& flag) VL_MT_SAFE_EXCLUDES(m_timeDumpMutex); diff --git a/test_regress/t/t_runflag_logfile.out b/test_regress/t/t_runflag_logfile.out new file mode 100644 index 000000000..b9ee28c4a --- /dev/null +++ b/test_regress/t/t_runflag_logfile.out @@ -0,0 +1,5 @@ +Hello World! +Hello 3rd rock! +Hello Mars! +Hello Pluto! +- t/t_runflag_logfile.v:20: Verilog $finish diff --git a/test_regress/t/t_runflag_logfile.py b/test_regress/t/t_runflag_logfile.py new file mode 100755 index 000000000..d74723910 --- /dev/null +++ b/test_regress/t/t_runflag_logfile.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.compile() + +logfile = test.obj_dir + "/logfile.log" + +test.execute(all_run_flags=['+verilator+log+file+' + logfile]) + +test.files_identical(logfile, test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_runflag_logfile.v b/test_regress/t/t_runflag_logfile.v new file mode 100644 index 000000000..be91610b2 --- /dev/null +++ b/test_regress/t/t_runflag_logfile.v @@ -0,0 +1,22 @@ +// DESCRIPTION: Verilator: Verilog example module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +int stdout_fd = 32'h8000_0001; +int stderr_fd = 32'h8000_0002; + +module top; + initial begin + // display + $display("Hello World!"); + $system("echo In a shell now"); + $display("Hello 3rd rock!"); + // fdisplay with stdout and stderr + $fdisplay(stdout_fd, "Hello Mars!"); + $system("echo In another shell now"); + $fdisplay(stderr_fd, "Hello Pluto!"); + $finish; + end +endmodule diff --git a/test_regress/t/t_runflag_logfile_debugger.py b/test_regress/t/t_runflag_logfile_debugger.py new file mode 100755 index 000000000..71d7ce734 --- /dev/null +++ b/test_regress/t/t_runflag_logfile_debugger.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +have_gdb = 'VERILATOR_GDB' in os.environ +if not have_gdb: + if 'VERILATOR_TEST_NO_GDB' in os.environ: + test.skip("Skipping due to VERILATOR_TEST_NO_GDB") + if not test.have_gdb: + test.skip("No gdb installed") + +test.compile() + +logfile = test.obj_dir + "/logfile.log" + +test.execute(all_run_flags=['+verilator+log+file+' + logfile]) + +# $display checks +test.file_grep(logfile, r'Hello World!') +test.file_grep(logfile, r'Hello 3rd rock!') + +# $fdisplay checks +test.file_grep(logfile, r'Hello Mars!') +test.file_grep(logfile, r'Hello Pluto!') + +test.passes() diff --git a/test_regress/t/t_runflag_logfile_debugger.v b/test_regress/t/t_runflag_logfile_debugger.v new file mode 100644 index 000000000..5190e04b4 --- /dev/null +++ b/test_regress/t/t_runflag_logfile_debugger.v @@ -0,0 +1,25 @@ +// DESCRIPTION: Verilator: Verilog example module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +// See also https://verilator.org/guide/latest/examples.html" + +// Found on Stackoverflow +int stdout_fd = 32'h8000_0001; +int stderr_fd = 32'h8000_0002; + +module top; + initial begin + // display + $display("Hello World!"); + $system("echo In a shell now"); + $display("Hello 3rd rock!"); + // fdisplay with stdout and stderr + $fdisplay(stdout_fd, "Hello Mars!"); + $system("echo In another shell now"); + $fdisplay(stderr_fd, "Hello Pluto!"); + $finish; + end +endmodule diff --git a/test_regress/t/t_runflag_logfile_error.out b/test_regress/t/t_runflag_logfile_error.out new file mode 100644 index 000000000..1fd3b3a7e --- /dev/null +++ b/test_regress/t/t_runflag_logfile_error.out @@ -0,0 +1,3 @@ +[0] %Error: t_runflag_logfile_error.v:11: Assertion failed in top.top: This is a generated error! +%Error: t/t_runflag_logfile_error.v:11: Verilog $stop +Aborting... diff --git a/test_regress/t/t_runflag_logfile_error.py b/test_regress/t/t_runflag_logfile_error.py new file mode 100755 index 000000000..b36852ef3 --- /dev/null +++ b/test_regress/t/t_runflag_logfile_error.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.compile() + +logfile = test.obj_dir + "/logfile.log" + +test.execute(all_run_flags=['+verilator+log+file+' + logfile], fails=True) + +test.files_identical(logfile, test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_runflag_logfile_error.v b/test_regress/t/t_runflag_logfile_error.v new file mode 100644 index 000000000..1c51216e7 --- /dev/null +++ b/test_regress/t/t_runflag_logfile_error.v @@ -0,0 +1,14 @@ +// DESCRIPTION: Verilator: Verilog example module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +// See also https://verilator.org/guide/latest/examples.html" + +module top; + initial begin + $error("This is a generated error!"); + $finish; + end +endmodule diff --git a/test_regress/t/t_runflag_logfile_open_bad.out b/test_regress/t/t_runflag_logfile_open_bad.out new file mode 100644 index 000000000..eb006a323 --- /dev/null +++ b/test_regress/t/t_runflag_logfile_open_bad.out @@ -0,0 +1,2 @@ +%Error: Logfile obj_vlt/t_runflag_logfile_open_bad/logfile.log cannot be created +Aborting... diff --git a/test_regress/t/t_runflag_logfile_open_bad.py b/test_regress/t/t_runflag_logfile_open_bad.py new file mode 100755 index 000000000..1da923d67 --- /dev/null +++ b/test_regress/t/t_runflag_logfile_open_bad.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import os +import stat +import vltest_bootstrap + +test.scenarios('vlt') + +test.compile() + +logfile = test.obj_dir + "/logfile.log" +capturefile = test.obj_dir + "/capture.log" + +# Create the logfile +with open(logfile, 'w', encoding='utf8') as f: + f.write("Read-only logfile\n") +# Change permission to read only so execute() fail when creating the logfile +os.chmod(logfile, stat.S_IREAD) + +log_flags = '+verilator+log+file+' + logfile +test.execute(all_run_flags=[log_flags, ">", capturefile, "2>&1"], fails=True) + +# Set read + write so test does not fail next time it is run +os.chmod(logfile, stat.S_IREAD | stat.S_IWRITE) + +test.files_identical(capturefile, test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_runflag_logfile_open_bad.v b/test_regress/t/t_runflag_logfile_open_bad.v new file mode 100644 index 000000000..d7ad7dd5d --- /dev/null +++ b/test_regress/t/t_runflag_logfile_open_bad.v @@ -0,0 +1,12 @@ +// DESCRIPTION: Verilator: Verilog example module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +module top; + initial begin + $display("We should not see this message"); + $finish; + end +endmodule