diff --git a/src/V3AstNodeExpr.h b/src/V3AstNodeExpr.h index 2c6b8f86b..111e6e9f6 100644 --- a/src/V3AstNodeExpr.h +++ b/src/V3AstNodeExpr.h @@ -2439,7 +2439,8 @@ class AstWith final : public AstNodeExpr { // Children: expression (equation establishing the with) // @astgen op1 := indexArgRefp : AstLambdaArgRef // @astgen op2 := valueArgRefp : AstLambdaArgRef - // @astgen op3 := exprp : List[AstNode] + // @astgen op3 := exprp : List[AstNode] // Pins, expression and constraints + // TODO: Separate expression and constraints public: AstWith(FileLine* fl, AstLambdaArgRef* indexArgRefp, AstLambdaArgRef* valueArgRefp, AstNode* exprp) @@ -2466,12 +2467,15 @@ class AstWithParse final : public AstNodeExpr { // Parents: expr|stmt // Children: funcref, expr // @astgen op1 := funcrefp : AstNodeExpr - // @astgen op2 := exprsp : List[AstNode] + // @astgen op3 := exprsp : List[AstNode] // With's parenthesis part + // @astgen op4 := constraintsp : List[AstNode] // With's braces part public: - AstWithParse(FileLine* fl, AstNodeExpr* funcrefp, AstNode* exprsp) + AstWithParse(FileLine* fl, AstNodeExpr* funcrefp, AstNode* exprsp, + AstNode* constraintsp = nullptr) : ASTGEN_SUPER_WithParse(fl) { this->funcrefp(funcrefp); addExprsp(exprsp); + addConstraintsp(constraintsp); } ASTGEN_MEMBERS_AstWithParse; bool sameNode(const AstNode* /*samep*/) const override { return true; } diff --git a/src/V3LinkDot.cpp b/src/V3LinkDot.cpp index c84351a65..95a7496da 100644 --- a/src/V3LinkDot.cpp +++ b/src/V3LinkDot.cpp @@ -1812,12 +1812,24 @@ class LinkDotFindVisitor final : public VNVisitor { VL_DO_DANGLING(argp->deleteTree(), argp); } // Type depends on the method used, let V3Width figure it out later - if (nodep->exprsp()) { // Else empty expression and pretend no "with" + if (nodep->exprsp() + || nodep->constraintsp()) { // Else empty expression and pretend no "with" + AstNode* exprOrConstraintsp = nullptr; + if (nodep->exprsp() && nodep->constraintsp()) { + // When support this probably should change AstWith to separate out + // the expr from the constraint equation using separate op2/op3 similar + // to AstWithParse + nodep->v3warn(E_UNSUPPORTED, "Unsupported: 'randomize with (...) {...}'"); + } else if (nodep->exprsp()) + exprOrConstraintsp = nodep->exprsp()->unlinkFrBackWithNext(); + if (nodep->constraintsp()) + exprOrConstraintsp = AstNode::addNext( + exprOrConstraintsp, nodep->constraintsp()->unlinkFrBackWithNext()); AstLambdaArgRef* const indexArgRefp = new AstLambdaArgRef{argFl, name + "__DOT__index", true}; AstLambdaArgRef* const valueArgRefp = new AstLambdaArgRef{argFl, name, false}; - AstWith* const newp = new AstWith{nodep->fileline(), indexArgRefp, valueArgRefp, - nodep->exprsp()->unlinkFrBackWithNext()}; + AstWith* const newp + = new AstWith{nodep->fileline(), indexArgRefp, valueArgRefp, exprOrConstraintsp}; funcrefp->addPinsp(newp); } funcrefp->addPinsp(argp); diff --git a/src/V3ParseImp.cpp b/src/V3ParseImp.cpp index 57c00c07d..5b134e2b2 100644 --- a/src/V3ParseImp.cpp +++ b/src/V3ParseImp.cpp @@ -533,17 +533,20 @@ size_t V3ParseImp::tokenPipeScanParam(size_t inDepth, bool forCell) { return depth; } -size_t V3ParseImp::tokenPipeScanTypeEq(size_t depth) { +size_t V3ParseImp::tokenPipeScanParens(size_t depth) { // Search around IEEE type_reference to see if is expression // Return location of following token, or input if not found // yTYPE__ETC '(' ... ')' ['==' '===' '!=' '!==='] + // ^- depth + // yRANDOMIZE yWITH '(' ... ')' ['{'] + // ^- depth if (tokenPeekp(depth)->token != '(') return depth; depth += 1; // Past the ( int parens = 1; // Count first ( while (true) { const int tok = tokenPeekp(depth)->token; if (tok == 0) { // LCOV_EXCL_BR_LINE - UINFO(9, "tokenPipeScanTypeEq hit EOF; probably syntax error to come"); + UINFO(9, "tokenPipeScanTypeParens hit EOF; probably syntax error to come"); break; // LCOV_EXCL_LINE } else if (tok == '(') { ++parens; @@ -680,11 +683,11 @@ void V3ParseImp::tokenPipeline() { } } else if (token == yTYPE__LEX) { VL_RESTORER(yylval); // Remember value, as about to read ahead - const size_t depth = tokenPipeScanTypeEq(0); + const size_t depth = tokenPipeScanParens(0); const int postToken = tokenPeekp(depth)->token; - if ( // v-- token v-- postToken - // yTYPE__EQ '(' .... ')' EQ_OPERATOR yTYPE_ETC '(' ... ')' - postToken == yP_EQUAL || postToken == yP_NOTEQUAL || postToken == yP_CASEEQUAL + // v-- token v-- postToken + // yTYPE__EQ '(' .... ')' EQ_OPERATOR yTYPE_ETC '(' ... ')' + if (postToken == yP_EQUAL || postToken == yP_NOTEQUAL || postToken == yP_CASEEQUAL || postToken == yP_CASENOTEQUAL) { token = yTYPE__EQ; } else { @@ -704,7 +707,16 @@ void V3ParseImp::tokenPipeline() { } } else if (token == yWITH__LEX) { if (nexttok == '(') { - token = yWITH__PAREN; + VL_RESTORER(yylval); // Remember value, as about to read ahead + const size_t depth = tokenPipeScanParens(0); + const int postToken = tokenPeekp(depth)->token; + // v-- token v-- postToken + // yWITH '(' .... ')' '{' + if (postToken == '{') { + token = yWITH__PAREN_CUR; + } else { + token = yWITH__PAREN; + } } else if (nexttok == '[') { token = yWITH__BRA; } else if (nexttok == '{') { diff --git a/src/V3ParseImp.h b/src/V3ParseImp.h index 0fe686f7d..df7705388 100644 --- a/src/V3ParseImp.h +++ b/src/V3ParseImp.h @@ -314,7 +314,7 @@ private: size_t tokenPipeScanIdType(size_t depth) VL_MT_DISABLED; size_t tokenPipeScanBracket(size_t depth) VL_MT_DISABLED; size_t tokenPipeScanParam(size_t depth, bool forInst) VL_MT_DISABLED; - size_t tokenPipeScanTypeEq(size_t depth) VL_MT_DISABLED; + size_t tokenPipeScanParens(size_t depth) VL_MT_DISABLED; size_t tokenPipeScanEqNew(size_t depth) VL_MT_DISABLED; const V3ParseBisonYYSType* tokenPeekp(size_t depth) VL_MT_DISABLED; void preprocDumps(std::ostream& os, bool forInputs) VL_MT_DISABLED; diff --git a/src/V3Width.cpp b/src/V3Width.cpp index 4cb78352a..680483b72 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -6726,11 +6726,13 @@ class WidthVisitor final : public VNVisitor { void visit(AstWith* nodep) override { // Should otherwise be underneath a method call AstNodeDType* const vdtypep = m_vup->dtypeNullSkipRefp(); - { - VL_RESTORER(m_withp); - m_withp = nodep; - userIterateChildren(nodep->indexArgRefp(), nullptr); - userIterateChildren(nodep->valueArgRefp(), nullptr); + VL_RESTORER(m_withp); + m_withp = nodep; + userIterateChildren(nodep->indexArgRefp(), nullptr); + userIterateChildren(nodep->valueArgRefp(), nullptr); + if (!nodep->exprp()) { + nodep->dtypeSetVoid(); + } else { if (!nodep->exprp()->hasDType()) { userIterateAndNext(nodep->exprp(), nullptr); } else { diff --git a/src/verilog.y b/src/verilog.y index 3cd333854..4d49ea9b2 100644 --- a/src/verilog.y +++ b/src/verilog.y @@ -589,6 +589,7 @@ BISONPRE_VERSION(3.7,%define api.header.include {"V3ParseBison.h"}) %token yWITH__ETC "with" %token yWITH__LEX "with-in-lex" %token yWITH__PAREN "with-then-(" +%token yWITH__PAREN_CUR "with-then-(-then-{" %token yWOR "wor" %token yWREAL "wreal" %token yXNOR "xnor" @@ -4052,7 +4053,8 @@ task_subroutine_callNoMethod: // function_subroutine_callNoMethod // // IEEE: randomize_call // // We implement randomize as a normal funcRef, since randomize isn't a keyword // // Note yNULL is already part of expressions, so they come for free - | funcRef yWITH__CUR constraint_block { $$ = new AstWithParse{$2, $1, $3}; } + | funcRef yWITH__CUR constraint_block { $$ = new AstWithParse{$2, $1, nullptr, $3}; } + | funcRef yWITH__PAREN_CUR '(' expr ')' constraint_block { $$ = new AstWithParse{$2, $1, $4, $6}; } ; function_subroutine_callNoMethod: // IEEE: function_subroutine_call (as function) @@ -4067,7 +4069,8 @@ function_subroutine_callNoMethod: // IEEE: function_subroutine // // IEEE: randomize_call // // We implement randomize as a normal funcRef, since randomize isn't a keyword // // Note yNULL is already part of expressions, so they come for free - | funcRef yWITH__CUR constraint_block { $$ = new AstWithParse{$2, $1, $3}; } + | funcRef yWITH__CUR constraint_block { $$ = new AstWithParse{$2, $1, nullptr, $3}; } + | funcRef yWITH__PAREN_CUR '(' expr ')' constraint_block { $$ = new AstWithParse{$2, $1, $4, $6}; } ; system_t_call: // IEEE: system_tf_call (as task) diff --git a/test_regress/t/t_randomize_with_constraint.out b/test_regress/t/t_randomize_with_constraint.out new file mode 100644 index 000000000..b784f8905 --- /dev/null +++ b/test_regress/t/t_randomize_with_constraint.out @@ -0,0 +1,8 @@ +%Error-UNSUPPORTED: t/t_randomize_with_constraint.v:13:26: Unsupported: 'randomize with (...) {...}' + 13 | return obj.randomize() with ( + | ^~~~ + ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest +%Error-UNSUPPORTED: t/t_randomize_with_constraint.v:21:26: Unsupported: 'randomize with (...) {...}' + 21 | return obj.randomize() with ( + | ^~~~ +%Error: Exiting due to diff --git a/test_regress/t/t_randomize_with_constraint.py b/test_regress/t/t_randomize_with_constraint.py new file mode 100755 index 000000000..1bf1426f9 --- /dev/null +++ b/test_regress/t/t_randomize_with_constraint.py @@ -0,0 +1,16 @@ +#!/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('vlt') + +test.lint(fails=True, expect_filename=test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_randomize_with_constraint.v b/test_regress/t/t_randomize_with_constraint.v new file mode 100644 index 000000000..87b56dbc0 --- /dev/null +++ b/test_regress/t/t_randomize_with_constraint.v @@ -0,0 +1,36 @@ +// 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 + +class Cls; + rand int m_x; + int m_y = -1; +endclass + +function int func1(Cls obj, int y); + return obj.randomize() with ( + m_x) { + m_x > 0; + m_x < y; + }; +endfunction + +function int func2(Cls obj, int y); + return obj.randomize() with ( + m_x) { + m_x > 0; + m_x < m_y; + }; +endfunction + +module t; + initial begin + Cls c; + int i; + c = new; + i = func1(c, 2); + i = func2(c, 2); + end +endmodule