From 67f26508badcdff20875988ea09ca655e859848f Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Mon, 1 Sep 2025 14:40:22 -0400 Subject: [PATCH] Support `$sscanf %t` --- include/verilated.cpp | 23 ++++++++++++++++-- src/V3AstNodeExpr.h | 6 ++++- src/V3EmitCFunc.cpp | 20 ++++++++-------- src/V3LinkParse.cpp | 5 ++++ test_regress/t/t_time_sscanf.py | 18 +++++++++++++++ test_regress/t/t_time_sscanf.v | 41 +++++++++++++++++++++++++++++++++ 6 files changed, 101 insertions(+), 12 deletions(-) create mode 100755 test_regress/t/t_time_sscanf.py create mode 100644 test_regress/t/t_time_sscanf.v diff --git a/include/verilated.cpp b/include/verilated.cpp index c3f44dbde..6774557bb 100644 --- a/include/verilated.cpp +++ b/include/verilated.cpp @@ -1329,7 +1329,7 @@ IData _vl_vsscanf(FILE* fp, // If a fscanf default: { // Deal with all read-and-scan somethings // Note LSBs are preserved if there's an overflow - const int obits = inIgnore ? 0 : va_arg(ap, int); + int obits = inIgnore ? 0 : va_arg(ap, int); VlWide qowp; VL_SET_WQ(qowp, 0ULL); WDataOutP owp = qowp; @@ -1390,7 +1390,26 @@ IData _vl_vsscanf(FILE* fp, // If a fscanf VL_SET_WQ(owp, u.ld); break; } - case 't': // FALLTHRU // Time + case 't': { // Time + _vl_vsss_skipspace(fp, floc, fromp, fstr); + _vl_vsss_read_str(fp, floc, fromp, fstr, t_tmp, "+-.0123456789eE"); + if (!t_tmp[0]) goto done; + union { + double r; + int64_t ld; + } u; + // Get pointer argument first, as proceeds the timeunit value + if (obits != 64) goto done; + QData* const realp = va_arg(ap, QData*); + const int timeunit = va_arg(ap, int); + const int userUnits + = Verilated::threadContextp()->impp()->timeFormatUnits(); // 0..-15 + const int shift = -userUnits + timeunit; // 0..-15 + u.r = std::strtod(t_tmp, nullptr) * vl_time_multiplier(-shift); + *realp = VL_CLEAN_QQ(obits, obits, u.ld); + obits = 0; // Already loaded the value, don't read arg + break; + } case '#': { // Unsigned decimal _vl_vsss_skipspace(fp, floc, fromp, fstr); _vl_vsss_read_str(fp, floc, fromp, fstr, t_tmp, "0123456789+-xXzZ?_"); diff --git a/src/V3AstNodeExpr.h b/src/V3AstNodeExpr.h index 7ff9ccbf9..85b59620a 100644 --- a/src/V3AstNodeExpr.h +++ b/src/V3AstNodeExpr.h @@ -2016,6 +2016,7 @@ class AstSScanF final : public AstNodeExpr { // @astgen op1 := exprsp : List[AstNode] // VarRefs for results // @astgen op2 := fromp : AstNode string m_text; + VTimescale m_timeunit; // Parent module time unit public: AstSScanF(FileLine* fl, const string& text, AstNode* fromp, AstNode* exprsp) @@ -2035,10 +2036,13 @@ public: bool isOutputter() override { return true; } // SPECIAL: makes output bool cleanOut() const override { return false; } bool sameNode(const AstNode* samep) const override { - return text() == VN_DBG_AS(samep, SScanF)->text(); + const AstSScanF* const asamep = VN_DBG_AS(samep, SScanF); + return text() == asamep->text() && timeunit() == asamep->timeunit(); } string text() const { return m_text; } // * = Text to display void text(const string& text) { m_text = text; } + VTimescale timeunit() const { return m_timeunit; } + void timeunit(const VTimescale& flag) { m_timeunit = flag; } }; class AstSampled final : public AstNodeExpr { // Verilog $sampled diff --git a/src/V3EmitCFunc.cpp b/src/V3EmitCFunc.cpp index 05bb63602..8cf009757 100644 --- a/src/V3EmitCFunc.cpp +++ b/src/V3EmitCFunc.cpp @@ -290,18 +290,20 @@ void EmitCFunc::displayArg(AstNode* dispp, AstNode** elistp, bool isScan, const } m_emitDispState.pushArg(fmtLetter, argp, ""); if (fmtLetter == 't' || fmtLetter == '^') { - const AstSFormatF* fmtp = nullptr; + VTimescale timeunit = VTimescale::NONE; if (const AstDisplay* const nodep = VN_CAST(dispp, Display)) { - fmtp = nodep->fmtp(); + timeunit = nodep->fmtp()->timeunit(); } else if (const AstSFormat* const nodep = VN_CAST(dispp, SFormat)) { - fmtp = nodep->fmtp(); - } else { - fmtp = VN_CAST(dispp, SFormatF); + timeunit = nodep->fmtp()->timeunit(); + } else if (const AstSScanF* const nodep = VN_CAST(dispp, SScanF)) { + timeunit = nodep->timeunit(); + } else if (const AstSFormatF* const nodep = VN_CAST(dispp, SFormatF)) { + timeunit = nodep->timeunit(); } - UASSERT_OBJ(fmtp, dispp, - "Use of %t must be under AstDisplay, AstSFormat, or AstSFormatF"); - UASSERT_OBJ(!fmtp->timeunit().isNone(), fmtp, "timenunit must be set"); - m_emitDispState.pushArg(' ', nullptr, cvtToStr((int)fmtp->timeunit().powerOfTen())); + UASSERT_OBJ(!timeunit.isNone(), dispp, + "Use of %t must be under AstDisplay, AstSFormat, or AstSFormatF, or " + "SScanF, and timeunit set"); + m_emitDispState.pushArg(' ', nullptr, cvtToStr((int)timeunit.powerOfTen())); } } else { m_emitDispState.pushArg(fmtLetter, nullptr, ""); diff --git a/src/V3LinkParse.cpp b/src/V3LinkParse.cpp index 17f7a4137..14190022d 100644 --- a/src/V3LinkParse.cpp +++ b/src/V3LinkParse.cpp @@ -815,6 +815,11 @@ class LinkParseVisitor final : public VNVisitor { iterateChildren(nodep); nodep->timeunit(m_modp->timeunit()); } + void visit(AstSScanF* nodep) override { + cleanFileline(nodep); + iterateChildren(nodep); + nodep->timeunit(m_modp->timeunit()); + } void visit(AstTime* nodep) override { cleanFileline(nodep); iterateChildren(nodep); diff --git a/test_regress/t/t_time_sscanf.py b/test_regress/t/t_time_sscanf.py new file mode 100755 index 000000000..f989a35fb --- /dev/null +++ b/test_regress/t/t_time_sscanf.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2025 by Wilson Snyder. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile() + +test.execute() + +test.passes() diff --git a/test_regress/t/t_time_sscanf.v b/test_regress/t/t_time_sscanf.v new file mode 100644 index 000000000..d2ecd5eb0 --- /dev/null +++ b/test_regress/t/t_time_sscanf.v @@ -0,0 +1,41 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2025 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +`define checkr(gotv,expv) do if ((gotv) != (expv)) begin $write("%%Error: %s:%0d: got=%f exp=%f\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +// verilog_format: on + +`timescale 1ns / 100ps + +module main; + + real r; + integer rc; + time t; + + // verilator lint_off REALCVT + + initial begin + rc = $sscanf("8.125", "%f", r); // as real + `checkd(rc, 1); + `checkr(r, 8.125); + + rc = $sscanf("8125", "%t", r); // in ns but round to 100 ps + `checkd(rc, 1); + t = r; + `checkr(t, 813); + + $timeformat(-3, 2, "ms", 10); + rc = $sscanf("8.125", "%t", r); // in ms + `checkd(rc, 1); + t = r; + `checkr(t, 8125000); + + $finish; + end +endmodule