From e6beab40371bb024a0ed51ae2caa231ad35b8ed4 Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Tue, 31 Mar 2020 20:42:07 -0400 Subject: [PATCH] Fix implicit conversion of floats to wide integers. --- Changes | 2 + include/verilated.h | 63 +++++++++++++++-- src/V3Ast.h | 1 + src/V3AstNodes.h | 11 +-- src/V3Number.cpp | 24 ++++++- src/V3Width.cpp | 19 ++++-- test_regress/t/t_math_real_round.pl | 21 ++++++ test_regress/t/t_math_real_round.v | 101 ++++++++++++++++++++++++++++ 8 files changed, 224 insertions(+), 18 deletions(-) create mode 100755 test_regress/t/t_math_real_round.pl create mode 100644 test_regress/t/t_math_real_round.v diff --git a/Changes b/Changes index 33b16aba5..4dafd40f2 100644 --- a/Changes +++ b/Changes @@ -23,6 +23,8 @@ The contributors that suggested a given feature are shown in []. Thanks! **** Fix assertions with unique case inside, #2199. [hdzhangdoc] +**** Fix implicit conversion of floats to wide integers. + * Verilator 4.030 2020-03-08 diff --git a/include/verilated.h b/include/verilated.h index 8e64ae00c..7381e67b1 100644 --- a/include/verilated.h +++ b/include/verilated.h @@ -695,13 +695,12 @@ static inline QData VL_CVT_Q_D(double lhs) VL_PURE { union { double d; QData q; } u; u.d=lhs; return u.q; } /// Return double from QData (numeric) static inline double VL_ITOR_D_I(IData lhs) VL_PURE { - return static_cast(static_cast(lhs)); } + return static_cast(static_cast(lhs)); +} /// Return QData from double (numeric) -static inline IData VL_RTOI_I_D(double lhs) VL_PURE { - return static_cast(VL_TRUNC(lhs)); } -/// Return QData from double (numeric) -static inline IData VL_RTOIROUND_I_D(double lhs) VL_PURE { - return static_cast(VL_ROUND(lhs)); } +static inline IData VL_RTOI_I_D(double lhs) VL_PURE { + return static_cast(VL_TRUNC(lhs)); +} // Sign extend such that if MSB set, we get ffff_ffff, else 0s // (Requires clean input) @@ -1320,6 +1319,14 @@ static inline WDataOutP VL_NEGATE_W(int words, WDataOutP owp, WDataInP lwp) VL_M } return owp; } +static void VL_NEGATE_INPLACE_W(int words, WDataOutP owp_lwp) VL_MT_SAFE { + EData carry = 1; + for (int i = 0; i < words; ++i) { + EData word = ~owp_lwp[i] + carry; + carry = (word < ~owp_lwp[i]); + owp_lwp[i] = word; + } +} // EMIT_RULE: VL_MUL: oclean=dirty; lclean==clean; rclean==clean; // EMIT_RULE: VL_DIV: oclean=dirty; lclean==clean; rclean==clean; @@ -2236,6 +2243,50 @@ static inline WDataOutP VL_SEL_WWII(int obits, int lbits, int, int, return owp; } +//====================================================================== +// Math needing insert/select + +/// Return QData from double (numeric) +// EMIT_RULE: VL_RTOIROUND_Q_D: oclean=dirty; lclean==clean/real +static inline QData VL_RTOIROUND_Q_D(int bits, double lhs) VL_PURE { + // IEEE format: [63]=sign [62:52]=exp+1023 [51:0]=mantissa + // This does not need to support subnormals as they are sub-integral + lhs = VL_ROUND(lhs); + if (!lhs) return 0; + QData q = VL_CVT_Q_D(lhs); + int lsb = static_cast((q >> VL_ULL(52)) & VL_MASK_Q(11)) - 1023 - 52; + vluint64_t mantissa = (q & VL_MASK_Q(52)) | (VL_ULL(1) << 52); + vluint64_t out = 0; + if (lsb < 0) { + out = mantissa >> -lsb; + } else if (lsb < 64) { + out = mantissa << lsb; + } + if (lhs < 0) out = -out; + return out; +} +static inline IData VL_RTOIROUND_I_D(int bits, double lhs) VL_PURE { + return static_cast(VL_RTOIROUND_Q_D(bits, lhs)); +} +static inline WDataOutP VL_RTOIROUND_W_D(int obits, WDataOutP owp, double lhs) VL_PURE { + // IEEE format: [63]=sign [62:52]=exp+1023 [51:0]=mantissa + // This does not need to support subnormals as they are sub-integral + lhs = VL_ROUND(lhs); + VL_ZERO_W(obits, owp); + if (!lhs) return owp; + QData q = VL_CVT_Q_D(lhs); + int lsb = static_cast((q >> VL_ULL(52)) & VL_MASK_Q(11)) - 1023 - 52; + vluint64_t mantissa = (q & VL_MASK_Q(52)) | (VL_ULL(1) << 52); + if (lsb < 0) { + VL_SET_WQ(owp, mantissa >> -lsb); + } else if (lsb < obits) { + int lsbword = VL_BITWORD_I(lsb); + _VL_INSERT_WQ(obits, owp, mantissa, lsb + 52, lsb); + } + if (lhs < 0) VL_NEGATE_INPLACE_W(VL_WORDS_I(obits), owp); + return owp; +} + //====================================================================== // Range assignments diff --git a/src/V3Ast.h b/src/V3Ast.h index 49ebadab8..71fe67c5c 100644 --- a/src/V3Ast.h +++ b/src/V3Ast.h @@ -1693,6 +1693,7 @@ public: // METHODS virtual bool hasDType() const { return true; } virtual string emitVerilog() = 0; /// Format string for verilog writing; see V3EmitV + // For documentation on emitC format see EmitCStmts::emitOpName virtual string emitC() = 0; virtual string emitSimpleOperator() { return ""; } virtual bool cleanOut() const = 0; // True if output has extra upper bits zero diff --git a/src/V3AstNodes.h b/src/V3AstNodes.h index 4ddb7b024..6a23d8435 100644 --- a/src/V3AstNodes.h +++ b/src/V3AstNodes.h @@ -4585,16 +4585,19 @@ public: virtual int instrCount() const { return instrCountDouble(); } }; class AstRToIRoundS : public AstNodeUniop { + // Convert real to integer, with arbitrary sized output (not just "integer" format) public: AstRToIRoundS(FileLine* fl, AstNode* lhsp) - : ASTGEN_SUPER(fl, lhsp) { dtypeSetSigned32(); } + : ASTGEN_SUPER(fl, lhsp) { + dtypeSetSigned32(); + } ASTNODE_NODE_FUNCS(RToIRoundS) virtual void numberOperate(V3Number& out, const V3Number& lhs) { out.opRToIRoundS(lhs); } virtual string emitVerilog() { return "%f$rtoi_rounded(%l)"; } - virtual string emitC() { return "VL_RTOIROUND_I_D(%li)"; } + virtual string emitC() { return "VL_RTOIROUND_%nq_D(%nw, %P, %li)"; } virtual bool cleanOut() const { return false; } - virtual bool cleanLhs() const { return false; } // Eliminated before matters - virtual bool sizeMattersLhs() const { return false; } // Eliminated before matters + virtual bool cleanLhs() const { return false; } + virtual bool sizeMattersLhs() const { return false; } virtual int instrCount() const { return instrCountDouble(); } }; class AstIToRD : public AstNodeUniop { diff --git a/src/V3Number.cpp b/src/V3Number.cpp index f1cad5eee..d397d203d 100644 --- a/src/V3Number.cpp +++ b/src/V3Number.cpp @@ -2055,8 +2055,28 @@ V3Number& V3Number::opRToIRoundS(const V3Number& lhs) { NUM_ASSERT_OP_ARGS1(lhs); NUM_ASSERT_DOUBLE_ARGS1(lhs); double v = VL_ROUND(lhs.toDouble()); - vlsint32_t i = static_cast(v); // C converts from double to vlsint32 - return setLongS(i); + setZero(); + union { double d; vluint64_t q; } u; + u.d = v; if (u.d) {} + + int exp = static_cast((u.q >> VL_ULL(52)) & VL_MASK_Q(11)) - 1023; + int lsb = exp - 52; + vluint64_t mantissa = (u.q & VL_MASK_Q(52)) | (VL_ULL(1) << 52); + if (v != 0) { + // IEEE format: [63]=sign [62:52]=exp+1023 [51:0]=mantissa + // This does not need to support subnormals as they are sub-integral + for (int bit = 0; bit <= 52; ++bit) { + if (mantissa & (VL_ULL(1) << bit)) { + int outbit = bit + lsb; + if (outbit >= 0) setBit(outbit, 1); + } + } + if (v < 0) { + V3Number noSign = *this; + opNegate(noSign); + } + } + return *this; } V3Number& V3Number::opRealToBits(const V3Number& lhs) { NUM_ASSERT_OP_ARGS1(lhs); diff --git a/src/V3Width.cpp b/src/V3Width.cpp index 93e2a1046..dbe2f591c 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -322,11 +322,17 @@ private: virtual void visit(AstIToRD* nodep) VL_OVERRIDE { visit_Or_Ls32(nodep); } // Widths: Output integer signed, input real - virtual void visit(AstRToIS* nodep) VL_OVERRIDE { visit_Os32_Lr(nodep); } - virtual void visit(AstRToIRoundS* nodep) VL_OVERRIDE { visit_Os32_Lr(nodep); } + virtual void visit(AstRToIS* nodep) VL_OVERRIDE { visit_Os32_Lr(nodep); } + virtual void visit(AstRToIRoundS* nodep) VL_OVERRIDE { + // Only created here, size comes from upper expression + if (m_vup->prelim()) { // First stage evaluation + iterateCheckReal(nodep, "LHS", nodep->lhsp(), BOTH); + } + if (!nodep->dtypep()->widthSized()) nodep->v3fatalSrc("RToIRoundS should be presized"); + } // Widths: Output integer unsigned, input real - virtual void visit(AstRealToBits* nodep) VL_OVERRIDE { visit_Ou64_Lr(nodep); } + virtual void visit(AstRealToBits* nodep) VL_OVERRIDE { visit_Ou64_Lr(nodep); } // Output integer, input string virtual void visit(AstLenN* nodep) VL_OVERRIDE { visit_Os32_string(nodep); } @@ -4180,7 +4186,7 @@ private: underp = spliceCvtD(underp); underp = userIterateSubtreeReturnEdits(underp, WidthVP(SELF, FINAL).p()); } else if (!expDTypep->isDouble() && underp->isDouble()) { - underp = spliceCvtS(underp, true); // Round RHS + underp = spliceCvtS(underp, true, expDTypep->width()); // Round RHS underp = userIterateSubtreeReturnEdits(underp, WidthVP(SELF, FINAL).p()); } else if (expDTypep->isString() && !underp->dtypep()->isString()) { underp = spliceCvtString(underp); @@ -4309,7 +4315,7 @@ private: if (nodep && nodep->isDouble()) { nodep->v3error("Expected integral (non-" << nodep->dtypep()->prettyDTypeName() << ") input to " << nodep->backp()->prettyTypeName()); - nodep = spliceCvtS(nodep, true); + nodep = spliceCvtS(nodep, true, 32); } return nodep; } @@ -4328,7 +4334,7 @@ private: return nodep; } } - AstNode* spliceCvtS(AstNode* nodep, bool warnOn) { + AstNode* spliceCvtS(AstNode* nodep, bool warnOn, int width) { // IEEE-2012 11.8.1: Signed: Type coercion creates signed // 11.8.2: Argument to convert is self-determined if (nodep && nodep->dtypep()->skipRefp()->isDouble()) { @@ -4338,6 +4344,7 @@ private: if (warnOn) nodep->v3warn(REALCVT, "Implicit conversion of real to integer"); AstNode* newp = new AstRToIRoundS(nodep->fileline(), nodep); linker.relink(newp); + newp->dtypeSetBitSized(width, AstNumeric::SIGNED); return newp; } else { return nodep; diff --git a/test_regress/t/t_math_real_round.pl b/test_regress/t/t_math_real_round.pl new file mode 100755 index 000000000..1d046ed3f --- /dev/null +++ b/test_regress/t/t_math_real_round.pl @@ -0,0 +1,21 @@ +#!/usr/bin/perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2003 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 + +scenarios(simulator => 1); + +compile( + ); + +execute( + check_finished => 1, + ); + +ok(1); +1; diff --git a/test_regress/t/t_math_real_round.v b/test_regress/t/t_math_real_round.v new file mode 100644 index 000000000..5c6a56870 --- /dev/null +++ b/test_regress/t/t_math_real_round.v @@ -0,0 +1,101 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// Copyright 2011 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 + +`define is_near_real(a,b) (( ((a)<(b)) ? (b)-(a) : (a)-(b)) < (((a)/(b))*0.0001)) +`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", `__FILE__,`__LINE__, (gotv), (expv)); $stop; end while(0); + +module t (/*AUTOARG*/ + // Inputs + clk + ); + input clk; + + integer cyc=0; + + real r; + reg [31:0] v32; + reg [63:0] v64; + reg [95:0] v96; + + initial begin + // verilator lint_off REALCVT + v32 = -1.5; + v64 = -1.5; + v96 = -1.5; + // verilator lint_on REALCVT + `checkh(v32, 32'hfffffffe); + `checkh(v64, 64'hfffffffffffffffe); + `checkh(v96, 96'hfffffffffffffffffffffffe); + + // verilator lint_off REALCVT + v32 = 12456789012345678912345.5; + v64 = 12456789012345678912345.5; + v96 = 12456789012345678912345.5; + // verilator lint_on REALCVT + `checkh(v32, 32'he5400000); + `checkh(v64, 64'h48acb7d4e5400000); + `checkh(v96, 96'h000002a348acb7d4e5400000); + + // verilator lint_off REALCVT + v32 = -12456789012345678912345.5; + v64 = -12456789012345678912345.5; + v96 = -12456789012345678912345.5; + // verilator lint_on REALCVT + `checkh(v32, 32'h1ac00000); + `checkh(v64, 64'hb753482b1ac00000); + `checkh(v96, 96'hfffffd5cb753482b1ac00000); + end + + // Test loop + always @ (posedge clk) begin + cyc <= cyc + 1; + if (cyc == 10) begin + r <= 0; + end + else if (cyc == 11) begin + // verilator lint_off REALCVT + v32 = r; + v64 = r; + v96 = r; + // verilator lint_on REALCVT + `checkh(v32, '0); + `checkh(v64, '0); + `checkh(v96, '0); + end + else if (cyc == 20) begin + r <= -5.24567; + end + else if (cyc == 21) begin + // verilator lint_off REALCVT + v32 = r; + v64 = r; + v96 = r; + // verilator lint_on REALCVT + `checkh(v32, 32'hfffffffb); + `checkh(v64, 64'hfffffffffffffffb); + `checkh(v96, 96'hfffffffffffffffffffffffb); + end + else if (cyc == 30) begin + r <= 12456789012345678912345.5; + end + else if (cyc == 31) begin + // verilator lint_off REALCVT + v32 = r; + v64 = r; + v96 = r; + // verilator lint_on REALCVT + `checkh(v32, 32'he5400000); + `checkh(v64, 64'h48acb7d4e5400000); + `checkh(v96, 96'h000002a348acb7d4e5400000); + end + else if (cyc == 99) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end +endmodule