From aacb38b776c3d3998d37dd22d509c994407dda98 Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Sat, 19 Nov 2022 15:23:37 -0500 Subject: [PATCH] Support assignment expressions. --- Changes | 1 + src/V3Cdc.cpp | 3 +- src/V3Delayed.cpp | 4 +- src/V3DfgOptimizer.cpp | 4 +- src/V3Expand.cpp | 4 +- src/V3Order.cpp | 4 +- src/V3Simulate.h | 4 +- src/V3Slice.cpp | 2 +- src/V3Split.cpp | 2 +- src/V3Subst.cpp | 3 +- src/V3Task.cpp | 2 +- src/V3Tristate.cpp | 5 +- src/verilog.y | 117 ++++++++++++++++++++++---------- test_regress/t/t_assign_expr.pl | 21 ++++++ test_regress/t/t_assign_expr.v | 77 +++++++++++++++++++++ 15 files changed, 201 insertions(+), 52 deletions(-) create mode 100755 test_regress/t/t_assign_expr.pl create mode 100644 test_regress/t/t_assign_expr.v diff --git a/Changes b/Changes index 15133343c..205064554 100644 --- a/Changes +++ b/Changes @@ -18,6 +18,7 @@ Verilator 5.003 devel * Support randcase. * Support pre_randomize and post_randomize. * Support $timeunit and $timeprecision. +* Support assignment expressions. * Add ENUMVALUE warning when value misused for enum (#726). * Internal AST improvements, also affect XML format (#3721). [Geza Lore] * Change ENDLABEL from warning into an error. diff --git a/src/V3Cdc.cpp b/src/V3Cdc.cpp index 54edca7c1..b47dc53d3 100644 --- a/src/V3Cdc.cpp +++ b/src/V3Cdc.cpp @@ -237,6 +237,7 @@ private: // METHODS void iterateNewStmt(AstNode* nodep) { if (m_scopep) { + VL_RESTORER(m_logicVertexp); UINFO(4, " STMT " << nodep << endl); m_logicVertexp = new CdcLogicVertex(&m_graph, m_scopep, nodep, m_domainp); if (m_domainp && m_domainp->hasClocked()) { // To/from a flop @@ -247,7 +248,6 @@ private: m_logicVertexp->dstDomainSet(true); } iterateChildren(nodep); - m_logicVertexp = nullptr; if (false && debug() >= 9) { UINFO(9, "Trace Logic:\n"); @@ -680,6 +680,7 @@ private: } } void visit(AstAssignDly* nodep) override { + VL_RESTORER(m_inDly); m_inDly = true; iterateChildren(nodep); m_inDly = false; diff --git a/src/V3Delayed.cpp b/src/V3Delayed.cpp index 56f67b57d..db5a07182 100644 --- a/src/V3Delayed.cpp +++ b/src/V3Delayed.cpp @@ -505,6 +505,8 @@ private: nodep->deleteTree(); } void visit(AstAssignDly* nodep) override { + VL_RESTORER(m_inDly); + VL_RESTORER(m_nextDlyp); m_inDly = true; m_nextDlyp = VN_CAST(nodep->nextp(), AssignDly); // Next assignment in same block, maybe nullptr. @@ -537,8 +539,6 @@ private: } else { iterateChildren(nodep); } - m_inDly = false; - m_nextDlyp = nullptr; } void visit(AstVarRef* nodep) override { diff --git a/src/V3DfgOptimizer.cpp b/src/V3DfgOptimizer.cpp index d3b2eba4f..94f880edb 100644 --- a/src/V3DfgOptimizer.cpp +++ b/src/V3DfgOptimizer.cpp @@ -190,18 +190,18 @@ class DataflowExtractVisitor final : public VNVisitor { } void visit(AstAssignForce* nodep) override { + VL_RESTORER(m_inForceReleaseLhs); iterate(nodep->rhsp()); UASSERT_OBJ(!m_inForceReleaseLhs, nodep, "Should not nest"); m_inForceReleaseLhs = true; iterate(nodep->lhsp()); - m_inForceReleaseLhs = false; } void visit(AstRelease* nodep) override { + VL_RESTORER(m_inForceReleaseLhs); UASSERT_OBJ(!m_inForceReleaseLhs, nodep, "Should not nest"); m_inForceReleaseLhs = true; iterate(nodep->lhsp()); - m_inForceReleaseLhs = false; } void visit(AstNodeExpr* nodep) override { iterateChildrenConst(nodep); } diff --git a/src/V3Expand.cpp b/src/V3Expand.cpp index d7d957654..d67b05d5a 100644 --- a/src/V3Expand.cpp +++ b/src/V3Expand.cpp @@ -828,12 +828,13 @@ private: void visit(AstNodeStmt* nodep) override { if (nodep->user1SetOnce()) return; // Process once + VL_RESTORER(m_stmtp); m_stmtp = nodep; iterateChildren(nodep); - m_stmtp = nullptr; } void visit(AstNodeAssign* nodep) override { if (nodep->user1SetOnce()) return; // Process once + VL_RESTORER(m_stmtp); m_stmtp = nodep; iterateChildren(nodep); bool did = false; @@ -869,7 +870,6 @@ private: } // Cleanup common code if (did) VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep); - m_stmtp = nullptr; } //-------------------- diff --git a/src/V3Order.cpp b/src/V3Order.cpp index 450ecd583..bd3a3ee6e 100644 --- a/src/V3Order.cpp +++ b/src/V3Order.cpp @@ -396,15 +396,15 @@ class OrderBuildVisitor final : public VNVisitor { void visit(AstAssignW* nodep) override { iterateLogic(nodep); } void visit(AstAssignPre* nodep) override { UASSERT_OBJ(!m_inPre, nodep, "Should not nest"); + VL_RESTORER(m_inPre); m_inPre = true; iterateLogic(nodep); - m_inPre = false; } void visit(AstAssignPost* nodep) override { UASSERT_OBJ(!m_inPost, nodep, "Should not nest"); + VL_RESTORER(m_inPost); m_inPost = true; iterateLogic(nodep); - m_inPost = false; } //--- Verilator concoctions diff --git a/src/V3Simulate.h b/src/V3Simulate.h index 02c8344d0..788d5e696 100644 --- a/src/V3Simulate.h +++ b/src/V3Simulate.h @@ -759,6 +759,9 @@ private: if (jumpingOver(nodep)) return; if (!optimizable()) return; // Accelerate checkNodeInfo(nodep); + + VL_RESTORER(m_inDlyAssign); + if (VN_IS(nodep, AssignForce)) { clearOptimizable(nodep, "Force"); } else if (VN_IS(nodep, AssignDly)) { @@ -793,7 +796,6 @@ private: assignOutValue(nodep, vscp, fetchValue(nodep->rhsp())); } } - m_inDlyAssign = false; } void visit(AstArraySel* nodep) override { checkNodeInfo(nodep); diff --git a/src/V3Slice.cpp b/src/V3Slice.cpp index 07041f204..6cab91c4f 100644 --- a/src/V3Slice.cpp +++ b/src/V3Slice.cpp @@ -156,9 +156,9 @@ class SliceVisitor final : public VNVisitor { // This will potentially call this function again to resolve next level of slicing return; } + VL_RESTORER(m_assignp); m_assignp = nodep; iterateChildren(nodep); - m_assignp = nullptr; } } diff --git a/src/V3Split.cpp b/src/V3Split.cpp index f5828b111..a27d1f62c 100644 --- a/src/V3Split.cpp +++ b/src/V3Split.cpp @@ -340,10 +340,10 @@ protected: // of what is before vs. after void visit(AstAssignDly* nodep) override { + VL_RESTORER(m_inDly); m_inDly = true; UINFO(4, " ASSIGNDLY " << nodep << endl); iterateChildren(nodep); - m_inDly = false; } void visit(AstVarRef* nodep) override { if (!m_stmtStackps.empty()) { diff --git a/src/V3Subst.cpp b/src/V3Subst.cpp index 4df63e5c8..34fa8951e 100644 --- a/src/V3Subst.cpp +++ b/src/V3Subst.cpp @@ -252,6 +252,7 @@ private: // VISITORS void visit(AstNodeAssign* nodep) override { + VL_RESTORER(m_ops); m_ops = 0; m_assignStep++; iterateAndNextNull(nodep->rhsp()); @@ -360,7 +361,7 @@ private: doDeletes(); } void visit(AstNode* nodep) override { - m_ops++; + ++m_ops; if (!nodep->isSubstOptimizable()) m_ops = SUBST_MAX_OPS_NA; iterateChildren(nodep); } diff --git a/src/V3Task.cpp b/src/V3Task.cpp index fea8d97c1..59aaca246 100644 --- a/src/V3Task.cpp +++ b/src/V3Task.cpp @@ -189,9 +189,9 @@ private: iterateChildren(nodep); } void visit(AstAssignW* nodep) override { + VL_RESTORER(m_assignwp); m_assignwp = nodep; VL_DO_DANGLING(iterateChildren(nodep), nodep); // May delete nodep. - m_assignwp = nullptr; } void visit(AstNodeFTaskRef* nodep) override { // Includes handling AstMethodCall, AstNew diff --git a/src/V3Tristate.cpp b/src/V3Tristate.cpp index 7e35067eb..d5dda0f54 100644 --- a/src/V3Tristate.cpp +++ b/src/V3Tristate.cpp @@ -1229,6 +1229,8 @@ class TristateVisitor final : public TristateBaseVisitor { void visit(AstOr* nodep) override { visitAndOr(nodep, false); } void visitAssign(AstNodeAssign* nodep) { + VL_RESTORER(m_alhs); + VL_RESTORER(m_currentStrength); if (m_graphing) { if (AstAssignW* assignWp = VN_CAST(nodep, AssignW)) addToAssignmentList(assignWp); @@ -1279,9 +1281,6 @@ class TristateVisitor final : public TristateBaseVisitor { } } iterateAndNextNull(nodep->lhsp()); - // back to default strength - m_currentStrength = VStrength::STRONG; - m_alhs = false; } } void visit(AstAssignW* nodep) override { visitAssign(nodep); } diff --git a/src/verilog.y b/src/verilog.y index 9c977f542..930683c25 100644 --- a/src/verilog.y +++ b/src/verilog.y @@ -2691,18 +2691,30 @@ genvar_initialization: // ==IEEE: genvar_initialization ; genvar_iteration: // ==IEEE: genvar_iteration - varRefBase '=' expr { $$ = new AstAssign($2,$1,$3); } - | varRefBase yP_PLUSEQ expr { $$ = new AstAssign($2,$1,new AstAdd ($2,$1->cloneTree(true),$3)); } - | varRefBase yP_MINUSEQ expr { $$ = new AstAssign($2,$1,new AstSub ($2,$1->cloneTree(true),$3)); } - | varRefBase yP_TIMESEQ expr { $$ = new AstAssign($2,$1,new AstMul ($2,$1->cloneTree(true),$3)); } - | varRefBase yP_DIVEQ expr { $$ = new AstAssign($2,$1,new AstDiv ($2,$1->cloneTree(true),$3)); } - | varRefBase yP_MODEQ expr { $$ = new AstAssign($2,$1,new AstModDiv ($2,$1->cloneTree(true),$3)); } - | varRefBase yP_ANDEQ expr { $$ = new AstAssign($2,$1,new AstAnd ($2,$1->cloneTree(true),$3)); } - | varRefBase yP_OREQ expr { $$ = new AstAssign($2,$1,new AstOr ($2,$1->cloneTree(true),$3)); } - | varRefBase yP_XOREQ expr { $$ = new AstAssign($2,$1,new AstXor ($2,$1->cloneTree(true),$3)); } - | varRefBase yP_SLEFTEQ expr { $$ = new AstAssign($2,$1,new AstShiftL ($2,$1->cloneTree(true),$3)); } - | varRefBase yP_SRIGHTEQ expr { $$ = new AstAssign($2,$1,new AstShiftR ($2,$1->cloneTree(true),$3)); } - | varRefBase yP_SSRIGHTEQ expr { $$ = new AstAssign($2,$1,new AstShiftRS($2,$1->cloneTree(true),$3)); } + varRefBase '=' expr + { $$ = new AstAssign{$2, $1, $3}; } + | varRefBase yP_PLUSEQ expr + { $$ = new AstAssign{$2, $1, new AstAdd{$2, $1->cloneTree(true), $3}}; } + | varRefBase yP_MINUSEQ expr + { $$ = new AstAssign{$2, $1, new AstSub{$2, $1->cloneTree(true), $3}}; } + | varRefBase yP_TIMESEQ expr + { $$ = new AstAssign{$2, $1, new AstMul{$2, $1->cloneTree(true), $3}}; } + | varRefBase yP_DIVEQ expr + { $$ = new AstAssign{$2, $1, new AstDiv{$2, $1->cloneTree(true), $3}}; } + | varRefBase yP_MODEQ expr + { $$ = new AstAssign{$2, $1, new AstModDiv{$2, $1->cloneTree(true), $3}}; } + | varRefBase yP_ANDEQ expr + { $$ = new AstAssign{$2, $1, new AstAnd{$2, $1->cloneTree(true), $3}}; } + | varRefBase yP_OREQ expr + { $$ = new AstAssign{$2, $1, new AstOr{$2, $1->cloneTree(true), $3}}; } + | varRefBase yP_XOREQ expr + { $$ = new AstAssign{$2, $1, new AstXor{$2, $1->cloneTree(true), $3}}; } + | varRefBase yP_SLEFTEQ expr + { $$ = new AstAssign{$2, $1, new AstShiftL{$2, $1->cloneTree(true), $3}}; } + | varRefBase yP_SRIGHTEQ expr + { $$ = new AstAssign{$2, $1, new AstShiftR{$2, $1->cloneTree(true), $3}}; } + | varRefBase yP_SSRIGHTEQ expr + { $$ = new AstAssign{$2, $1, new AstShiftRS{$2, $1->cloneTree(true), $3}}; } // // inc_or_dec_operator // When support ++ as a real AST type, maybe AstWhile::precondsp() becomes generic AstNodeExprStmt? | yP_PLUSPLUS varRefBase @@ -3425,17 +3437,28 @@ foperator_assignment: // IEEE: operator_assignment (for first part of | fexprLvalue '=' yD_FOPEN '(' expr ',' expr ')' { $$ = new AstFOpen($3,$1,$5,$7); } // //UNSUP ~f~exprLvalue yP_PLUS(etc) expr { UNSUP } - | fexprLvalue yP_PLUSEQ expr { $$ = new AstAssign($2,$1,new AstAdd ($2,$1->cloneTree(true),$3)); } - | fexprLvalue yP_MINUSEQ expr { $$ = new AstAssign($2,$1,new AstSub ($2,$1->cloneTree(true),$3)); } - | fexprLvalue yP_TIMESEQ expr { $$ = new AstAssign($2,$1,new AstMul ($2,$1->cloneTree(true),$3)); } - | fexprLvalue yP_DIVEQ expr { $$ = new AstAssign($2,$1,new AstDiv ($2,$1->cloneTree(true),$3)); } - | fexprLvalue yP_MODEQ expr { $$ = new AstAssign($2,$1,new AstModDiv ($2,$1->cloneTree(true),$3)); } - | fexprLvalue yP_ANDEQ expr { $$ = new AstAssign($2,$1,new AstAnd ($2,$1->cloneTree(true),$3)); } - | fexprLvalue yP_OREQ expr { $$ = new AstAssign($2,$1,new AstOr ($2,$1->cloneTree(true),$3)); } - | fexprLvalue yP_XOREQ expr { $$ = new AstAssign($2,$1,new AstXor ($2,$1->cloneTree(true),$3)); } - | fexprLvalue yP_SLEFTEQ expr { $$ = new AstAssign($2,$1,new AstShiftL ($2,$1->cloneTree(true),$3)); } - | fexprLvalue yP_SRIGHTEQ expr { $$ = new AstAssign($2,$1,new AstShiftR ($2,$1->cloneTree(true),$3)); } - | fexprLvalue yP_SSRIGHTEQ expr { $$ = new AstAssign($2,$1,new AstShiftRS($2,$1->cloneTree(true),$3)); } + | fexprLvalue yP_PLUSEQ expr + { $$ = new AstAssign{$2, $1, new AstAdd{$2, $1->cloneTree(true), $3}}; } + | fexprLvalue yP_MINUSEQ expr + { $$ = new AstAssign{$2, $1, new AstSub{$2, $1->cloneTree(true), $3}}; } + | fexprLvalue yP_TIMESEQ expr + { $$ = new AstAssign{$2, $1, new AstMul{$2, $1->cloneTree(true), $3}}; } + | fexprLvalue yP_DIVEQ expr + { $$ = new AstAssign{$2, $1, new AstDiv{$2, $1->cloneTree(true), $3}}; } + | fexprLvalue yP_MODEQ expr + { $$ = new AstAssign{$2, $1, new AstModDiv{$2, $1->cloneTree(true), $3}}; } + | fexprLvalue yP_ANDEQ expr + { $$ = new AstAssign{$2, $1, new AstAnd{$2, $1->cloneTree(true), $3}}; } + | fexprLvalue yP_OREQ expr + { $$ = new AstAssign{$2, $1, new AstOr{$2, $1->cloneTree(true), $3}}; } + | fexprLvalue yP_XOREQ expr + { $$ = new AstAssign{$2, $1, new AstXor{$2, $1->cloneTree(true), $3}}; } + | fexprLvalue yP_SLEFTEQ expr + { $$ = new AstAssign{$2, $1, new AstShiftL{$2, $1->cloneTree(true), $3}}; } + | fexprLvalue yP_SRIGHTEQ expr + { $$ = new AstAssign{$2, $1, new AstShiftR{$2, $1->cloneTree(true), $3}}; } + | fexprLvalue yP_SSRIGHTEQ expr + { $$ = new AstAssign{$2, $1, new AstShiftRS{$2, $1->cloneTree(true), $3}}; } //UNSUP replace above with: //UNSUP BISONPRE_COPY(operator_assignment,{s/~f~/f/g}) // {copied} ; @@ -4403,18 +4426,42 @@ expr: // IEEE: part of expression/constant_expression/ // // // IEEE: '(' operator_assignment ')' // // Need exprScope of variable_lvalue to prevent conflict - //UNSUP '(' ~p~exprScope '=' expr ')' { UNSUP } - //UNSUP '(' ~p~exprScope yP_PLUSEQ expr ')' { UNSUP } - //UNSUP '(' ~p~exprScope yP_MINUSEQ expr ')' { UNSUP } - //UNSUP '(' ~p~exprScope yP_TIMESEQ expr ')' { UNSUP } - //UNSUP '(' ~p~exprScope yP_DIVEQ expr ')' { UNSUP } - //UNSUP '(' ~p~exprScope yP_MODEQ expr ')' { UNSUP } - //UNSUP '(' ~p~exprScope yP_ANDEQ expr ')' { UNSUP } - //UNSUP '(' ~p~exprScope yP_OREQ expr ')' { UNSUP } - //UNSUP '(' ~p~exprScope yP_XOREQ expr ')' { UNSUP } - //UNSUP '(' ~p~exprScope yP_SLEFTEQ expr ')' { UNSUP } - //UNSUP '(' ~p~exprScope yP_SRIGHTEQ expr ')' { UNSUP } - //UNSUP '(' ~p~exprScope yP_SSRIGHTEQ expr ')' { UNSUP } + | '(' ~p~exprScope '=' expr ')' + { $$ = new AstExprStmt{$1, new AstAssign{$3, $2, $4}, + $2->cloneTree(true)}; } + | '(' ~p~exprScope yP_PLUSEQ expr ')' + { $$ = new AstExprStmt{$1, new AstAssign{$3, $2, new AstAdd{$3, $2->cloneTree(true), $4}}, + $2->cloneTree(true)}; } + | '(' ~p~exprScope yP_MINUSEQ expr ')' + { $$ = new AstExprStmt{$1, new AstAssign{$3, $2, new AstSub{$3, $2->cloneTree(true), $4}}, + $2->cloneTree(true)}; } + | '(' ~p~exprScope yP_TIMESEQ expr ')' + { $$ = new AstExprStmt{$1, new AstAssign{$3, $2, new AstMul{$3, $2->cloneTree(true), $4}}, + $2->cloneTree(true)}; } + | '(' ~p~exprScope yP_DIVEQ expr ')' + { $$ = new AstExprStmt{$1, new AstAssign{$3, $2, new AstDiv{$3, $2->cloneTree(true), $4}}, + $2->cloneTree(true)}; } + | '(' ~p~exprScope yP_MODEQ expr ')' + { $$ = new AstExprStmt{$1, new AstAssign{$3, $2, new AstModDiv{$3, $2->cloneTree(true), $4}}, + $2->cloneTree(true)}; } + | '(' ~p~exprScope yP_ANDEQ expr ')' + { $$ = new AstExprStmt{$1, new AstAssign{$3, $2, new AstAnd{$3, $2->cloneTree(true), $4}}, + $2->cloneTree(true)}; } + | '(' ~p~exprScope yP_OREQ expr ')' + { $$ = new AstExprStmt{$1, new AstAssign{$3, $2, new AstOr{$3, $2->cloneTree(true), $4}}, + $2->cloneTree(true)}; } + | '(' ~p~exprScope yP_XOREQ expr ')' + { $$ = new AstExprStmt{$1, new AstAssign{$3, $2, new AstXor{$3, $2->cloneTree(true), $4}}, + $2->cloneTree(true)}; } + | '(' ~p~exprScope yP_SLEFTEQ expr ')' + { $$ = new AstExprStmt{$1, new AstAssign{$3, $2, new AstShiftL{$3, $2->cloneTree(true), $4}}, + $2->cloneTree(true)}; } + | '(' ~p~exprScope yP_SRIGHTEQ expr ')' + { $$ = new AstExprStmt{$1, new AstAssign{$3, $2, new AstShiftR{$3, $2->cloneTree(true), $4}}, + $2->cloneTree(true)}; } + | '(' ~p~exprScope yP_SSRIGHTEQ expr ')' + { $$ = new AstExprStmt{$1, new AstAssign{$3, $2, new AstShiftRS{$3, $2->cloneTree(true), $4}}, + $2->cloneTree(true)}; } // // // IEEE: expression binary_operator expression | ~l~expr '+' ~r~expr { $$ = new AstAdd ($2,$1,$3); } diff --git a/test_regress/t/t_assign_expr.pl b/test_regress/t/t_assign_expr.pl new file mode 100755 index 000000000..1aa73f80a --- /dev/null +++ b/test_regress/t/t_assign_expr.pl @@ -0,0 +1,21 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 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_assign_expr.v b/test_regress/t/t_assign_expr.v new file mode 100644 index 000000000..b54320153 --- /dev/null +++ b/test_regress/t/t_assign_expr.v @@ -0,0 +1,77 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +`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); + +module t(/*AUTOARG*/); + + int a; + int b; + int i; + + initial begin + a = 10; + i = (a = 2); + `checkd(a, 2); `checkd(i, 2); + + a = 10; + i = (a += 2); + `checkd(a, 12); `checkd(i, 12); + + a = 10; + i = (a -= 2); + `checkd(a, 8); `checkd(i, 8); + + a = 10; + i = (a *= 2); + `checkd(a, 20); `checkd(i, 20); + + a = 10; + i = (a /= 2); + `checkd(a, 5); `checkd(i, 5); + + a = 11; + i = (a %= 2); + `checkd(a, 1); `checkd(i, 1); + + a = 10; + i = (a &= 2); + `checkd(a, 2); `checkd(i, 2); + + a = 8; + i = (a |= 2); + `checkd(a, 10); `checkd(i, 10); + + a = 10; + i = (a ^= 2); + `checkd(a, 8); `checkd(i, 8); + + a = 10; + i = (a <<= 2); + `checkd(a, 40); `checkd(i, 40); + + a = 10; + i = (a >>= 2); + `checkd(a, 2); `checkd(i, 2); + + a = 10; + i = (a >>>= 2); + `checkd(a, 2); `checkd(i, 2); + + a = 10; + i = (a = (b = 5)); + `checkd(a, 5); `checkd(i, 5); `checkd(b, 5); + + a = 10; + b = 6; + i = ((a += (b += 1) + 1)); + `checkd(a, 18); `checkd(i, 18); `checkd(b, 7); + + $write("*-* All Finished *-*\n"); + $finish; + end + +endmodule